/**
Copyright 2021 Forestry.io Holdings, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/ import { FieldDefinitionNode , ScalarTypeDefinitionNode , InputValueDefinitionNode , ObjectTypeDefinitionNode , InterfaceTypeDefinitionNode , NamedTypeNode , UnionTypeDefinitionNode , TypeDefinitionNode , DirectiveNode , EnumTypeDefinitionNode , InputObjectTypeDefinitionNode , DocumentNode , FragmentDefinitionNode , SelectionNode , SelectionSetNode , FieldNode , InlineFragmentNode , OperationDefinitionNode , } from 'graphql' ; import _ from 'lodash' ; const SysFieldDefinition = { kind : 'Field' as const , name : { kind : 'Name' as const , value : 'sys' , } , arguments : [ ] , directives : [ ] , selectionSet : { kind : 'SelectionSet' as const , selections : [ { kind : 'Field' as const , name : { kind : 'Name' as const , value : 'filename' , } , arguments : [ ] , directives : [ ] , } , { kind : 'Field' as const , name : { kind : 'Name' as const , value : 'basename' , } , arguments : [ ] , directives : [ ] , } , { kind : 'Field' as const , name : { kind : 'Name' as const , value : 'breadcrumbs' , } , arguments : [ ] , directives : [ ] , } , { kind : 'Field' as const , name : { kind : 'Name' as const , value : 'path' , } , arguments : [ ] , directives : [ ] , } , { kind : 'Field' as const , name : { kind : 'Name' as const , value : 'relativePath' , } , arguments : [ ] , directives : [ ] , } , { kind : 'Field' as const , name : { kind : 'Name' as const , value : 'extension' , } , arguments : [ ] , directives : [ ] , } , ] , } , } ; /**
 * the `gql` module provides functions and types which can be
 * used to build up the GraphQL AST. The primary reason for us using
 * this instead of the [builders provided by the graphql package](https://graphql.org/graphql-js/type/#examples)
 * is due to the dynamic and asynchronous nature of our needs.
 *
 * The tradeoff is a low-level API that's often more verbose, and it's
 * not a complete match of the GraphQL spec, so additional properties will likely
 * be needed as our needs grow.
 */ export const astBuilder = { /**
     * `FormFieldBuilder` acts as a shortcut to building an entire `ObjectTypeDefinition`, we use this
     * because all Tina field objects share a common set of fields ('name', 'label', 'component')
     */ FormFieldBuilder : ( { name , additionalFields , } ) => { return astBuilder . ObjectTypeDefinition ( { name : name , interfaces : [ astBuilder . NamedType ( { name : 'FormField' } ) ] , fields : [ astBuilder . FieldDefinition ( { name : 'name' , required : true , type : astBuilder . TYPES . String , } ) , astBuilder . FieldDefinition ( { name : 'label' , required : true , type : astBuilder . TYPES . String , } ) , astBuilder . FieldDefinition ( { name : 'component' , required : true , type : astBuilder . TYPES . String , } ) , ... ( additionalFields || [ ] ) , ] , } ) ; } , ScalarTypeDefinition : ( { name , description , } ) : ScalarTypeDefinitionNode => { return { kind : 'ScalarTypeDefinition' , name : { kind : 'Name' , value : name , } , description : { kind : 'StringValue' , value : description || '' , } , directives : [ ] , } ; } , InputValueDefinition : ( { name , type , list , required , } ) => { let res = { } ; const namedType = { kind : 'NamedType' as const , name : { kind : 'Name' as const , value : type , } , } ; const def = { kind : 'InputValueDefinition' as const , name : { kind : 'Name' as const , value : name , } , } ; if ( list ) { if ( required ) { res = { ... def , type : { kind : 'ListType' as const , type : { kind : 'NonNullType' , type : namedType , } , } , } ; } else { res = { ... def , type : { kind : 'ListType' as const , type : namedType , } , } ; } } else { if ( required ) { res = { ... def , type : { kind : 'NonNullType' , type : namedType , } , } ; } else { res = { ... def , type : namedType , } ; } } return res as InputValueDefinitionNode ; } , EnumDefinition : ( props ) : EnumTypeDefinitionNode => { return { kind : 'EnumTypeDefinition' , name : { kind : 'Name' , value : props . name , } , values : props . values . map ( ( val ) => { return { kind : 'EnumValueDefinition' , name : { kind : 'Name' , value : val , } , } ; } ) , } ; } , FieldNodeDefinition : ( { name , type , args = [ ] , list , required , } ) => ( { name : { kind : 'Name' as const , value : name } , kind : 'Field' as const , } as FieldNode ) , FieldDefinition : ( { name , type , args = [ ] , list , required , } ) => { let res = { } ; const namedType = { kind : 'NamedType' as const , name : { kind : 'Name' as const , value : type , } , } ; const def = { kind : 'FieldDefinition' as const , name : { kind : 'Name' as const , value : name , } , arguments : args , } ; if ( list ) { if ( required ) { res = { ... def , type : { kind : 'NonNullType' as const , type : { kind : 'ListType' as const , type : { kind : 'NonNullType' , type : namedType , } , } , } , } ; } else { res = { ... def , type : { kind : 'ListType' as const , type : namedType , } , } ; } } else { if ( required ) { res = { ... def , type : { kind : 'NonNullType' as const , type : namedType , } , } ; } else { res = { ... def , type : namedType , } ; } } return res as FieldDefinitionNode ; } , InterfaceTypeDefinition : ( { name , fields , description = '' , } ) : InterfaceTypeDefinitionNode => { return { kind : 'InterfaceTypeDefinition' , description : { kind : 'StringValue' , value : description } , name : { kind : 'Name' , value : name , } , interfaces : [ ] , directives : [ ] , fields : fields , } ; } , InputObjectTypeDefinition : ( { name , fields , } ) : InputObjectTypeDefinitionNode => ( { kind : 'InputObjectTypeDefinition' as const , name : { kind : 'Name' as const , value : name , } , fields , } ) , UnionTypeDefinition : ( { name , types , } ) : UnionTypeDefinitionNode => ( { kind : 'UnionTypeDefinition' as const , name : { kind : 'Name' as const , value : name , } , directives : [ ] , types : types . map ( ( name ) => ( { kind : 'NamedType' as const , name : { kind : 'Name' as const , value : name , } , } ) ) , } ) , NamedType : ( { name } ) : NamedTypeNode => { return { kind : 'NamedType' , name : { kind : 'Name' , value : name , } , } ; } , ObjectTypeDefinition : ( { name , fields , interfaces = [ ] , directives = [ ] , args = [ ] , } ) : ObjectTypeDefinitionNode => ( { kind : 'ObjectTypeDefinition' as const , interfaces , directives , name : { kind : 'Name' as const , value : name , } , fields , } ) , FieldWithSelectionSetDefinition : ( { name , selections , } ) => { return { name : { kind : 'Name' as const , value : name } , kind : 'Field' as const , selectionSet : { kind : 'SelectionSet' as const , selections , } , } ; } , InlineFragmentDefinition : ( { name , selections , } ) : InlineFragmentNode => { return { kind : 'InlineFragment' as const , selectionSet : { kind : 'SelectionSet' as const , selections , } , typeCondition : { kind : 'NamedType' as const , name : { kind : 'Name' as const , value : name , } , } , } ; } , FragmentDefinition : ( { name , fragmentName , selections , } ) : FragmentDefinitionNode => { return { kind : 'FragmentDefinition' as const , name : { kind : 'Name' as const , value : fragmentName , } , typeCondition : { kind : 'NamedType' as const , name : { kind : 'Name' as const , value : name , } , } , directives : [ ] , selectionSet : { kind : 'SelectionSet' as const , selections , } , } ; } , TYPES : { Scalar : ( type ) => { const scalars = { string : 'String' , boolean : 'Boolean' , number : 'Int' , datetime : 'String' , image : 'String' , text : 'String' , } ; return scalars [ type ] ; } , MultiCollectionDocument : 'DocumentNode' , CollectionDocumentUnion : 'DocumentUnion' , String : 'String' , Reference : 'Reference' , Collection : 'Collection' , ID : 'ID' , SystemInfo : 'SystemInfo' , Boolean : 'Boolean' , JSON : 'JSON' , Node : 'Node' , PageInfo : 'PageInfo' , Connection : 'Connection' , Number : 'Int' , Document : 'Document' , } , QueryOperationDefinition : ( { queryName , fragName , } ) : OperationDefinitionNode => { return { kind : 'OperationDefinition' as const , operation : 'query' as const , name : { kind : 'Name' as const , value : queryName , } , variableDefinitions : [ { kind : 'VariableDefinition' as const , type : { kind : 'NonNullType' , type : { kind : 'NamedType' , name : { kind : 'Name' as const , value : 'String' } , } , } , variable : { kind : 'Variable' as const , name : { kind : 'Name' as const , value : 'relativePath' } , } , } , ] , selectionSet : { kind : 'SelectionSet' as const , selections : [ { kind : 'Field' , name : { kind : 'Name' , value : queryName , } , arguments : [ { kind : 'Argument' , name : { kind : 'Name' , value : 'relativePath' , } , value : { kind : 'Variable' , name : { kind : 'Name' , value : 'relativePath' , } , } , } , ] , directives : [ ] , selectionSet : { kind : 'SelectionSet' , selections : [ SysFieldDefinition , { kind : 'Field' , name : { kind : 'Name' , value : 'id' , } , arguments : [ ] , directives : [ ] , } , { kind : 'Field' , name : { kind : 'Name' , value : 'data' , } , arguments : [ ] , directives : [ ] , selectionSet : { kind : 'SelectionSet' , selections : [ { kind : 'FragmentSpread' , name : { kind : 'Name' , value : fragName , } , directives : [ ] , } , ] , } , } , ] , } , } , ] , } , } ; } , ListQueryOperationDefinition : ( { queryName , fragName , } ) : OperationDefinitionNode => { return { kind : 'OperationDefinition' , operation : 'query' , name : { kind : 'Name' , value : queryName , } , variableDefinitions : [ ] , directives : [ ] , selectionSet : { kind : 'SelectionSet' , selections : [ { kind : 'Field' , name : { kind : 'Name' , value : queryName , } , arguments : [ ] , directives : [ ] , selectionSet : { kind : 'SelectionSet' , selections : [ { kind : 'Field' , name : { kind : 'Name' , value : 'totalCount' , } , arguments : [ ] , directives : [ ] , } , { kind : 'Field' , name : { kind : 'Name' , value : 'edges' , } , arguments : [ ] , directives : [ ] , selectionSet : { kind : 'SelectionSet' , selections : [ { kind : 'Field' , name : { kind : 'Name' , value : 'node' , } , arguments : [ ] , directives : [ ] , selectionSet : { kind : 'SelectionSet' , selections : [ { kind : 'Field' , name : { kind : 'Name' , value : 'id' , } , arguments : [ ] , directives : [ ] , } , SysFieldDefinition , { kind : 'Field' , name : { kind : 'Name' , value : 'data' , } , arguments : [ ] , directives : [ ] , selectionSet : { kind : 'SelectionSet' , selections : [ { kind : 'FragmentSpread' , name : { kind : 'Name' , value : fragName , } , directives : [ ] , } , ] , } , } , ] , } , } , ] , } , } , ] , } , } , ] , } , } ; } , toGraphQLAst : ( ast ) : DocumentNode => { const definitions = _ . uniqBy ( [ ... extractInlineTypes ( ast . query ) , ... extractInlineTypes ( ast . globalTemplates ) , ... ast . definitions , ] , ( field ) => field . name . value ) ; return { kind : 'Document' , definitions , } ; } , } ; type scalarNames = 'string' | 'boolean' | 'datetime' | 'image' | 'text' | 'number' ; const capitalize = ( s ) => { if ( typeof s !== 'string' ) return '' ; return s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) ; } ; export const extractInlineTypes = ( item ) => { if ( Array . isArray ( item ) ) { const accumulator = item . map ( ( i ) => { return extractInlineTypes ( i ) ; } ) ; return _ . flattenDeep ( accumulator ) ; } else { const accumulator = [ item ] ; for ( const node of walk ( item ) ) { if ( node . kind === 'UnionTypeDefinition' ) { node . types = _ . uniqBy ( node . types , ( type ) => type . name . value ) ; } if ( node . kind === 'NamedType' ) { if ( typeof node . name . value !== 'string' ) { accumulator . push ( node . name . value ) ; node . name . value = node . name . value . name . value ; } } } return accumulator ; } } ; export function * walk ( maybeNode , visited = new WeakSet ( ) ) { if ( typeof maybeNode === 'string' ) { return ; } if ( visited . has ( maybeNode ) ) { return ; } for ( const value of Object . values ( maybeNode ) ) { if ( Array . isArray ( value ) ) { for ( const element of value ) { yield * walk ( element , visited ) ; } } else { yield * walk ( value , visited ) ; } } yield maybeNode ; visited . add ( maybeNode ) ; } export function addNamespaceToSchema < T extends object | string > ( maybeNode , namespace = [ ] ) { if ( typeof maybeNode === 'string' ) { return maybeNode ; } if ( typeof maybeNode === 'boolean' ) { return maybeNode ; } const newNode = maybeNode ; const keys = Object . keys ( maybeNode ) ; Object . values ( maybeNode ) . map ( ( m , index ) => { const key = keys [ index ] ; if ( Array . isArray ( m ) ) { newNode [ key ] = m . map ( ( element ) => { if ( ! element ) { return ; } if ( ! element . hasOwnProperty ( 'name' ) ) { return element ; } const value = element . name || element . value ; return addNamespaceToSchema ( element , [ ... namespace , value ] ) ; } ) ; } else { if ( ! m ) { return ; } if ( ! m . hasOwnProperty ( 'name' ) ) { newNode [ key ] = m ; } else { newNode [ key ] = addNamespaceToSchema ( m , [ ... namespace , m . name ] ) ; } } } ) ; return { ... newNode , namespace : namespace } ; } const generateNamespacedFieldName = ( names , suffix = '' ) => { return ( suffix ? [ ... names , suffix ] : names ) . map ( capitalize ) . join ( '' ) ; } ; export const NAMER = { dataFilterTypeNameOn : ( namespace ) => { return generateNamespacedFieldName ( namespace , '_FilterOn' ) ; } , dataFilterTypeName : ( namespace ) => { return generateNamespacedFieldName ( namespace , 'Filter' ) ; } , dataMutationTypeNameOn : ( namespace ) => { return generateNamespacedFieldName ( namespace , '_MutationOn' ) ; } , dataMutationTypeName : ( namespace ) => { return generateNamespacedFieldName ( namespace , 'Mutation' ) ; } , updateName : ( namespace ) => { return 'update' + generateNamespacedFieldName ( namespace , 'Document' ) ; } , createName : ( namespace ) => { return 'create' + generateNamespacedFieldName ( namespace , 'Document' ) ; } , queryName : ( namespace ) => { return 'get' + generateNamespacedFieldName ( namespace , 'Document' ) ; } , generateQueryListName : ( namespace ) => { return 'get' + generateNamespacedFieldName ( namespace , 'List' ) ; } , fragmentName : ( namespace ) => { return generateNamespacedFieldName ( namespace , '' ) + 'Parts' ; } , collectionTypeName : ( namespace ) => { return generateNamespacedFieldName ( namespace , 'Collection' ) ; } , documentTypeName : ( namespace ) => { return generateNamespacedFieldName ( namespace , 'Document' ) ; } , dataTypeName : ( namespace ) => { return generateNamespacedFieldName ( namespace , '' ) ; } , referenceConnectionType : ( namespace ) => { return generateNamespacedFieldName ( namespace , 'Connection' ) ; } , referenceConnectionEdgesTypeName : ( namespace ) => { return generateNamespacedFieldName ( namespace , 'ConnectionEdges' ) ; } , } ;