import { visit , DocumentNode , ObjectTypeDefinitionNode , ObjectTypeExtensionNode , Kind , TypeDefinitionNode , TypeExtensionNode , EnumTypeDefinitionNode , EnumTypeExtensionNode , InputObjectTypeDefinitionNode , InputObjectTypeExtensionNode , GraphQLSchema , isScalarType , InterfaceTypeDefinitionNode , InterfaceTypeExtensionNode , } from 'graphql' ; import { pascalCase } from 'change-case-all' ; import { unique , withQuotes , buildBlock , pushUnique , concatByKey , uniqueByKey , createObject , collectUsedTypes , indent , } from './utils' ; import { ModulesConfig } from './config' ; import { BaseVisitor } from '@graphql-codegen/visitor-plugin-common' ; type RegistryKeys = 'objects' | 'inputs' | 'interfaces' | 'scalars' | 'unions' | 'enums' ; type Registry = Record < RegistryKeys , string [ ] > ; const registryKeys = [ 'objects' , 'inputs' , 'interfaces' , 'scalars' , 'unions' , 'enums' ] ; const resolverKeys = [ 'scalars' , 'objects' , 'enums' ] ; export function buildModule ( name , doc , { importNamespace , importPath , encapsulate , shouldDeclare , rootTypes , schema , baseVisitor , useGraphQLModules , } ) { const picks = createObject ( registryKeys , ( ) => ( { } ) ) ; const defined = createObject ( registryKeys , ( ) => [ ] ) ; const extended = createObject ( registryKeys , ( ) => [ ] ) ; const usedTypes = collectUsedTypes ( doc ) ; visit ( doc , { ObjectTypeDefinition ( node ) { collectTypeDefinition ( node ) ; } , ObjectTypeExtension ( node ) { collectTypeExtension ( node ) ; } , InputObjectTypeDefinition ( node ) { collectTypeDefinition ( node ) ; } , InputObjectTypeExtension ( node ) { collectTypeExtension ( node ) ; } , InterfaceTypeDefinition ( node ) { collectTypeDefinition ( node ) ; } , InterfaceTypeExtension ( node ) { collectTypeExtension ( node ) ; } , ScalarTypeDefinition ( node ) { collectTypeDefinition ( node ) ; } , UnionTypeDefinition ( node ) { collectTypeDefinition ( node ) ; } , UnionTypeExtension ( node ) { collectTypeExtension ( node ) ; } , EnumTypeDefinition ( node ) { collectTypeDefinition ( node ) ; } , EnumTypeExtension ( node ) { collectTypeExtension ( node ) ; } , } ) ; const visited = createObject ( registryKeys , key => concatByKey ( defined , extended , key ) ) ; const external = createObject ( registryKeys , key => uniqueByKey ( extended , defined , key ) ) ; const imports = [ `import * as ${ importNamespace } from "${ importPath }";` ] ; if ( useGraphQLModules ) { imports . push ( `import * as gm from "graphql-modules";` ) ; } let content = [ printDefinedFields ( ) , printDefinedEnumValues ( ) , printDefinedInputFields ( ) , printSchemaTypes ( usedTypes ) , printScalars ( visited ) , printResolveSignaturesPerType ( visited ) , printResolversType ( visited ) , useGraphQLModules ? printResolveMiddlewareMap ( ) : undefined , ] . filter ( Boolean ) . join ( '\n\n' ) ; if ( encapsulate === 'namespace' ) { content = `${ shouldDeclare ? 'declare' : 'export' } namespace ${ baseVisitor . convertName ( name , { suffix : 'Module' , useTypesPrefix : false , useTypesSuffix : false , } ) } {\n` + ( shouldDeclare ? `${ indent ( 2 ) ( imports . join ( '\n' ) ) }\n` : '' ) + indent ( 2 ) ( content ) + '\n}' ; } return [ ... ( ! shouldDeclare ? imports : [ ] ) , content ] . filter ( Boolean ) . join ( '\n' ) ; /**
     * A dictionary of fields to pick from an object
     */ function printDefinedFields ( ) { return buildBlock ( { name : `interface DefinedFields` , lines : [ ... visited . objects , ... visited . interfaces ] . map ( typeName => `${ typeName }: ${ printPicks ( typeName , { ... picks . objects , ... picks . interfaces , } ) };` ) , } ) ; } /**
     * A dictionary of values to pick from an enum
     */ function printDefinedEnumValues ( ) { return buildBlock ( { name : `interface DefinedEnumValues` , lines : visited . enums . map ( typeName => `${ typeName }: ${ printPicks ( typeName , picks . enums ) };` ) , } ) ; } function encapsulateTypeName ( typeName ) { if ( encapsulate === 'prefix' ) { return `${ pascalCase ( name ) }_${ typeName }` ; } return typeName ; } /**
     * A dictionary of fields to pick from an input
     */ function printDefinedInputFields ( ) { return buildBlock ( { name : `interface DefinedInputFields` , lines : visited . inputs . map ( typeName => `${ typeName }: ${ printPicks ( typeName , picks . inputs ) };` ) , } ) ; } /**
     * Prints signatures of schema types with picks
     */ function printSchemaTypes ( types ) { return types . filter ( type => ! visited . scalars . includes ( type ) ) . map ( printExportType ) . join ( '\n' ) ; } function printResolveSignaturesPerType ( registry ) { return [ [ ... registry . objects , ... registry . interfaces ] . map ( name => printResolverType ( name , 'DefinedFields' , ! rootTypes . includes ( name ) && defined . objects . includes ( name ) ? ` | '__isTypeOf'` : '' ) ) . join ( '\n' ) , ] . join ( '\n' ) ; } function printScalars ( registry ) { if ( ! registry . scalars . length ) { return '' ; } return [ `export type ${ encapsulateTypeName ( 'Scalars' ) } = Pick<${ importNamespace }.Scalars, ${ registry . scalars . map ( withQuotes ) . join ( ' | ' ) }>;` , ... registry . scalars . map ( scalar => { const convertedName = baseVisitor . convertName ( scalar , { suffix : 'ScalarConfig' , } ) ; return `export type ${ encapsulateTypeName ( convertedName ) } = ${ importNamespace }.${ convertedName };` ; } ) , ] . join ( '\n' ) ; } /**
     * Aggregation of type resolver signatures
     */ function printResolversType ( registry ) { const lines = [ ] ; for ( const kind in registry ) { const k = kind as RegistryKeys ; if ( registry . hasOwnProperty ( k ) && resolverKeys . includes ( k as any ) ) { const types = registry [ k ] ; types . forEach ( typeName => { if ( k === 'enums' ) { return ; } else if ( k === 'scalars' ) { lines . push ( `${ typeName }?: ${ encapsulateTypeName ( importNamespace ) }.Resolvers['${ typeName }'];` ) ; } else { lines . push ( `${ typeName }?: ${ encapsulateTypeName ( typeName ) }Resolvers;` ) ; } } ) ; } } return buildBlock ( { name : `export interface ${ encapsulateTypeName ( 'Resolvers' ) }` , lines , } ) ; } /**
     * Signature for a map of resolve middlewares
     */ function printResolveMiddlewareMap ( ) { const wildcardField = printResolveMiddlewareRecord ( withQuotes ( '*' ) ) ; const blocks = [ buildBlock ( { name : `${ withQuotes ( '*' ) }?:` , lines : [ wildcardField ] } ) ] ; for ( const typeName in picks . objects ) { if ( picks . objects . hasOwnProperty ( typeName ) ) { const fields = picks . objects [ typeName ] ; const lines = [ wildcardField ] . concat ( fields . map ( field => printResolveMiddlewareRecord ( field ) ) ) ; blocks . push ( buildBlock ( { name : `${ typeName }?:` , lines , } ) ) ; } } return buildBlock ( { name : `export interface ${ encapsulateTypeName ( 'MiddlewareMap' ) }` , lines : blocks , } ) ; } function printResolveMiddlewareRecord ( path ) { return `${ path }?: gm.Middleware[];` ; } function printResolverType ( typeName , picksTypeName , extraKeys = '' ) { return `export type ${ encapsulateTypeName ( `${ typeName }Resolvers` ) } = Pick<${ importNamespace }.${ baseVisitor . convertName ( typeName , { suffix : 'Resolvers' , } ) }, ${ picksTypeName }['${ typeName }']${ extraKeys }>;` ; } function printPicks ( typeName , records ) { return records [ typeName ] . filter ( unique ) . map ( withQuotes ) . join ( ' | ' ) ; } function printTypeBody ( typeName ) { const coreType = `${ importNamespace }.${ baseVisitor . convertName ( typeName , { useTypesSuffix : true , useTypesPrefix : true , } ) }` ; if ( external . enums . includes ( typeName ) || external . objects . includes ( typeName ) ) { if ( schema && isScalarType ( schema . getType ( typeName ) ) ) { return `${ importNamespace }.Scalars['${ typeName }']` ; } return coreType ; } if ( defined . enums . includes ( typeName ) && picks . enums [ typeName ] ) { return `DefinedEnumValues['${ typeName }']` ; } if ( defined . objects . includes ( typeName ) && picks . objects [ typeName ] ) { return `Pick<${ coreType }, DefinedFields['${ typeName }']>` ; } if ( defined . interfaces . includes ( typeName ) && picks . interfaces [ typeName ] ) { return `Pick<${ coreType }, DefinedFields['${ typeName }']>` ; } if ( defined . inputs . includes ( typeName ) && picks . inputs [ typeName ] ) { return `Pick<${ coreType }, DefinedInputFields['${ typeName }']>` ; } return coreType ; } function printExportType ( typeName ) { return `export type ${ encapsulateTypeName ( typeName ) } = ${ printTypeBody ( typeName ) };` ; } function collectFields ( node , picksObj ) { const name = node . name . value ; if ( node . fields ) { if ( ! picksObj [ name ] ) { picksObj [ name ] = [ ] ; } node . fields . forEach ( field => { picksObj [ name ] . push ( field . name . value ) ; } ) ; } } function collectValuesFromEnum ( node ) { const name = node . name . value ; if ( node . values ) { if ( ! picks . enums [ name ] ) { picks . enums [ name ] = [ ] ; } node . values . forEach ( field => { picks . enums [ name ] . push ( field . name . value ) ; } ) ; } } function collectTypeDefinition ( node ) { const name = node . name . value ; switch ( node . kind ) { case Kind . OBJECT_TYPE_DEFINITION : { defined . objects . push ( name ) ; collectFields ( node , picks . objects ) ; break ; } case Kind . ENUM_TYPE_DEFINITION : { defined . enums . push ( name ) ; collectValuesFromEnum ( node ) ; break ; } case Kind . INPUT_OBJECT_TYPE_DEFINITION : { defined . inputs . push ( name ) ; collectFields ( node , picks . inputs ) ; break ; } case Kind . SCALAR_TYPE_DEFINITION : { defined . scalars . push ( name ) ; break ; } case Kind . INTERFACE_TYPE_DEFINITION : { defined . interfaces . push ( name ) ; collectFields ( node , picks . interfaces ) ; break ; } case Kind . UNION_TYPE_DEFINITION : { defined . unions . push ( name ) ; break ; } } } function collectTypeExtension ( node ) { const name = node . name . value ; switch ( node . kind ) { case Kind . OBJECT_TYPE_EXTENSION : { collectFields ( node , picks . objects ) ; if ( rootTypes . includes ( name ) ) { pushUnique ( defined . objects , name ) ; return ; } pushUnique ( extended . objects , name ) ; break ; } case Kind . ENUM_TYPE_EXTENSION : { collectValuesFromEnum ( node ) ; pushUnique ( extended . enums , name ) ; break ; } case Kind . INPUT_OBJECT_TYPE_EXTENSION : { collectFields ( node , picks . inputs ) ; pushUnique ( extended . inputs , name ) ; break ; } case Kind . INTERFACE_TYPE_EXTENSION : { collectFields ( node , picks . interfaces ) ; pushUnique ( extended . interfaces , name ) ; break ; } case Kind . UNION_TYPE_EXTENSION : { pushUnique ( extended . unions , name ) ; break ; } } } }