import { print , typeFromAST , getNamedType , isAbstractType , Kind , isCompositeType , GraphQLOutputType , GraphQLInputType , GraphQLObjectType , GraphQLError , GraphQLSchema , GraphQLType , GraphQLCompositeType , DocumentNode , OperationDefinitionNode , FragmentDefinitionNode , SelectionSetNode , SelectionNode , isSpecifiedScalarType , isEnumType , isUnionType , isScalarType , isObjectType , isInputObjectType , isInterfaceType , NonNullTypeNode , GraphQLNonNull , } from 'graphql' ; import { getOperationRootType , getFieldDef , valueFromValueNode , filePathForNode , withTypenameFieldAddedWhereNeeded , isMetaFieldName , } from '../utilities/graphql' ; export interface CompilerOptions { addTypename ? : boolean ; mergeInFieldsFromFragmentSpreads ? : boolean ; passthroughCustomScalars ? : boolean ; customScalarsPrefix ? : string ; namespace ? : string ; generateOperationIds ? : boolean ; operationIdsPath ? : string ; addS3Wrapper ? : boolean ; } export interface CompilerContext { schema : GraphQLSchema ; typesUsed : GraphQLType [ ] ; operations : { [ operationName ] : Operation ; } ; fragments : { [ fragmentName ] : Fragment ; } ; options : CompilerOptions ; } export interface Operation { operationId ? : string ; operationName : string ; operationType : string ; variables : { name : string ; type : GraphQLType ; } [ ] ; filePath : string ; source : string ; rootType : GraphQLObjectType ; selectionSet : SelectionSet ; } export interface Fragment { filePath : string ; fragmentName : string ; source : string ; type : GraphQLCompositeType ; selectionSet : SelectionSet ; } export interface SelectionSet { possibleTypes : GraphQLObjectType [ ] ; selections : Selection [ ] ; } export interface Argument { name : string ; value : any ; type ? : GraphQLInputType ; } export type Selection = Field | TypeCondition | BooleanCondition | FragmentSpread ; export interface Field { kind : 'Field' ; responseKey : string ; name : string ; alias ? : string ; args ? : Argument [ ] ; type : GraphQLOutputType ; description ? : string ; isDeprecated ? : boolean ; deprecationReason ? : string ; isConditional ? : boolean ; selectionSet ? : SelectionSet ; } export interface TypeCondition { kind : 'TypeCondition' ; type : GraphQLCompositeType ; selectionSet : SelectionSet ; } export interface BooleanCondition { kind : 'BooleanCondition' ; variableName : string ; inverted : boolean ; selectionSet : SelectionSet ; } export interface FragmentSpread { kind : 'FragmentSpread' ; fragmentName : string ; isConditional ? : boolean ; selectionSet : SelectionSet ; } export function compileToIR ( schema , document , options = { } ) { if ( options . addTypename ) { document = withTypenameFieldAddedWhereNeeded ( document ) ; } const compiler = new Compiler ( schema , options ) ; const operations = Object . create ( null ) ; const fragments = Object . create ( null ) ; for ( const definition of document . definitions ) { try { switch ( definition . kind ) { case Kind . OPERATION_DEFINITION : const operation = compiler . compileOperation ( definition ) ; operations [ operation . operationName ] = operation ; break ; case Kind . FRAGMENT_DEFINITION : const fragment = compiler . compileFragment ( definition ) ; fragments [ fragment . fragmentName ] = fragment ; break ; } } catch ( e ) { if ( e instanceof GraphQLError ) { if ( definition . kind === 'OperationDefinition' && definition . name ) { const locInfo = definition . name . loc ? `in ${ definition . name . loc . source . name }` : '' ; console . log ( `${ e . message } but found ${ definition . operation }: ${ definition . name . value } ${ locInfo }  ` ) ; } else { console . log ( e . message ) ; } } } } for ( const fragmentSpread of compiler . unresolvedFragmentSpreads ) { const fragment = fragments [ fragmentSpread . fragmentName ] ; if ( ! fragment ) { throw new Error ( `Cannot find fragment "${ fragmentSpread . fragmentName }"` ) ; } const possibleTypes = fragment . selectionSet . possibleTypes . filter ( type => fragmentSpread . selectionSet . possibleTypes . includes ( type ) ) ; fragmentSpread . isConditional = fragment . selectionSet . possibleTypes . some ( type => ! fragmentSpread . selectionSet . possibleTypes . includes ( type ) ) ; fragmentSpread . selectionSet = { possibleTypes , selections : fragment . selectionSet . selections , } ; } const typesUsed = compiler . typesUsed ; return { schema , typesUsed , operations , fragments , options } ; } class Compiler { options : CompilerOptions ; schema : GraphQLSchema ; typesUsedSet : Set < GraphQLType > ; unresolvedFragmentSpreads : FragmentSpread [ ] = [ ] ; constructor ( schema , options ) { this . schema = schema ; this . options = options ; this . typesUsedSet = new Set ( ) ; } addTypeUsed ( type ) { if ( this . typesUsedSet . has ( type ) ) return ; if ( isEnumType ( type ) || isUnionType ( type ) || isInputObjectType ( type ) || isInterfaceType ( type ) || isObjectType ( type ) || ( isScalarType ( type ) && ! isSpecifiedScalarType ( type ) ) ) { this . typesUsedSet . add ( type ) ; } if ( isInterfaceType ( type ) || isUnionType ( type ) ) { for ( const concreteType of this . schema . getPossibleTypes ( type ) ) { this . addTypeUsed ( getNamedType ( concreteType ) ) ; } } if ( isInputObjectType ( type ) ) { for ( const field of Object . values ( type . getFields ( ) ) ) { this . addTypeUsed ( getNamedType ( field . type ) ) ; } } if ( isObjectType ( type ) ) { for ( const fieldType of type . getInterfaces ( ) ) { this . addTypeUsed ( getNamedType ( fieldType ) ) ; } for ( const field of Object . values ( type . getFields ( ) ) ) { this . addTypeUsed ( getNamedType ( field . type ) ) ; } } } get typesUsed ( ) : GraphQLType [ ] { return Array . from ( this . typesUsedSet ) ; } compileOperation ( operationDefinition ) { if ( ! operationDefinition . name ) { throw new Error ( 'Operations should be named' ) ; } const filePath = filePathForNode ( operationDefinition ) ; const operationName = operationDefinition . name . value ; const operationType = operationDefinition . operation ; const variables = ( operationDefinition . variableDefinitions || [ ] ) . map ( node => { const name = node . variable . name . value ; const type = typeFromAST ( this . schema , node . type as NonNullTypeNode ) ; this . addTypeUsed ( getNamedType ( type as GraphQLType ) ) ; return { name , type : type as GraphQLNonNull < any > } ; } ) ; const source = print ( operationDefinition ) ; const rootType = getOperationRootType ( this . schema , operationDefinition ) as GraphQLObjectType ; const selectionSet = this . compileSelectionSet ( operationDefinition . selectionSet , rootType ) ; this . addTypeUsed ( getNamedType ( ( selectionSet . selections [ 0 ] as Field ) . type ) ) ; return { filePath , operationName , operationType , variables , source , rootType , selectionSet , } ; } compileFragment ( fragmentDefinition ) { const fragmentName = fragmentDefinition . name . value ; const filePath = filePathForNode ( fragmentDefinition ) ; const source = print ( fragmentDefinition ) ; const type = typeFromAST ( this . schema , fragmentDefinition . typeCondition ) as GraphQLCompositeType ; return { fragmentName , filePath , source , type , selectionSet : this . compileSelectionSet ( fragmentDefinition . selectionSet , type ) , } ; } compileSelectionSet ( selectionSetNode , parentType , possibleTypes = this . possibleTypesForType ( parentType ) , visitedFragments = new Set ( ) ) { return { possibleTypes , selections : selectionSetNode . selections . map ( selectionNode => wrapInBooleanConditionsIfNeeded ( this . compileSelection ( selectionNode , parentType , possibleTypes , visitedFragments ) , selectionNode , possibleTypes ) ) . filter ( x => x ) as Selection [ ] , } ; } compileSelection ( selectionNode , parentType , possibleTypes , visitedFragments ) { switch ( selectionNode . kind ) { case Kind . FIELD : { const name = selectionNode . name . value ; const alias = selectionNode . alias ? selectionNode . alias . value : undefined ; const fieldDef = getFieldDef ( this . schema , parentType , selectionNode ) ; if ( ! fieldDef ) { throw new GraphQLError ( `Cannot query field "${ name }" on type "${ String ( parentType ) }"` , [ selectionNode ] ) ; } const fieldType = fieldDef . type ; const unmodifiedFieldType = getNamedType ( fieldType ) ; this . addTypeUsed ( unmodifiedFieldType ) ; const { description , isDeprecated , deprecationReason } = fieldDef ; const responseKey = alias || name ; const args = selectionNode . arguments && selectionNode . arguments . length > 0 ? selectionNode . arguments . map ( arg => { const name = arg . name . value ; const argDef = fieldDef . args . find ( argDef => argDef . name === arg . name . value ) ; return { name , value : valueFromValueNode ( arg . value ) , type : ( argDef && argDef . type ) || undefined , } ; } ) : undefined ; let field = { kind : 'Field' , responseKey , name , alias , args , type : fieldType , description : ! isMetaFieldName ( name ) && description ? description : undefined , isDeprecated , deprecationReason : deprecationReason || undefined , } ; if ( isCompositeType ( unmodifiedFieldType ) ) { const selectionSetNode = selectionNode . selectionSet ; if ( ! selectionSetNode ) { throw new GraphQLError ( `Composite field "${ name }" on type "${ String ( parentType ) }" requires selection set` , [ selectionNode ] ) ; } field . selectionSet = this . compileSelectionSet ( selectionNode . selectionSet as SelectionSetNode , unmodifiedFieldType ) ; } return field ; } case Kind . INLINE_FRAGMENT : { const typeNode = selectionNode . typeCondition ; const type = typeNode ? ( typeFromAST ( this . schema , typeNode ) as GraphQLCompositeType ) : parentType ; const possibleTypesForTypeCondition = this . possibleTypesForType ( type ) . filter ( type => possibleTypes . includes ( type ) ) ; return { kind : 'TypeCondition' , type , selectionSet : this . compileSelectionSet ( selectionNode . selectionSet , type , possibleTypesForTypeCondition ) , } ; } case Kind . FRAGMENT_SPREAD : { const fragmentName = selectionNode . name . value ; if ( visitedFragments . has ( fragmentName ) ) return null ; visitedFragments . add ( fragmentName ) ; const fragmentSpread = { kind : 'FragmentSpread' , fragmentName , selectionSet : { possibleTypes , selections : [ ] , } , } ; this . unresolvedFragmentSpreads . push ( fragmentSpread ) ; return fragmentSpread ; } } } possibleTypesForType ( type ) { if ( isAbstractType ( type ) ) { return Array . from ( this . schema . getPossibleTypes ( type ) ) || [ ] ; } else { return [ type ] ; } } } function wrapInBooleanConditionsIfNeeded ( selection , selectionNode , possibleTypes ) { if ( ! selection ) return null ; if ( ! selectionNode . directives ) return selection ; for ( const directive of selectionNode . directives ) { const directiveName = directive . name . value ; if ( directiveName === 'skip' || directiveName === 'include' ) { if ( ! directive . arguments ) continue ; const value = directive . arguments [ 0 ] . value ; switch ( value . kind ) { case 'BooleanValue' : if ( directiveName === 'skip' ) { return value . value ? null : selection ; } else { return value . value ? selection : null ; } break ; case 'Variable' : selection = { kind : 'BooleanCondition' , variableName : value . name . value , inverted : directiveName === 'skip' , selectionSet : { possibleTypes , selections : [ selection ] , } , } ; break ; } } } return selection ; }