import { devAssert } from '../jsutils/devAssert' ; import { inspect } from '../jsutils/inspect' ; import { invariant } from '../jsutils/invariant' ; import { keyMap } from '../jsutils/keyMap' ; import { mapValue } from '../jsutils/mapValue' ; import type { Maybe } from '../jsutils/Maybe' ; import type { DirectiveDefinitionNode , DocumentNode , EnumTypeDefinitionNode , EnumTypeExtensionNode , EnumValueDefinitionNode , FieldDefinitionNode , InputObjectTypeDefinitionNode , InputObjectTypeExtensionNode , InputValueDefinitionNode , InterfaceTypeDefinitionNode , InterfaceTypeExtensionNode , NamedTypeNode , ObjectTypeDefinitionNode , ObjectTypeExtensionNode , ScalarTypeDefinitionNode , ScalarTypeExtensionNode , SchemaDefinitionNode , SchemaExtensionNode , TypeDefinitionNode , TypeNode , UnionTypeDefinitionNode , UnionTypeExtensionNode , } from '../language/ast' ; import { Kind } from '../language/kinds' ; import { isTypeDefinitionNode , isTypeExtensionNode , } from '../language/predicates' ; import type { GraphQLArgumentConfig , GraphQLEnumValueConfigMap , GraphQLFieldConfig , GraphQLFieldConfigArgumentMap , GraphQLFieldConfigMap , GraphQLInputFieldConfigMap , GraphQLNamedType , GraphQLType , } from '../type/definition' ; import { GraphQLEnumType , GraphQLInputObjectType , GraphQLInterfaceType , GraphQLList , GraphQLNonNull , GraphQLObjectType , GraphQLScalarType , GraphQLUnionType , isEnumType , isInputObjectType , isInterfaceType , isListType , isNonNullType , isObjectType , isScalarType , isUnionType , } from '../type/definition' ; import { GraphQLDeprecatedDirective , GraphQLDirective , GraphQLSpecifiedByDirective , } from '../type/directives' ; import { introspectionTypes , isIntrospectionType } from '../type/introspection' ; import { isSpecifiedScalarType , specifiedScalarTypes } from '../type/scalars' ; import type { GraphQLSchemaNormalizedConfig , GraphQLSchemaValidationOptions , } from '../type/schema' ; import { assertSchema , GraphQLSchema } from '../type/schema' ; import { assertValidSDLExtension } from '../validation/validate' ; import { getDirectiveValues } from '../execution/values' ; import { valueFromAST } from './valueFromAST' ; interface Options extends GraphQLSchemaValidationOptions { /**
     * Set to true to assume the SDL is valid.
     *
     * Default: false
     */ assumeValidSDL ? : boolean ; } /**
 * Produces a new schema given an existing schema and a document which may
 * contain GraphQL type extensions and definitions. The original schema will
 * remain unaltered.
 *
 * Because a schema represents a graph of references, a schema cannot be
 * extended without effectively making an entire copy. We do not know until it's
 * too late if subgraphs remain unchanged.
 *
 * This algorithm copies the provided schema, applying extensions while
 * producing the copy. The original schema remains unaltered.
 */ export function extendSchema ( schema , documentAST , options ? ) { assertSchema ( schema ) ; devAssert ( documentAST != null && documentAST . kind === Kind . DOCUMENT , 'Must provide valid Document AST.' ) ; if ( options ?. assumeValid !== true && options ?. assumeValidSDL !== true ) { assertValidSDLExtension ( documentAST , schema ) ; } const schemaConfig = schema . toConfig ( ) ; const extendedConfig = extendSchemaImpl ( schemaConfig , documentAST , options ) ; return schemaConfig === extendedConfig ? schema : new GraphQLSchema ( extendedConfig ) ; } internal export function extendSchemaImpl ( schemaConfig , documentAST , options ? ) { const typeDefs = [ ] ; const typeExtensionsMap = Object . create ( null ) ; const directiveDefs = [ ] ; let schemaDef ; const schemaExtensions = [ ] ; for ( const def of documentAST . definitions ) { if ( def . kind === Kind . SCHEMA_DEFINITION ) { schemaDef = def ; } else if ( def . kind === Kind . SCHEMA_EXTENSION ) { schemaExtensions . push ( def ) ; } else if ( isTypeDefinitionNode ( def ) ) { typeDefs . push ( def ) ; } else if ( isTypeExtensionNode ( def ) ) { const extendedTypeName = def . name . value ; const existingTypeExtensions = typeExtensionsMap [ extendedTypeName ] ; typeExtensionsMap [ extendedTypeName ] = existingTypeExtensions ? existingTypeExtensions . concat ( [ def ] ) : [ def ] ; } else if ( def . kind === Kind . DIRECTIVE_DEFINITION ) { directiveDefs . push ( def ) ; } } if ( Object . keys ( typeExtensionsMap ) . length === 0 && typeDefs . length === 0 && directiveDefs . length === 0 && schemaExtensions . length === 0 && schemaDef == null ) { return schemaConfig ; } const typeMap = Object . create ( null ) ; for ( const existingType of schemaConfig . types ) { typeMap [ existingType . name ] = extendNamedType ( existingType ) ; } for ( const typeNode of typeDefs ) { const name = typeNode . name . value ; typeMap [ name ] = stdTypeMap [ name ] ?? buildType ( typeNode ) ; } const operationTypes = { query : schemaConfig . query && replaceNamedType ( schemaConfig . query ) , mutation : schemaConfig . mutation && replaceNamedType ( schemaConfig . mutation ) , subscription : schemaConfig . subscription && replaceNamedType ( schemaConfig . subscription ) , ... ( schemaDef && getOperationTypes ( [ schemaDef ] ) ) , ... getOperationTypes ( schemaExtensions ) , } ; return { description : schemaDef ?. description ?. value , ... operationTypes , types : Object . values ( typeMap ) , directives : [ ... schemaConfig . directives . map ( replaceDirective ) , ... directiveDefs . map ( buildDirective ) , ] , extensions : Object . create ( null ) , astNode : schemaDef ?? schemaConfig . astNode , extensionASTNodes : schemaConfig . extensionASTNodes . concat ( schemaExtensions ) , assumeValid : options ?. assumeValid ?? false , } ; function replaceType < T extends GraphQLType > ( type ) { if ( isListType ( type ) ) { return new GraphQLList ( replaceType ( type . ofType ) ) ; } if ( isNonNullType ( type ) ) { return new GraphQLNonNull ( replaceType ( type . ofType ) ) ; } return replaceNamedType ( type ) ; } function replaceNamedType < T extends GraphQLNamedType > ( type ) { return typeMap [ type . name ] ; } function replaceDirective ( directive ) { const config = directive . toConfig ( ) ; return new GraphQLDirective ( { ... config , args : mapValue ( config . args , extendArg ) , } ) ; } function extendNamedType ( type ) { if ( isIntrospectionType ( type ) || isSpecifiedScalarType ( type ) ) { return type ; } if ( isScalarType ( type ) ) { return extendScalarType ( type ) ; } if ( isObjectType ( type ) ) { return extendObjectType ( type ) ; } if ( isInterfaceType ( type ) ) { return extendInterfaceType ( type ) ; } if ( isUnionType ( type ) ) { return extendUnionType ( type ) ; } if ( isEnumType ( type ) ) { return extendEnumType ( type ) ; } if ( isInputObjectType ( type ) ) { return extendInputObjectType ( type ) ; } invariant ( false , 'Unexpected type: ' + inspect ( type ) ) ; } function extendInputObjectType ( type ) { const config = type . toConfig ( ) ; const extensions = typeExtensionsMap [ config . name ] ?? [ ] ; return new GraphQLInputObjectType ( { ... config , fields : ( ) => ( { ... mapValue ( config . fields , ( field ) => ( { ... field , type : replaceType ( field . type ) , } ) ) , ... buildInputFieldMap ( extensions ) , } ) , extensionASTNodes : config . extensionASTNodes . concat ( extensions ) , } ) ; } function extendEnumType ( type ) { const config = type . toConfig ( ) ; const extensions = typeExtensionsMap [ type . name ] ?? [ ] ; return new GraphQLEnumType ( { ... config , values : { ... config . values , ... buildEnumValueMap ( extensions ) , } , extensionASTNodes : config . extensionASTNodes . concat ( extensions ) , } ) ; } function extendScalarType ( type ) { const config = type . toConfig ( ) ; const extensions = typeExtensionsMap [ config . name ] ?? [ ] ; let specifiedByURL = config . specifiedByURL ; for ( const extensionNode of extensions ) { specifiedByURL = getSpecifiedByURL ( extensionNode ) ?? specifiedByURL ; } return new GraphQLScalarType ( { ... config , specifiedByURL , extensionASTNodes : config . extensionASTNodes . concat ( extensions ) , } ) ; } function extendObjectType ( type ) { const config = type . toConfig ( ) ; const extensions = typeExtensionsMap [ config . name ] ?? [ ] ; return new GraphQLObjectType ( { ... config , interfaces : ( ) => [ ... type . getInterfaces ( ) . map ( replaceNamedType ) , ... buildInterfaces ( extensions ) , ] , fields : ( ) => ( { ... mapValue ( config . fields , extendField ) , ... buildFieldMap ( extensions ) , } ) , extensionASTNodes : config . extensionASTNodes . concat ( extensions ) , } ) ; } function extendInterfaceType ( type ) { const config = type . toConfig ( ) ; const extensions = typeExtensionsMap [ config . name ] ?? [ ] ; return new GraphQLInterfaceType ( { ... config , interfaces : ( ) => [ ... type . getInterfaces ( ) . map ( replaceNamedType ) , ... buildInterfaces ( extensions ) , ] , fields : ( ) => ( { ... mapValue ( config . fields , extendField ) , ... buildFieldMap ( extensions ) , } ) , extensionASTNodes : config . extensionASTNodes . concat ( extensions ) , } ) ; } function extendUnionType ( type ) { const config = type . toConfig ( ) ; const extensions = typeExtensionsMap [ config . name ] ?? [ ] ; return new GraphQLUnionType ( { ... config , types : ( ) => [ ... type . getTypes ( ) . map ( replaceNamedType ) , ... buildUnionTypes ( extensions ) , ] , extensionASTNodes : config . extensionASTNodes . concat ( extensions ) , } ) ; } function extendField ( field ) { return { ... field , type : replaceType ( field . type ) , args : field . args && mapValue ( field . args , extendArg ) , } ; } function extendArg ( arg ) { return { ... arg , type : replaceType ( arg . type ) , } ; } function getOperationTypes ( nodes ) { const opTypes = { } ; for ( const node of nodes ) { const operationTypesNodes = node . operationTypes ?? [ ] ; for ( const operationType of operationTypesNodes ) { opTypes [ operationType . operation ] = getNamedType ( operationType . type ) ; } } return opTypes ; } function getNamedType ( node ) { const name = node . name . value ; const type = stdTypeMap [ name ] ?? typeMap [ name ] ; if ( type === undefined ) { throw new Error ( `Unknown type: "${ name }".` ) ; } return type ; } function getWrappedType ( node ) { if ( node . kind === Kind . LIST_TYPE ) { return new GraphQLList ( getWrappedType ( node . type ) ) ; } if ( node . kind === Kind . NON_NULL_TYPE ) { return new GraphQLNonNull ( getWrappedType ( node . type ) ) ; } return getNamedType ( node ) ; } function buildDirective ( node ) { return new GraphQLDirective ( { name : node . name . value , description : node . description ?. value , locations : node . locations . map ( ( { value } ) => value ) , isRepeatable : node . repeatable , args : buildArgumentMap ( node . arguments ) , astNode : node , } ) ; } function buildFieldMap ( nodes ) { const fieldConfigMap = Object . create ( null ) ; for ( const node of nodes ) { const nodeFields = node . fields ?? [ ] ; for ( const field of nodeFields ) { fieldConfigMap [ field . name . value ] = { type : getWrappedType ( field . type ) , description : field . description ?. value , args : buildArgumentMap ( field . arguments ) , deprecationReason : getDeprecationReason ( field ) , astNode : field , } ; } } return fieldConfigMap ; } function buildArgumentMap ( args ) { const argsNodes = args ?? [ ] ; const argConfigMap = Object . create ( null ) ; for ( const arg of argsNodes ) { const type = getWrappedType ( arg . type ) ; argConfigMap [ arg . name . value ] = { type , description : arg . description ?. value , defaultValue : valueFromAST ( arg . defaultValue , type ) , deprecationReason : getDeprecationReason ( arg ) , astNode : arg , } ; } return argConfigMap ; } function buildInputFieldMap ( nodes ) { const inputFieldMap = Object . create ( null ) ; for ( const node of nodes ) { const fieldsNodes = node . fields ?? [ ] ; for ( const field of fieldsNodes ) { const type = getWrappedType ( field . type ) ; inputFieldMap [ field . name . value ] = { type , description : field . description ?. value , defaultValue : valueFromAST ( field . defaultValue , type ) , deprecationReason : getDeprecationReason ( field ) , astNode : field , } ; } } return inputFieldMap ; } function buildEnumValueMap ( nodes ) { const enumValueMap = Object . create ( null ) ; for ( const node of nodes ) { const valuesNodes = node . values ?? [ ] ; for ( const value of valuesNodes ) { enumValueMap [ value . name . value ] = { description : value . description ?. value , deprecationReason : getDeprecationReason ( value ) , astNode : value , } ; } } return enumValueMap ; } function buildInterfaces ( nodes ) { return nodes . flatMap ( ( node ) => node . interfaces ?. map ( getNamedType ) ?? [ ] ) ; } function buildUnionTypes ( nodes ) { return nodes . flatMap ( ( node ) => node . types ?. map ( getNamedType ) ?? [ ] ) ; } function buildType ( astNode ) { const name = astNode . name . value ; const extensionASTNodes = typeExtensionsMap [ name ] ?? [ ] ; switch ( astNode . kind ) { case Kind . OBJECT_TYPE_DEFINITION : { const allNodes = [ astNode , ... extensionASTNodes ] ; return new GraphQLObjectType ( { name , description : astNode . description ?. value , interfaces : ( ) => buildInterfaces ( allNodes ) , fields : ( ) => buildFieldMap ( allNodes ) , astNode , extensionASTNodes , } ) ; } case Kind . INTERFACE_TYPE_DEFINITION : { const allNodes = [ astNode , ... extensionASTNodes ] ; return new GraphQLInterfaceType ( { name , description : astNode . description ?. value , interfaces : ( ) => buildInterfaces ( allNodes ) , fields : ( ) => buildFieldMap ( allNodes ) , astNode , extensionASTNodes , } ) ; } case Kind . ENUM_TYPE_DEFINITION : { const allNodes = [ astNode , ... extensionASTNodes ] ; return new GraphQLEnumType ( { name , description : astNode . description ?. value , values : buildEnumValueMap ( allNodes ) , astNode , extensionASTNodes , } ) ; } case Kind . UNION_TYPE_DEFINITION : { const allNodes = [ astNode , ... extensionASTNodes ] ; return new GraphQLUnionType ( { name , description : astNode . description ?. value , types : ( ) => buildUnionTypes ( allNodes ) , astNode , extensionASTNodes , } ) ; } case Kind . SCALAR_TYPE_DEFINITION : { return new GraphQLScalarType ( { name , description : astNode . description ?. value , specifiedByURL : getSpecifiedByURL ( astNode ) , astNode , extensionASTNodes , } ) ; } case Kind . INPUT_OBJECT_TYPE_DEFINITION : { const allNodes = [ astNode , ... extensionASTNodes ] ; return new GraphQLInputObjectType ( { name , description : astNode . description ?. value , fields : ( ) => buildInputFieldMap ( allNodes ) , astNode , extensionASTNodes , } ) ; } } } } const stdTypeMap = keyMap ( [ ... specifiedScalarTypes , ... introspectionTypes ] , ( type ) => type . name ) ; /**
 * Given a field or enum value node, returns the string value for the
 * deprecation reason.
 */ function getDeprecationReason ( node ) { const deprecated = getDirectiveValues ( GraphQLDeprecatedDirective , node ) ; return deprecated ?. reason ; } /**
 * Given a scalar node, returns the string value for the specifiedByURL.
 */ function getSpecifiedByURL ( node ) { const specifiedBy = getDirectiveValues ( GraphQLSpecifiedByDirective , node ) ; return specifiedBy ?. url ; }