import { DefinitionNode , DirectiveDefinitionNode , DirectiveLocation , DirectiveNode , DocumentNode , FieldDefinitionNode , GraphQLError , InputValueDefinitionNode , parse , SchemaDefinitionNode , Source , TypeNode , valueFromASTUntyped , ValueNode , NamedTypeNode , ArgumentNode , StringValueNode , ASTNode , SchemaExtensionNode , parseType , Kind , } from "graphql" ; import { Maybe } from "graphql/jsutils/Maybe" ; import { BuiltIns , Schema , graphQLBuiltIns , newNamedType , NamedTypeKind , NamedType , SchemaDefinition , SchemaElement , ObjectType , InterfaceType , FieldDefinition , Type , ListType , OutputType , isOutputType , isInputType , InputType , NonNullType , ArgumentDefinition , InputFieldDefinition , DirectiveDefinition , UnionType , InputObjectType , EnumType , Extension } from "./definitions" ; function buildValue ( value ? ) { return value ? valueFromASTUntyped ( value ) : undefined ; } export function buildSchema ( source , builtIns = graphQLBuiltIns , validate = true ) { return buildSchemaFromAST ( parse ( source ) , builtIns , validate ) ; } export function buildSchemaFromAST ( documentNode , builtIns = graphQLBuiltIns , validate = true ) { const schema = new Schema ( builtIns ) ; const directiveDefinitionNodes = buildNamedTypeAndDirectivesShallow ( documentNode , schema ) ; for ( const directiveDefinitionNode of directiveDefinitionNodes ) { buildDirectiveDefinitionInner ( directiveDefinitionNode , schema . directive ( directiveDefinitionNode . name . value ) ! ) ; } for ( const definitionNode of documentNode . definitions ) { switch ( definitionNode . kind ) { case 'OperationDefinition' : case 'FragmentDefinition' : throw new GraphQLError ( "Invalid executable definition found while building schema" , definitionNode ) ; case 'SchemaDefinition' : buildSchemaDefinitionInner ( definitionNode , schema . schemaDefinition ) ; break ; case 'SchemaExtension' : buildSchemaDefinitionInner ( definitionNode , schema . schemaDefinition , schema . schemaDefinition . newExtension ( ) ) ; break ; case 'ScalarTypeDefinition' : case 'ObjectTypeDefinition' : case 'InterfaceTypeDefinition' : case 'UnionTypeDefinition' : case 'EnumTypeDefinition' : case 'InputObjectTypeDefinition' : buildNamedTypeInner ( definitionNode , schema . type ( definitionNode . name . value ) ! ) ; break ; case 'ScalarTypeExtension' : case 'ObjectTypeExtension' : case 'InterfaceTypeExtension' : case 'UnionTypeExtension' : case 'EnumTypeExtension' : case 'InputObjectTypeExtension' : const toExtend = schema . type ( definitionNode . name . value ) ! ; const extension = toExtend . newExtension ( ) ; extension . sourceAST = definitionNode ; buildNamedTypeInner ( definitionNode , toExtend , extension ) ; break ; } } Schema . prototype [ 'forceSetCachedDocument' ] . call ( schema , documentNode ) ; if ( validate ) { schema . validate ( ) ; } return schema ; } function buildNamedTypeAndDirectivesShallow ( documentNode , schema ) { const directiveDefinitionNodes = [ ] ; for ( const definitionNode of documentNode . definitions ) { switch ( definitionNode . kind ) { case 'ScalarTypeDefinition' : case 'ObjectTypeDefinition' : case 'InterfaceTypeDefinition' : case 'UnionTypeDefinition' : case 'EnumTypeDefinition' : case 'InputObjectTypeDefinition' : case 'ScalarTypeExtension' : case 'ObjectTypeExtension' : case 'InterfaceTypeExtension' : case 'UnionTypeExtension' : case 'EnumTypeExtension' : case 'InputObjectTypeExtension' : const existing = schema . type ( definitionNode . name . value ) ; if ( ! existing || existing . isBuiltIn ) { schema . addType ( newNamedType ( withoutTrailingDefinition ( definitionNode . kind ) , definitionNode . name . value ) ) ; } break ; case 'DirectiveDefinition' : directiveDefinitionNodes . push ( definitionNode ) ; schema . addDirectiveDefinition ( definitionNode . name . value ) ; break ; } } return directiveDefinitionNodes ; } type NodeWithDirectives = { directives ? : ReadonlyArray < DirectiveNode > ; } ; type NodeWithDescription = { description ? : Maybe < StringValueNode > ; } ; type NodeWithArguments = { arguments ? : ReadonlyArray < ArgumentNode > ; } ; function withoutTrailingDefinition ( str ) { const endString = str . endsWith ( 'Definition' ) ? 'Definition' : 'Extension' ; return str . slice ( 0 , str . length - endString . length ) as NamedTypeKind ; } function getReferencedType ( node , schema ) { const type = schema . type ( node . name . value ) ; if ( ! type ) { throw new GraphQLError ( `Unknown type ${ node . name . value }` , node ) ; } return type ; } function withNodeAttachedToError ( operation , node ) { try { operation ( ) ; } catch ( e ) { if ( e instanceof GraphQLError ) { const allNodes = e . nodes ? [ node , ... e . nodes ] : node ; throw new GraphQLError ( e . message , allNodes , e . source , e . positions , e . path , e , e . extensions ) ; } else { throw e ; } } } function buildSchemaDefinitionInner ( schemaNode , schemaDefinition , extension ? ) { for ( const opTypeNode of schemaNode . operationTypes ?? [ ] ) { withNodeAttachedToError ( ( ) => schemaDefinition . setRoot ( opTypeNode . operation , opTypeNode . type . name . value ) . setOfExtension ( extension ) , opTypeNode ) ; } schemaDefinition . sourceAST = schemaNode ; schemaDefinition . description = 'description' in schemaNode ? schemaNode . description ?. value : undefined ; buildAppliedDirectives ( schemaNode , schemaDefinition , extension ) ; } function buildAppliedDirectives ( elementNode , element , extension ? ) { for ( const directive of elementNode . directives ?? [ ] ) { withNodeAttachedToError ( ( ) => { const d = element . applyDirective ( directive . name . value , buildArgs ( directive ) ) ; d . setOfExtension ( extension ) ; d . sourceAST = directive ; } , directive ) ; } } function buildArgs ( argumentsNode ) { const args = Object . create ( null ) ; for ( const argNode of argumentsNode . arguments ?? [ ] ) { args [ argNode . name . value ] = buildValue ( argNode . value ) ; } return args ; } function buildNamedTypeInner ( definitionNode , type , extension ? ) { switch ( definitionNode . kind ) { case 'ObjectTypeDefinition' : case 'ObjectTypeExtension' : case 'InterfaceTypeDefinition' : case 'InterfaceTypeExtension' : const fieldBasedType = type as ObjectType | InterfaceType ; for ( const fieldNode of definitionNode . fields ?? [ ] ) { const field = fieldBasedType . addField ( fieldNode . name . value ) ; field . setOfExtension ( extension ) ; buildFieldDefinitionInner ( fieldNode , field ) ; } for ( const itfNode of definitionNode . interfaces ?? [ ] ) { withNodeAttachedToError ( ( ) => { const itfName = itfNode . name . value ; if ( fieldBasedType . implementsInterface ( itfName ) ) { throw new GraphQLError ( `Type ${ type } can only implement ${ itfName } once.` ) ; } fieldBasedType . addImplementedInterface ( itfName ) . setOfExtension ( extension ) ; } , itfNode ) ; } break ; case 'UnionTypeDefinition' : case 'UnionTypeExtension' : const unionType = type as UnionType ; for ( const namedType of definitionNode . types ?? [ ] ) { withNodeAttachedToError ( ( ) => { const name = namedType . name . value ; if ( unionType . hasTypeMember ( name ) ) { throw new GraphQLError ( `Union type ${ unionType } can only include type ${ name } once.` ) ; } unionType . addType ( name ) . setOfExtension ( extension ) ; } , namedType ) ; } break ; case 'EnumTypeDefinition' : case 'EnumTypeExtension' : const enumType = type as EnumType ; for ( const enumVal of definitionNode . values ?? [ ] ) { const v = enumType . addValue ( enumVal . name . value ) ; v . description = enumVal . description ?. value ; v . setOfExtension ( extension ) ; buildAppliedDirectives ( enumVal , v ) ; } break ; case 'InputObjectTypeDefinition' : case 'InputObjectTypeExtension' : const inputObjectType = type as InputObjectType ; for ( const fieldNode of definitionNode . fields ?? [ ] ) { const field = inputObjectType . addField ( fieldNode . name . value ) ; field . setOfExtension ( extension ) ; buildInputFieldDefinitionInner ( fieldNode , field ) ; } break ; } buildAppliedDirectives ( definitionNode , type , extension ) ; type . description = definitionNode . description ?. value ; type . sourceAST = definitionNode ; } function buildFieldDefinitionInner ( fieldNode , field ) { const type = buildTypeReferenceFromAST ( fieldNode . type , field . schema ( ) ) ; field . type = ensureOutputType ( type , field . coordinate , fieldNode ) ; for ( const inputValueDef of fieldNode . arguments ?? [ ] ) { buildArgumentDefinitionInner ( inputValueDef , field . addArgument ( inputValueDef . name . value ) ) ; } buildAppliedDirectives ( fieldNode , field ) ; field . description = fieldNode . description ?. value ; field . sourceAST = fieldNode ; } function ensureOutputType ( type , what , node ) { if ( isOutputType ( type ) ) { return type ; } else { throw new GraphQLError ( `The type of ${ what } must be Output Type but got: ${ type }, a ${ type . kind }.` , node ) ; } } function ensureInputType ( type , what , node ) { if ( isInputType ( type ) ) { return type ; } else { throw new GraphQLError ( `The type of ${ what } must be Input Type but got: ${ type }, a ${ type . kind }.` , node ) ; } } export function builtTypeReference ( encodedType , schema ) { return buildTypeReferenceFromAST ( parseType ( encodedType ) , schema ) ; } function buildTypeReferenceFromAST ( typeNode , schema ) { switch ( typeNode . kind ) { case Kind . LIST_TYPE : return new ListType ( buildTypeReferenceFromAST ( typeNode . type , schema ) ) ; case Kind . NON_NULL_TYPE : const wrapped = buildTypeReferenceFromAST ( typeNode . type , schema ) ; if ( wrapped . kind == Kind . NON_NULL_TYPE ) { throw new GraphQLError ( `Cannot apply the non-null operator (!) twice to the same type` , typeNode ) ; } return new NonNullType ( wrapped ) ; default : return getReferencedType ( typeNode , schema ) ; } } function buildArgumentDefinitionInner ( inputNode , arg ) { const type = buildTypeReferenceFromAST ( inputNode . type , arg . schema ( ) ) ; arg . type = ensureInputType ( type , arg . coordinate , inputNode ) ; arg . defaultValue = buildValue ( inputNode . defaultValue ) ; buildAppliedDirectives ( inputNode , arg ) ; arg . description = inputNode . description ?. value ; arg . sourceAST = inputNode ; } function buildInputFieldDefinitionInner ( fieldNode , field ) { const type = buildTypeReferenceFromAST ( fieldNode . type , field . schema ( ) ) ; field . type = ensureInputType ( type , field . coordinate , fieldNode ) ; field . defaultValue = buildValue ( fieldNode . defaultValue ) ; buildAppliedDirectives ( fieldNode , field ) ; field . description = fieldNode . description ?. value ; field . sourceAST = fieldNode ; } function buildDirectiveDefinitionInner ( directiveNode , directive ) { for ( const inputValueDef of directiveNode . arguments ?? [ ] ) { buildArgumentDefinitionInner ( inputValueDef , directive . addArgument ( inputValueDef . name . value ) ) ; } directive . repeatable = directiveNode . repeatable ; const locations = directiveNode . locations . map ( ( { value } ) => value as DirectiveLocation ) ; directive . addLocations ( ... locations ) ; directive . description = directiveNode . description ?. value ; directive . sourceAST = directiveNode ; }