UNPKG

apollo-codegen-core

Version:
340 lines (306 loc) 8.96 kB
import { GraphQLSchema, GraphQLType, GraphQLObjectType, GraphQLCompositeType, GraphQLInputType, GraphQLUnionType, GraphQLInterfaceType, DocumentNode, TypeNode, } from "graphql"; import { compileToIR, CompilerContext, SelectionSet, Field, FragmentSpread, } from "./"; import { collectFragmentsReferenced } from "./visitors/collectFragmentsReferenced"; import { generateOperationId } from "./visitors/generateOperationId"; import { typeCaseForSelectionSet } from "./visitors/typeCase"; import { collectAndMergeFields } from "./visitors/collectAndMergeFields"; import "../utilities/array"; export interface CompilerOptions { addTypename?: boolean; mergeInFieldsFromFragmentSpreads?: boolean; passthroughCustomScalars?: boolean; customScalarsPrefix?: string; namespace?: string; generateOperationIds?: boolean; exposeTypeNodes?: boolean; } export interface LegacyCompilerContext { schema: GraphQLSchema; operations: { [operationName: string]: LegacyOperation }; fragments: { [fragmentName: string]: LegacyFragment }; typesUsed: GraphQLType[]; options: CompilerOptions; unionTypes: GraphQLUnionType[]; interfaceTypes: Map< GraphQLInterfaceType, (GraphQLObjectType | GraphQLInterfaceType)[] >; } export interface LegacyOperation { filePath?: string; operationName: string; operationId?: string; operationType: string; rootType: GraphQLObjectType; variables: { name: string; type: GraphQLType; }[]; source: string; sourceWithFragments?: string; fields: LegacyField[]; fragmentSpreads?: string[]; inlineFragments?: LegacyInlineFragment[]; fragmentsReferenced: string[]; } export interface LegacyFragment { filePath?: string; fragmentName: string; source: string; typeCondition: GraphQLCompositeType; possibleTypes: GraphQLObjectType[]; fields: LegacyField[]; fragmentSpreads: string[]; inlineFragments: LegacyInlineFragment[]; } export interface LegacyInlineFragment { typeCondition: GraphQLObjectType; possibleTypes: GraphQLObjectType[]; fields: LegacyField[]; fragmentSpreads: string[]; } export interface LegacyField { responseName: string; fieldName: string; args?: Argument[]; type: GraphQLType; typeNode?: TypeNode; description?: string; isConditional?: boolean; conditions?: BooleanCondition[]; isDeprecated?: boolean; deprecationReason?: string; fields?: LegacyField[]; fragmentSpreads?: string[]; inlineFragments?: LegacyInlineFragment[]; } export interface BooleanCondition { variableName: string; inverted: boolean; } export interface Argument { name: string; value: any; type?: GraphQLInputType; } export function compileToLegacyIR( schema: GraphQLSchema, document: DocumentNode, options: CompilerOptions = { mergeInFieldsFromFragmentSpreads: true, exposeTypeNodes: false, } ): LegacyCompilerContext { const context = compileToIR(schema, document, options); const transformer = new LegacyIRTransformer(context, options); return transformer.transformIR(); } class LegacyIRTransformer { constructor( public context: CompilerContext, public options: CompilerOptions = { mergeInFieldsFromFragmentSpreads: true } ) {} transformIR(): LegacyCompilerContext { const operations: { [operationName: string]: LegacyOperation; } = Object.create({}); for (const [operationName, operation] of Object.entries( this.context.operations )) { const { filePath, operationType, rootType, variables, source, selectionSet, } = operation; const fragmentsReferenced = collectFragmentsReferenced( selectionSet, this.context.fragments ); const { sourceWithFragments, operationId } = generateOperationId( operation, this.context.fragments, fragmentsReferenced ); operations[operationName] = { filePath, operationName, operationType, rootType, variables, source, ...this.transformSelectionSetToLegacyIR(selectionSet), fragmentsReferenced: Array.from(fragmentsReferenced), sourceWithFragments, operationId, }; } const fragments: { [fragmentName: string]: LegacyFragment } = Object.create( {} ); for (const [fragmentName, fragment] of Object.entries( this.context.fragments )) { const { selectionSet, type, ...fragmentWithoutSelectionSet } = fragment; fragments[fragmentName] = { typeCondition: type, possibleTypes: selectionSet.possibleTypes, ...fragmentWithoutSelectionSet, ...this.transformSelectionSetToLegacyIR(selectionSet), }; } const legacyContext: LegacyCompilerContext = { schema: this.context.schema, operations, fragments, typesUsed: this.context.typesUsed, options: this.options, unionTypes: this.context.unionTypes, interfaceTypes: this.context.interfaceTypes, }; return legacyContext; } transformSelectionSetToLegacyIR(selectionSet: SelectionSet) { const typeCase = typeCaseForSelectionSet( selectionSet, !!this.options.mergeInFieldsFromFragmentSpreads ); const fields: LegacyField[] = this.transformFieldsToLegacyIR( collectAndMergeFields(typeCase.default, false) ); const inlineFragments: LegacyInlineFragment[] = typeCase.variants.flatMap( (variant) => { const fields = this.transformFieldsToLegacyIR( collectAndMergeFields(variant, false) ); if ( // Filter out records that represent the same possible types as the default record. selectionSet.possibleTypes.every((type) => variant.possibleTypes.includes(type) ) && // Filter out empty records for consistency with legacy compiler. fields.length < 1 ) return undefined; const fragmentSpreads: string[] = this.collectFragmentSpreads( selectionSet, variant.possibleTypes ).map((fragmentSpread: FragmentSpread) => fragmentSpread.fragmentName); return variant.possibleTypes.map((possibleType) => { return { typeCondition: possibleType, possibleTypes: [possibleType], fields, fragmentSpreads, } as LegacyInlineFragment; }); } ); for (const inlineFragment of inlineFragments) { inlineFragments[inlineFragment.typeCondition.name as any] = inlineFragment; } const fragmentSpreads: string[] = this.collectFragmentSpreads( selectionSet ).map((fragmentSpread: FragmentSpread) => fragmentSpread.fragmentName); return { fields, fragmentSpreads, inlineFragments, }; } transformFieldsToLegacyIR(fields: Field[]) { return fields.map((field) => { const { args, type, typeNode, isConditional, description, isDeprecated, deprecationReason, selectionSet, } = field; const conditions = field.conditions && field.conditions.length > 0 ? field.conditions.map(({ kind, variableName, inverted }) => { return { kind, variableName, inverted, }; }) : undefined; return { responseName: field.alias || field.name, fieldName: field.name, type, typeNode, args, isConditional, conditions, description, isDeprecated, deprecationReason, ...(selectionSet ? this.transformSelectionSetToLegacyIR(selectionSet) : {}), } as LegacyField; }); } collectFragmentSpreads( selectionSet: SelectionSet, possibleTypes: GraphQLObjectType[] = selectionSet.possibleTypes ): FragmentSpread[] { const fragmentSpreads: FragmentSpread[] = []; for (const selection of selectionSet.selections) { switch (selection.kind) { case "FragmentSpread": fragmentSpreads.push(selection); break; case "TypeCondition": if ( possibleTypes.every((type) => selection.selectionSet.possibleTypes.includes(type) ) ) { fragmentSpreads.push( ...this.collectFragmentSpreads( selection.selectionSet, possibleTypes ) ); } break; case "BooleanCondition": fragmentSpreads.push( ...this.collectFragmentSpreads( selection.selectionSet, possibleTypes ) ); break; } } // Unique the fragment spreads before returning them. return Array.from(new Set(fragmentSpreads)); } }