import { ConstArgumentNode , ASTNode , buildASTSchema as buildGraphqlSchemaFromAST , DirectiveLocation , ConstDirectiveNode , ConstValueNode , DocumentNode , GraphQLError , GraphQLSchema , Kind , ListTypeNode , NamedTypeNode , parse , printError , TypeNode , VariableDefinitionNode , VariableNode } from "graphql" ; import { CoreDirectiveArgs , CoreSpecDefinition , CORE_VERSIONS , FeatureUrl , isCoreSpecDirectiveApplication , removeFeatureElements } from "./coreSpec" ; import { arrayEquals , assert , mapValues , MapWithCachedArrays , setValues } from "./utils" ; import { withDefaultValues , valueEquals , valueToString , valueToAST , variablesInValue , valueFromAST , valueNodeToConstValueNode } from "./values" ; import { removeInaccessibleElements } from "./inaccessibleSpec" ; import { printSchema , Options , defaultPrintOptions } from './print' ; import { sameType } from './types' ; import { addIntrospectionFields , introspectionFieldNames , isIntrospectionName } from "./introspection" ; import { err } from '@apollo/core-schema' ; import { GraphQLErrorExt } from "@apollo/core-schema/dist/error" ; import { validateSDL } from "graphql/validation/validate" ; import { SDLValidationRule } from "graphql/validation/ValidationContext" ; import { specifiedSDLRules } from "graphql/validation/specifiedRules" ; import { validateSchema } from "./validate" ; const validationErrorCode = 'GraphQLValidationFailed' ; export const ErrGraphQLValidationFailed = ( causes ) => err ( validationErrorCode , { message : 'The schema is not a valid GraphQL schema' , causes } ) ; /**
 * Given an error that may have been thrown during schema validation, extract the causes of validation failure.
 * If the error is not a graphQL error, undefined is returned.
 */ export function errorCauses ( e ) { if ( e instanceof GraphQLErrorExt ) { if ( e . code === validationErrorCode ) { return ( ( e as any ) . causes ) as GraphQLError [ ] ; } return [ e ] ; } if ( e instanceof GraphQLError ) { return [ e ] ; } return undefined ; } export function printGraphQLErrorsOrRethrow ( e ) { const causes = errorCauses ( e ) ; if ( ! causes ) { throw e ; } return causes . map ( e => printError ( e ) ) . join ( '\n\n' ) ; } export function printErrors ( errors ) { return errors . map ( e => printError ( e ) ) . join ( '\n\n' ) ; } export const typenameFieldName = '__typename' ; export type QueryRootKind = 'query' ; export type MutationRootKind = 'mutation' ; export type SubscriptionRootKind = 'subscription' ; export type SchemaRootKind = QueryRootKind | MutationRootKind | SubscriptionRootKind ; export const allSchemaRootKinds = [ 'query' , 'mutation' , 'subscription' ] ; export function defaultRootName ( rootKind ) { return rootKind . charAt ( 0 ) . toUpperCase ( ) + rootKind . slice ( 1 ) ; } function checkDefaultSchemaRoot ( type ) { if ( type . kind !== 'ObjectType' ) { return undefined ; } switch ( type . name ) { case 'Query' : return 'query' ; case 'Mutation' : return 'mutation' ; case 'Subscription' : return 'subscription' ; default : return undefined ; } } export type Type = NamedType | WrapperType ; export type NamedType = ScalarType | ObjectType | InterfaceType | UnionType | EnumType | InputObjectType ; export type OutputType = ScalarType | ObjectType | InterfaceType | UnionType | EnumType | ListType < any > | NonNullType < any > ; export type InputType = ScalarType | EnumType | InputObjectType | ListType < any > | NonNullType < any > ; export type WrapperType = ListType < any > | NonNullType < any > ; export type AbstractType = InterfaceType | UnionType ; export type CompositeType = ObjectType | InterfaceType | UnionType ; export type OutputTypeReferencer = FieldDefinition < any > ; export type InputTypeReferencer = InputFieldDefinition | ArgumentDefinition < any > ; export type ObjectTypeReferencer = OutputTypeReferencer | UnionType | SchemaDefinition ; export type InterfaceTypeReferencer = OutputTypeReferencer | ObjectType | InterfaceType ; export type NullableType = NamedType | ListType < any > ; export type NamedTypeKind = NamedType [ 'kind' ] ; export function isNamedType ( type ) { return type instanceof BaseNamedType ; } export function isWrapperType ( type ) { return isListType ( type ) || isNonNullType ( type ) ; } export function isListType ( type ) { return type . kind == 'ListType' ; } export function isNonNullType ( type ) { return type . kind == 'NonNullType' ; } export function isScalarType ( type ) { return type . kind == 'ScalarType' ; } export function isCustomScalarType ( type ) { return isScalarType ( type ) && ! graphQLBuiltIns . defaultGraphQLBuiltInTypes . includes ( type . name ) ; } export function isIntType ( type ) { return type === type . schema ( ) . intType ( ) ; } export function isStringType ( type ) { return type === type . schema ( ) . stringType ( ) ; } export function isFloatType ( type ) { return type === type . schema ( ) . floatType ( ) ; } export function isBooleanType ( type ) { return type === type . schema ( ) . booleanType ( ) ; } export function isIDType ( type ) { return type === type . schema ( ) . idType ( ) ; } export function isObjectType ( type ) { return type . kind == 'ObjectType' ; } export function isInterfaceType ( type ) { return type . kind == 'InterfaceType' ; } export function isEnumType ( type ) { return type . kind == 'EnumType' ; } export function isUnionType ( type ) { return type . kind == 'UnionType' ; } export function isInputObjectType ( type ) { return type . kind == 'InputObjectType' ; } export function isOutputType ( type ) { switch ( baseType ( type ) . kind ) { case 'ScalarType' : case 'ObjectType' : case 'UnionType' : case 'EnumType' : case 'InterfaceType' : return true ; default : return false ; } } export function isInputType ( type ) { switch ( baseType ( type ) . kind ) { case 'ScalarType' : case 'EnumType' : case 'InputObjectType' : return true ; default : return false ; } } export function baseType ( type ) { return isWrapperType ( type ) ? type . baseType ( ) : type ; } export function isNullableType ( type ) { return ! isNonNullType ( type ) ; } export function isAbstractType ( type ) { return isInterfaceType ( type ) || isUnionType ( type ) ; } export function isCompositeType ( type ) { return isObjectType ( type ) || isInterfaceType ( type ) || isUnionType ( type ) ; } export function possibleRuntimeTypes ( type ) { switch ( type . kind ) { case 'InterfaceType' : return type . possibleRuntimeTypes ( ) ; case 'UnionType' : return type . types ( ) ; case 'ObjectType' : return [ type ] ; } } export function runtimeTypesIntersects ( t1 , t2 ) { const rt1 = possibleRuntimeTypes ( t1 ) ; const rt2 = possibleRuntimeTypes ( t2 ) ; for ( const obj1 of rt1 ) { if ( rt2 . some ( obj2 => obj1 . name === obj2 . name ) ) { return true ; } } return false ; } export const executableDirectiveLocations = [ DirectiveLocation . QUERY , DirectiveLocation . MUTATION , DirectiveLocation . SUBSCRIPTION , DirectiveLocation . FIELD , DirectiveLocation . FRAGMENT_DEFINITION , DirectiveLocation . FRAGMENT_SPREAD , DirectiveLocation . INLINE_FRAGMENT , DirectiveLocation . VARIABLE_DEFINITION , ] ; /**
 * Converts a type to an AST of a "reference" to that type, one corresponding to the type `toString()` (and thus never a type definition).
 *
 * To print a type definition, see the `printTypeDefinitionAndExtensions` method.
 */ export function typeToAST ( type ) { switch ( type . kind ) { case 'ListType' : return { kind : Kind . LIST_TYPE , type : typeToAST ( type . ofType ) } ; case 'NonNullType' : return { kind : Kind . NON_NULL_TYPE , type : typeToAST ( type . ofType ) as NamedTypeNode | ListTypeNode } ; default : return { kind : Kind . NAMED_TYPE , name : { kind : Kind . NAME , value : type . name } } ; } } export function typeFromAST ( schema , node ) { switch ( node . kind ) { case Kind . LIST_TYPE : return new ListType ( typeFromAST ( schema , node . type ) ) ; case Kind . NON_NULL_TYPE : return new NonNullType ( typeFromAST ( schema , node . type ) as NullableType ) ; default : const type = schema . type ( node . name . value ) ; if ( ! type ) { throw new GraphQLError ( `Unknown type "${ node . name . value }"` , node ) ; } return type ; } } export type LeafType = ScalarType | EnumType ; export function isLeafType ( type ) { return isScalarType ( type ) || isEnumType ( type ) ; } export interface Named { readonly name : string ; } export type ExtendableElement = SchemaDefinition | NamedType ; export class DirectiveTargetElement < T extends DirectiveTargetElement < T > > { public readonly appliedDirectives : Directive < T > [ ] = [ ] ; constructor ( private readonly _schema ) { } schema ( ) { return this . _schema ; } appliedDirectivesOf ( name ) ; appliedDirectivesOf < TApplicationArgs extends { [ key ] : any ; } = { [ key ] : any ; } > ( definition ) ; appliedDirectivesOf ( nameOrDefinition ) { const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition . name ; return this . appliedDirectives . filter ( d => d . name == directiveName ) ; } hasAppliedDirective ( nameOrDefinition ) { const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition . name ; return this . appliedDirectives . some ( d => d . name == directiveName ) ; } applyDirective < TApplicationArgs extends { [ key ] : any ; } = { [ key ] : any ; } > ( defOrDirective , args ? ) { let toAdd ; if ( defOrDirective instanceof Directive ) { if ( defOrDirective . schema ( ) != this . schema ( ) ) { throw new Error ( `Cannot add directive ${ defOrDirective } to ${ this } as it is attached to another schema` ) ; } toAdd = defOrDirective ; if ( args ) { toAdd . setArguments ( args ) ; } } else { toAdd = new Directive < T , TApplicationArgs > ( defOrDirective . name , args ?? Object . create ( null ) ) ; } Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; this . appliedDirectives . push ( toAdd ) ; return toAdd ; } appliedDirectivesToDirectiveNodes ( ) { if ( this . appliedDirectives . length == 0 ) { return undefined ; } return this . appliedDirectives . map ( directive => { return { kind : Kind . DIRECTIVE , name : { kind : Kind . NAME , value : directive . name , } , arguments : directive . argumentsToAST ( ) } ; } ) ; } appliedDirectivesToString ( ) { return this . appliedDirectives . length == 0 ? '' : ' ' + this . appliedDirectives . join ( ' ' ) ; } variablesInAppliedDirectives ( ) { return this . appliedDirectives . reduce ( ( acc , d ) => mergeVariables ( acc , variablesInArguments ( d . arguments ( ) ) ) , [ ] ) ; } } export function sourceASTs ( ... elts ) { return elts . map ( elt => elt ?. sourceAST ) . filter ( elt => elt !== undefined ) as ASTNode [ ] ; } abstract class Element < TParent extends SchemaElement < any , any > | Schema | DirectiveTargetElement < any > > { protected _parent ? : TParent ; sourceAST ? : ASTNode ; schema ( ) { const schema = this . schemaInternal ( ) ; assert ( schema , 'requested schema does not exist. Probably because the element is unattached' ) ; return schema ; } protected schemaInternal ( ) { if ( ! this . _parent ) { return undefined ; } else if ( this . _parent instanceof Schema ) { return this . _parent as any ; } else if ( this . _parent instanceof SchemaElement ) { return this . _parent . schemaInternal ( ) ; } else if ( this . _parent instanceof DirectiveTargetElement ) { return this . _parent . schema ( ) ; } assert ( false , 'unreachable code. parent is of unknown type' ) ; } get parent ( ) : TParent { assert ( this . _parent , 'trying to access non-existent parent' ) ; return this . _parent ; } isAttached ( ) { return ! ! this . _parent ; } private setParent ( parent ) { assert ( ! this . _parent , "Cannot set parent of an already attached element" ) ; this . _parent = parent ; this . onAttached ( ) ; } protected onAttached ( ) { } protected checkUpdate ( ) { if ( ! this . isAttached ( ) ) { throw error ( `Cannot modify detached element ${ this }` ) ; } } } export class Extension < TElement extends ExtendableElement > { protected _extendedElement ? : TElement ; sourceAST ? : ASTNode ; get extendedElement ( ) : TElement | undefined { return this . _extendedElement ; } private setExtendedElement ( element ) { assert ( ! this . _extendedElement , "Cannot attached already attached extension" ) ; this . _extendedElement = element ; } } export abstract class SchemaElement < TOwnType extends SchemaElement < any , TParent > , TParent extends SchemaElement < any , any > | Schema > extends Element < TParent > { protected readonly _appliedDirectives : Directive < TOwnType > [ ] = [ ] ; description ? : string ; get appliedDirectives ( ) : readonly Directive < TOwnType > [ ] { return this . _appliedDirectives ; } appliedDirectivesOf < TApplicationArgs extends { [ key ] : any ; } = { [ key ] : any ; } > ( nameOrDefinition ) { const directiveName = typeof nameOrDefinition === 'string' ? nameOrDefinition : nameOrDefinition . name ; return this . _appliedDirectives . filter ( d => d . name == directiveName ) as Directive < TOwnType , TApplicationArgs > [ ] ; } hasAppliedDirective ( nameOrDefinition ) { return ( typeof nameOrDefinition === 'string' ? this . appliedDirectivesOf ( nameOrDefinition ) : this . appliedDirectivesOf ( nameOrDefinition ) ) . length !== 0 ; } applyDirective < TApplicationArgs extends { [ key ] : any ; } = { [ key ] : any ; } > ( nameOrDefOrDirective , args ? ) { let toAdd ; if ( nameOrDefOrDirective instanceof Directive ) { this . checkUpdate ( nameOrDefOrDirective ) ; toAdd = nameOrDefOrDirective ; if ( args ) { toAdd . setArguments ( args ) ; } } else { let name ; if ( typeof nameOrDefOrDirective === 'string' ) { this . checkUpdate ( ) ; const def = this . schema ( ) . directive ( nameOrDefOrDirective ) ; if ( ! def ) { throw new GraphQLError ( `Cannot apply unknown directive "@${ nameOrDefOrDirective }"` ) ; } name = nameOrDefOrDirective ; } else { this . checkUpdate ( nameOrDefOrDirective ) ; name = nameOrDefOrDirective . name ; } toAdd = new Directive < TOwnType , TApplicationArgs > ( name , args ?? Object . create ( null ) ) ; Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; } this . _appliedDirectives . push ( toAdd ) ; DirectiveDefinition . prototype [ 'addReferencer' ] . call ( toAdd . definition ! , toAdd ) ; this . onModification ( ) ; return toAdd ; } protected removeAppliedDirectives ( ) { const applied = this . _appliedDirectives . concat ( ) ; applied . forEach ( d => d . remove ( ) ) ; } protected onModification ( ) { const schema = this . schemaInternal ( ) ; if ( schema ) { Schema . prototype [ 'onModification' ] . call ( schema ) ; } } protected isElementBuiltIn ( ) { return false ; } protected removeTypeReferenceInternal ( type ) { this . removeTypeReference ( type as any ) ; } protected abstract removeTypeReference ( type ) ; protected checkRemoval ( ) { if ( this . isElementBuiltIn ( ) && ! Schema . prototype [ 'canModifyBuiltIn' ] . call ( this . schema ( ) ) ) { throw error ( `Cannot modify built-in ${ this }` ) ; } } protected checkUpdate ( addedElement ? ) { super . checkUpdate ( ) ; if ( ! Schema . prototype [ 'canModifyBuiltIn' ] . call ( this . schema ( ) ) ) { let thisElement = this ; while ( thisElement && thisElement instanceof SchemaElement ) { if ( thisElement . isElementBuiltIn ( ) ) { throw error ( `Cannot modify built-in (or part of built-in) ${ this }` ) ; } thisElement = thisElement . parent ; } } if ( addedElement && addedElement . isAttached ( ) ) { const thatSchema = addedElement . schema ( ) ; if ( thatSchema && thatSchema != this . schema ( ) ) { throw error ( `Cannot add element ${ addedElement } to ${ this } as it is attached to another schema` ) ; } } } } export abstract class NamedSchemaElement < TOwnType extends NamedSchemaElement < TOwnType , TParent , TReferencer > , TParent extends NamedSchemaElement < any , any , any > | Schema , TReferencer > extends SchemaElement < TOwnType , TParent > implements Named { protected _name : string ; constructor ( name ) { super ( ) ; this . _name = name ; } get name ( ) : string { return this . _name ; } abstract coordinate : string ; abstract remove ( ) ; } abstract class BaseNamedType < TReferencer , TOwnType extends NamedType & NamedSchemaElement < TOwnType , Schema , TReferencer > > extends NamedSchemaElement < TOwnType , Schema , TReferencer > { protected readonly _referencers : Set < TReferencer > = new Set ( ) ; protected readonly _extensions : Set < Extension < TOwnType > > = new Set ( ) ; constructor ( name , readonly isBuiltIn = false ) { super ( name ) ; } private addReferencer ( referencer ) { this . _referencers . add ( referencer ) ; } private removeReferencer ( referencer ) { this . _referencers . delete ( referencer ) ; } get coordinate ( ) : string { return this . name ; } * allChildElements ( ) { } extensions ( ) { return this . _extensions ; } newExtension ( ) { return this . addExtension ( new Extension < TOwnType > ( ) ) ; } addExtension ( extension ) { this . checkUpdate ( ) ; if ( this . _extensions . has ( extension ) ) { return extension ; } if ( extension . extendedElement ) { throw error ( `Cannot add extension to type ${ this }: it is already added to another type` ) ; } this . _extensions . add ( extension ) ; Extension . prototype [ 'setExtendedElement' ] . call ( extension , this ) ; this . onModification ( ) ; return extension ; } isIntrospectionType ( ) { return isIntrospectionName ( this . name ) ; } hasExtensionElements ( ) { return this . _extensions . size > 0 ; } hasNonExtensionElements ( ) { return this . _appliedDirectives . some ( d => d . ofExtension ( ) === undefined ) || this . hasNonExtensionInnerElements ( ) ; } protected abstract hasNonExtensionInnerElements ( ) ; protected isElementBuiltIn ( ) { return this . isBuiltIn ; } rename ( newName ) { this . checkUpdate ( ) ; const oldName = this . _name ; this . _name = newName ; Schema . prototype [ 'renameTypeInternal' ] . call ( this . _parent , oldName , newName ) ; this . onModification ( ) ; } returns remove ( ) { if ( ! this . _parent ) { return [ ] ; } this . checkRemoval ( ) ; this . onModification ( ) ; this . removeInnerElements ( ) ; Schema . prototype [ 'removeTypeInternal' ] . call ( this . _parent , this ) ; this . removeAppliedDirectives ( ) ; this . sourceAST = undefined ; const toReturn = setValues ( this . _referencers ) . map ( r => { SchemaElement . prototype [ 'removeTypeReferenceInternal' ] . call ( r , this ) ; return r ; } ) ; this . _referencers . clear ( ) ; this . _parent = undefined ; return toReturn ; } /**
     * Removes this this definition _and_, recursively, any other elements that references this type and would be invalid
     * after the removal.
     *
     * Note that contrarily to `remove()` (which this method essentially call recursively), this method leaves the schema
     * valid (assuming it was valid beforehand) _unless_ all the schema ends up being removed through recursion (in which
     * case this leaves an empty schema, and that is not technically valid).
     *
     * Also note that this method does _not_ necessarily remove all the elements that reference this type: for instance,
     * if this type is an interface, objects implementing it will _not_ be removed, they will simply stop implementing
     * the interface. In practice, this method mainly remove fields that were using the removed type (in either argument or
     * return type), but it can also remove object/input object/interface if through such field removal some type ends up
     * empty, and it can remove unions if through that removal process and union becomes empty.
     */ removeRecursive ( ) { this . remove ( ) . forEach ( ref => this . removeReferenceRecursive ( ref ) ) ; } protected abstract removeReferenceRecursive ( ref ) ; referencers ( ) { return setValues ( this . _referencers ) ; } isReferenced ( ) { return this . _referencers . size > 0 ; } protected abstract removeInnerElements ( ) ; toString ( ) { return this . name ; } } export abstract class NamedSchemaElementWithType < TType extends Type , TOwnType extends NamedSchemaElementWithType < TType , TOwnType , P , Referencer > , P extends NamedSchemaElement < any , any , any > | Schema , Referencer > extends NamedSchemaElement < TOwnType , P , Referencer > { private _type ? : TType ; get type ( ) : TType | undefined { return this . _type ; } set type ( type ) { if ( type ) { this . checkUpdate ( type ) ; } else { this . checkRemoval ( ) ; } if ( this . _type ) { removeReferenceToType ( this , this . _type ) ; } this . _type = type ; if ( type ) { addReferenceToType ( this , type ) ; } } protected removeTypeReference ( type ) { assert ( this . _type && baseType ( this . _type ) === type , ( ) => `Cannot remove reference to type ${ type } on ${ this } as its type is ${ this . _type }` ) ; this . _type = undefined ; } } function error ( message ) { return new GraphQLError ( message ) ; } abstract class BaseExtensionMember < TExtended extends ExtendableElement > extends Element < TExtended > { private _extension ? : Extension < TExtended > ; ofExtension ( ) { return this . _extension ; } setOfExtension ( extension ) { this . checkUpdate ( ) ; if ( extension && ! this . _parent ?. extensions ( ) . has ( extension as any ) ) { throw error ( `Cannot set object as part of the provided extension: it is not an extension of parent ${ this . parent }` ) ; } this . _extension = extension ; } remove ( ) { this . removeInner ( ) ; Schema . prototype [ 'onModification' ] . call ( this . schema ( ) ) ; this . _extension = undefined ; this . _parent = undefined ; } protected abstract removeInner ( ) ; } function sortedMemberNames ( u ) { return u . members ( ) . map ( m => m . type . name ) . sort ( ( n1 , n2 ) => n1 . localeCompare ( n2 ) ) ; } export class BuiltIns { readonly defaultGraphQLBuiltInTypes : readonly string [ ] = [ 'Int' , 'Float' , 'String' , 'Boolean' , 'ID' ] ; private readonly defaultGraphQLBuiltInDirectives : readonly string [ ] = [ 'include' , 'skip' , 'deprecated' , 'specifiedBy' ] ; addBuiltInTypes ( schema ) { this . defaultGraphQLBuiltInTypes . forEach ( t => this . addBuiltInScalar ( schema , t ) ) ; } addBuiltInDirectives ( schema ) { for ( const name of [ 'include' , 'skip' ] ) { this . addBuiltInDirective ( schema , name ) . addLocations ( DirectiveLocation . FIELD , DirectiveLocation . FRAGMENT_SPREAD , DirectiveLocation . INLINE_FRAGMENT ) . addArgument ( 'if' , new NonNullType ( schema . booleanType ( ) ) ) ; } this . addBuiltInDirective ( schema , 'deprecated' ) . addLocations ( DirectiveLocation . FIELD_DEFINITION , DirectiveLocation . ENUM_VALUE , DirectiveLocation . ARGUMENT_DEFINITION , DirectiveLocation . INPUT_FIELD_DEFINITION ) . addArgument ( 'reason' , schema . stringType ( ) , 'No longer supported' ) ; this . addBuiltInDirective ( schema , 'specifiedBy' ) . addLocations ( DirectiveLocation . SCALAR ) . addArgument ( 'url' , new NonNullType ( schema . stringType ( ) ) ) ; } isGraphQLBuiltIn ( element ) { if ( isIntrospectionName ( element . name ) ) { return true ; } if ( element instanceof FieldDefinition ) { return false ; } else if ( element instanceof DirectiveDefinition ) { return this . defaultGraphQLBuiltInDirectives . includes ( element . name ) ; } else { return this . defaultGraphQLBuiltInTypes . includes ( element . name ) ; } } prepareValidation ( _ ) { } onValidation ( schema , unvalidatedDirectives ? ) { const errors = [ ] ; for ( const type of schema . builtInTypes ( undefined , true ) ) { const maybeRedefined = schema . type ( type . name ) ! ; if ( ! maybeRedefined . isBuiltIn ) { this . ensureSameTypeStructure ( type , maybeRedefined , errors ) ; } } for ( const directive of schema . builtInDirectives ( true ) ) { if ( unvalidatedDirectives && unvalidatedDirectives . includes ( directive . name ) ) { continue ; } const maybeRedefined = schema . directive ( directive . name ) ! ; if ( ! maybeRedefined . isBuiltIn ) { this . ensureSameDirectiveStructure ( directive , maybeRedefined , errors ) ; } } return errors ; } validationRules ( ) { return specifiedSDLRules ; } maybeUpdateSubgraphDocument ( _ , document ) { return document ; } private ensureSameDirectiveStructure ( builtIn , manuallyDefined , errors ) { this . ensureSameArguments ( builtIn , manuallyDefined , `directive ${ builtIn }` , errors ) ; if ( ! builtIn . repeatable && manuallyDefined . repeatable ) { errors . push ( error ( `Invalid redefinition of built-in directive ${ builtIn }: ${ builtIn } should${ builtIn . repeatable ? "" : " not" } be repeatable` ) ) ; } if ( ! manuallyDefined . locations . every ( loc => builtIn . locations . includes ( loc ) ) ) { errors . push ( error ( `Invalid redefinition of built-in directive ${ builtIn }: ${ builtIn } should have locations ${ builtIn . locations . join ( ', ' ) }, but found (non-subset) ${ manuallyDefined . locations . join ( ', ' ) }` ) ) ; } } private ensureSameArguments ( builtIn , manuallyDefined , what , errors ) { const expectedArguments = builtIn . arguments ( ) ; const foundArguments = manuallyDefined . arguments ( ) ; if ( expectedArguments . length !== foundArguments . length ) { errors . push ( error ( `Invalid redefinition of built-in ${ what }: should have ${ expectedArguments . length } arguments but ${ foundArguments . length } found in redefinition` ) ) ; return ; } for ( const expectedArgument of expectedArguments ) { const foundArgument = manuallyDefined . argument ( expectedArgument . name ) ! ; const expectedType = expectedArgument . type ! ; let actualType = foundArgument . type ! ; if ( isNonNullType ( actualType ) && ! isNonNullType ( expectedType ) ) { actualType = actualType . ofType ; } if ( ! sameType ( expectedType , actualType ) ) { errors . push ( error ( `Invalid redefinition of built-in ${ what }: ${ expectedArgument . coordinate } should have type ${ expectedArgument . type ! } but found type ${ foundArgument . type ! }` ) ) ; } else if ( ! isNonNullType ( actualType ) && ! valueEquals ( expectedArgument . defaultValue , foundArgument . defaultValue ) ) { errors . push ( error ( `Invalid redefinition of built-in ${ what }: ${ expectedArgument . coordinate } should have default value ${ valueToString ( expectedArgument . defaultValue ) } but found default value ${ valueToString ( foundArgument . defaultValue ) }` ) ) ; } } } private ensureSameTypeStructure ( builtIn , manuallyDefined , errors ) { if ( builtIn . kind !== manuallyDefined . kind ) { errors . push ( error ( `Invalid redefinition of built-in type ${ builtIn }: ${ builtIn } should be a ${ builtIn . kind } type but redefined as a ${ manuallyDefined . kind }` ) ) ; return ; } switch ( builtIn . kind ) { case 'ScalarType' : return ; case 'ObjectType' : const redefinedObject = manuallyDefined as ObjectType ; for ( const builtInField of builtIn . fields ( ) ) { const redefinedField = redefinedObject . field ( builtInField . name ) ; if ( ! redefinedField ) { errors . push ( error ( `Invalid redefinition of built-in type ${ builtIn }: redefinition is missing field ${ builtInField }` ) ) ; return ; } let rType = redefinedField . type ! ; if ( ! isNonNullType ( builtInField . type ! ) && isNonNullType ( rType ) ) { rType = rType . ofType ; } if ( ! sameType ( builtInField . type ! , rType ) ) { errors . push ( error ( `Invalid redefinition of field ${ builtInField } of built-in type ${ builtIn }: should have type ${ builtInField . type } but redefined with type ${ redefinedField . type }` ) ) ; return ; } this . ensureSameArguments ( builtInField , redefinedField , `field ${ builtInField . coordinate }` , errors ) ; } break ; case 'UnionType' : const redefinedUnion = manuallyDefined as UnionType ; const builtInMembers = sortedMemberNames ( builtIn ) ; const redefinedMembers = sortedMemberNames ( redefinedUnion ) ; if ( ! arrayEquals ( builtInMembers , redefinedMembers ) ) { errors . push ( error ( `Invalid redefinition of built-in type ${ builtIn }: redefinition has members [${ redefinedMembers }] but should have members [${ builtInMembers }]` ) ) ; } break ; default : errors . push ( error ( `Invalid redefinition of built-in type ${ builtIn }: cannot redefine ${ builtIn . kind } built-in types` ) ) ; } } protected addBuiltInScalar ( schema , name ) { return schema . addType ( new ScalarType ( name , true ) ) ; } protected addBuiltInObject ( schema , name ) { return schema . addType ( new ObjectType ( name , true ) ) ; } protected addBuiltInUnion ( schema , name ) { return schema . addType ( new UnionType ( name , true ) ) ; } protected addBuiltInDirective ( schema , name ) { return schema . addDirectiveDefinition ( new DirectiveDefinition ( name , true ) ) ; } protected addBuiltInField ( parentType , name , type ) { return parentType . addField ( new FieldDefinition ( name , true ) , type ) ; } protected getTypedDirective < TApplicationArgs extends { [ key ] : any ; } > ( schema , name ) { const directive = schema . directive ( name ) ; if ( ! directive ) { throw new Error ( `The provided schema has not be built with the ${ name } directive built-in` ) ; } return directive as DirectiveDefinition < TApplicationArgs > ; } includeDirective ( schema ) { return this . getTypedDirective ( schema , 'include' ) ; } skipDirective ( schema ) { return this . getTypedDirective ( schema , 'skip' ) ; } deprecatedDirective ( schema ) { return this . getTypedDirective ( schema , 'deprecated' ) ; } specifiedByDirective ( schema ) { return this . getTypedDirective ( schema , 'specifiedBy' ) ; } } export class CoreFeature { constructor ( readonly url , readonly nameInSchema , readonly directive , readonly purpose ? ) { } isFeatureDefinition ( element ) { return element . name . startsWith ( this . nameInSchema + '__' ) || ( element . kind === 'DirectiveDefinition' && element . name === this . nameInSchema ) ; } } export class CoreFeatures { readonly coreDefinition : CoreSpecDefinition ; private readonly byAlias : Map < string , CoreFeature > = new Map ( ) ; private readonly byIdentity : Map < string , CoreFeature > = new Map ( ) ; constructor ( readonly coreItself ) { this . add ( coreItself ) ; const coreDef = CORE_VERSIONS . find ( coreItself . url . version ) ; if ( ! coreDef ) { throw error ( `Schema uses unknown version ${ coreItself . url . version } of the core spec (known versions: ${ CORE_VERSIONS . versions ( ) . join ( ', ' ) })` ) ; } this . coreDefinition = coreDef ; } getByIdentity ( identity ) { return this . byIdentity . get ( identity ) ; } allFeatures ( ) { return this . byIdentity . values ( ) ; } private removeFeature ( featureIdentity ) { const feature = this . byIdentity . get ( featureIdentity ) ; if ( feature ) { this . byIdentity . delete ( featureIdentity ) ; this . byAlias . delete ( feature . nameInSchema ) ; } } private maybeAddFeature ( directive ) { if ( directive . definition ?. name !== this . coreItself . nameInSchema ) { return undefined ; } const args = ( directive as Directive < SchemaDefinition , CoreDirectiveArgs > ) . arguments ( ) ; const url = FeatureUrl . parse ( args . feature ) ; const existing = this . byIdentity . get ( url . identity ) ; if ( existing ) { throw error ( `Duplicate inclusion of feature ${ url . identity }` ) ; } const feature = new CoreFeature ( url , args . as ?? url . name , directive , args . for ) ; this . add ( feature ) ; return feature ; } private add ( feature ) { this . byAlias . set ( feature . nameInSchema , feature ) ; this . byIdentity . set ( feature . url . identity , feature ) ; } } const toASTPrintOptions = { ... defaultPrintOptions , showNonGraphQLBuiltIns : true } ; export class Schema { private _schemaDefinition : SchemaDefinition ; private readonly _builtInTypes = new MapWithCachedArrays < string , NamedType > ( ) ; private readonly _types = new MapWithCachedArrays < string , NamedType > ( ) ; private readonly _builtInDirectives = new MapWithCachedArrays < string , DirectiveDefinition > ( ) ; private readonly _directives = new MapWithCachedArrays < string , DirectiveDefinition > ( ) ; private _coreFeatures ? : CoreFeatures ; private isConstructed : boolean = false ; private isValidated : boolean = false ; private cachedDocument ? : DocumentNode ; private apiSchema ? : Schema ; constructor ( readonly builtIns = graphQLBuiltIns ) { this . _schemaDefinition = new SchemaDefinition ( ) ; Element . prototype [ 'setParent' ] . call ( this . _schemaDefinition , this ) ; builtIns . addBuiltInTypes ( this ) ; builtIns . addBuiltInDirectives ( this ) ; this . isConstructed = true ; } private canModifyBuiltIn ( ) { return ! this . isConstructed ; } private runWithBuiltInModificationAllowed ( fct ) { const wasConstructed = this . isConstructed ; this . isConstructed = false ; fct ( ) ; this . isConstructed = wasConstructed ; } private renameTypeInternal ( oldName , newName ) { this . _types . set ( newName , this . _types . get ( oldName ) ! ) ; this . _types . delete ( oldName ) ; } private removeTypeInternal ( type ) { this . _types . delete ( type . name ) ; } private removeDirectiveInternal ( definition ) { this . _directives . delete ( definition . name ) ; } private markAsCoreSchema ( coreItself ) { this . _coreFeatures = new CoreFeatures ( coreItself ) ; } private unmarkAsCoreSchema ( ) { this . _coreFeatures = undefined ; } private onModification ( ) { if ( this . isConstructed ) { this . invalidate ( ) ; this . cachedDocument = undefined ; this . apiSchema = undefined ; } } private forceSetCachedDocument ( document , addNonGraphQLBuiltIns = true ) { this . cachedDocument = addNonGraphQLBuiltIns ? this . builtIns . maybeUpdateSubgraphDocument ( this , document ) : document ; } isCoreSchema ( ) { return this . coreFeatures !== undefined ; } get coreFeatures ( ) : CoreFeatures | undefined { return this . _coreFeatures ; } toAST ( ) { if ( ! this . cachedDocument ) { this . forceSetCachedDocument ( parse ( printSchema ( this , toASTPrintOptions ) , { noLocation : true } ) , false ) ; } return this . cachedDocument ! ; } toAPISchema ( ) { if ( ! this . apiSchema ) { this . validate ( ) ; const apiSchema = this . clone ( ) ; removeInaccessibleElements ( apiSchema ) ; const coreFeatures = apiSchema . coreFeatures ; if ( coreFeatures ) { for ( const coreFeature of coreFeatures . allFeatures ( ) ) { removeFeatureElements ( apiSchema , coreFeature ) ; } } assert ( ! apiSchema . isCoreSchema ( ) , "The API schema shouldn't be a core schema" ) ; apiSchema . validate ( ) ; this . apiSchema = apiSchema ; } return this . apiSchema ; } toGraphQLJSSchema ( isSubgraph = false ) { if ( ! isSubgraph ) { return buildGraphqlSchemaFromAST ( this . toAST ( ) ) ; } const ast = parse ( printSchema ( this , { ... toASTPrintOptions , mergeTypesAndExtensions : true } ) , { noLocation : true } ) ; return buildGraphqlSchemaFromAST ( ast ) ; } get schemaDefinition ( ) : SchemaDefinition { return this . _schemaDefinition ; } /**
     * All the types defined on this schema, excluding the built-in types.
     */ types < T extends NamedType > ( kind ? , includeNonGraphQLBuiltIns = false ) { const allKinds = this . _types . values ( ) ; const forKind = ( kind ? allKinds . filter ( t => t . kind === kind ) : allKinds ) as readonly T [ ] ; return includeNonGraphQLBuiltIns ? this . builtInTypes ( kind ) . filter ( t => ! graphQLBuiltIns . isGraphQLBuiltIn ( t ) ) . concat ( forKind ) : forKind ; } /**
     * All the built-in types for this schema (those that are not displayed when printing the schema).
     */ builtInTypes < T extends NamedType > ( kind ? , includeShadowed = false ) { const allBuiltIns = this . _builtInTypes . values ( ) ; const forKind = ( kind ? allBuiltIns . filter ( t => t . kind === kind ) : allBuiltIns ) as readonly T [ ] ; return includeShadowed ? forKind : forKind . filter ( t => ! this . isShadowedBuiltInType ( t ) ) ; } private isShadowedBuiltInType ( type ) { return type . isBuiltIn && this . _types . has ( type . name ) ; } /**
      * All the types, including the built-in ones.
      */ allTypes < T extends NamedType > ( kind ? ) { return this . builtInTypes ( kind ) . concat ( this . types ( kind ) ) ; } /**
     * The type of the provide name in this schema if one is defined or if it is the name of a built-in.
     */ type ( name ) { const type = this . _types . get ( name ) ; return type ? type : this . _builtInTypes . get ( name ) ; } typeOfKind < T extends NamedType > ( name , kind ) { const type = this . type ( name ) ; return type && type . kind === kind ? type as T : undefined ; } intType ( ) { return this . _builtInTypes . get ( 'Int' ) ! as ScalarType ; } floatType ( ) { return this . _builtInTypes . get ( 'Float' ) ! as ScalarType ; } stringType ( ) { return this . _builtInTypes . get ( 'String' ) ! as ScalarType ; } booleanType ( ) { return this . _builtInTypes . get ( 'Boolean' ) ! as ScalarType ; } idType ( ) { return this . _builtInTypes . get ( 'ID' ) ! as ScalarType ; } addType < T extends NamedType > ( type ) { const existing = this . type ( type . name ) ; if ( existing && ! existing . isBuiltIn ) { throw error ( `Type ${ type } already exists in this schema` ) ; } if ( type . isAttached ( ) ) { if ( type . parent == this ) { return type ; } throw error ( `Cannot add type ${ type } to this schema; it is already attached to another schema` ) ; } if ( type . isBuiltIn ) { if ( ! this . isConstructed ) { this . _builtInTypes . set ( type . name , type ) ; } else { throw error ( `Cannot add built-in ${ type } to this schema (built-ins can only be added at schema construction time)` ) ; } } else { this . _types . set ( type . name , type ) ; } Element . prototype [ 'setParent' ] . call ( type , this ) ; const defaultSchemaRoot = checkDefaultSchemaRoot ( type ) ; if ( defaultSchemaRoot && ! this . schemaDefinition . root ( defaultSchemaRoot ) ) { this . schemaDefinition . setRoot ( defaultSchemaRoot , type as ObjectType ) ; } this . onModification ( ) ; return type ; } /**
     * All the directive defined on this schema, excluding the built-in directives.
     */ directives ( includeNonGraphQLBuiltIns = false ) { return includeNonGraphQLBuiltIns ? this . builtInDirectives ( ) . filter ( d => ! graphQLBuiltIns . isGraphQLBuiltIn ( d ) ) . concat ( this . _directives . values ( ) ) : this . _directives . values ( ) ; } /**
     * All the built-in directives for this schema (those that are not displayed when printing the schema).
     */ builtInDirectives ( includeShadowed = false ) { return includeShadowed ? this . _builtInDirectives . values ( ) : this . _builtInDirectives . values ( ) . filter ( d => ! this . isShadowedBuiltInDirective ( d ) ) ; } allDirectives ( ) { return this . builtInDirectives ( ) . concat ( this . directives ( ) ) ; } private isShadowedBuiltInDirective ( directive ) { return directive . isBuiltIn && this . _directives . has ( directive . name ) ; } directive ( name ) { const directive = this . _directives . get ( name ) ; return directive ? directive : this . _builtInDirectives . get ( name ) ; } * allNamedSchemaElement ( ) { for ( const type of this . types ( ) ) { yield type ; yield * type . allChildElements ( ) ; } for ( const directive of this . directives ( ) ) { yield directive ; yield * directive . arguments ( ) ; } } * allSchemaElement ( ) { yield this . _schemaDefinition ; yield * this . allNamedSchemaElement ( ) ; } addDirectiveDefinition ( name ) ; addDirectiveDefinition ( directive ) ; addDirectiveDefinition ( directiveOrName ) { const definition = typeof directiveOrName === 'string' ? new DirectiveDefinition ( directiveOrName ) : directiveOrName ; const existing = this . directive ( definition . name ) ; if ( existing && ! existing . isBuiltIn ) { throw error ( `Directive ${ definition } already exists in this schema` ) ; } if ( definition . isAttached ( ) ) { if ( definition . parent == this ) { return definition ; } throw error ( `Cannot add directive ${ definition } to this schema; it is already attached to another schema` ) ; } if ( definition . isBuiltIn ) { if ( ! this . isConstructed ) { this . _builtInDirectives . set ( definition . name , definition ) ; } else { throw error ( `Cannot add built-in ${ definition } to this schema (built-ins can only be added at schema construction time)` ) ; } } else { this . _directives . set ( definition . name , definition ) ; } Element . prototype [ 'setParent' ] . call ( definition , this ) ; this . onModification ( ) ; return definition ; } invalidate ( ) { this . isValidated = false ; } validate ( ) { if ( this . isValidated ) { return ; } this . runWithBuiltInModificationAllowed ( ( ) => { this . builtIns . prepareValidation ( this ) ; addIntrospectionFields ( this ) ; } ) ; let errors = validateSDL ( this . toAST ( ) , undefined , this . builtIns . validationRules ( ) ) ; errors = errors . concat ( validateSchema ( this ) ) ; if ( errors . length === 0 ) { this . runWithBuiltInModificationAllowed ( ( ) => { errors = this . builtIns . onValidation ( this ) ; } ) ; } if ( errors . length > 0 ) { throw ErrGraphQLValidationFailed ( errors as GraphQLError [ ] ) ; } this . isValidated = true ; } clone ( builtIns ? ) { const cloned = new Schema ( builtIns ?? this . builtIns ) ; copy ( this , cloned ) ; if ( this . isValidated ) { cloned . validate ( ) ; } return cloned ; } } export class RootType extends BaseExtensionMember < SchemaDefinition > { constructor ( readonly rootKind , readonly type ) { super ( ) ; } isDefaultRootName ( ) { return defaultRootName ( this . rootKind ) == this . type . name ; } protected removeInner ( ) { SchemaDefinition . prototype [ 'removeRootType' ] . call ( this . _parent , this ) ; } } export class SchemaDefinition extends SchemaElement < SchemaDefinition , Schema > { readonly kind = 'SchemaDefinition' as const ; protected readonly _roots = new MapWithCachedArrays < SchemaRootKind , RootType > ( ) ; protected readonly _extensions = new Set < Extension < SchemaDefinition > > ( ) ; roots ( ) { return this . _roots . values ( ) ; } applyDirective < TApplicationArgs extends { [ key ] : any ; } = { [ key ] : any ; } > ( nameOrDefOrDirective , args ? ) { const applied = super . applyDirective ( nameOrDefOrDirective , args ) as Directive < SchemaDefinition , TApplicationArgs > ; const schema = this . schema ( ) ; const coreFeatures = schema . coreFeatures ; if ( isCoreSpecDirectiveApplication ( applied ) ) { if ( coreFeatures ) { throw error ( `Invalid duplicate application of the @core feature` ) ; } const schemaDirective = applied as Directive < SchemaDefinition , CoreDirectiveArgs > ; const args = schemaDirective . arguments ( ) ; const url = FeatureUrl . parse ( args . feature ) ; const core = new CoreFeature ( url , args . as ?? 'core' , schemaDirective , args . for ) ; Schema . prototype [ 'markAsCoreSchema' ] . call ( schema , core ) ; } else if ( coreFeatures ) { CoreFeatures . prototype [ 'maybeAddFeature' ] . call ( coreFeatures , applied ) ; } this . onModification ( ) ; return applied ; } root ( rootKind ) { return this . _roots . get ( rootKind ) ; } rootType ( rootKind ) { return this . root ( rootKind ) ?. type ; } setRoot ( rootKind , nameOrType ) { let toSet ; if ( typeof nameOrType === 'string' ) { this . checkUpdate ( ) ; const obj = this . schema ( ) . type ( nameOrType ) ; if ( ! obj ) { throw new GraphQLError ( `Cannot set schema ${ rootKind } root to unknown type ${ nameOrType }` ) ; } else if ( obj . kind != 'ObjectType' ) { throw new GraphQLError ( `${ defaultRootName ( rootKind ) } root type must be an Object type${ rootKind === 'query' ? '' : ' if provided' }, it cannot be set to ${ nameOrType } (an ${ obj . kind }).` ) ; } toSet = new RootType ( rootKind , obj ) ; } else { this . checkUpdate ( nameOrType ) ; toSet = new RootType ( rootKind , nameOrType ) ; } const prevRoot = this . _roots . get ( rootKind ) ; if ( prevRoot ) { removeReferenceToType ( this , prevRoot . type ) ; } this . _roots . set ( rootKind , toSet ) ; Element . prototype [ 'setParent' ] . call ( toSet , this ) ; addReferenceToType ( this , toSet . type ) ; this . onModification ( ) ; return toSet ; } extensions ( ) { return this . _extensions ; } newExtension ( ) { return this . addExtension ( new Extension ( ) ) ; } addExtension ( extension ) { this . checkUpdate ( ) ; if ( this . _extensions . has ( extension ) ) { return extension ; } if ( extension . extendedElement ) { throw error ( `Cannot add extension to this schema: extension is already added to another schema` ) ; } this . _extensions . add ( extension ) ; Extension . prototype [ 'setExtendedElement' ] . call ( extension , this ) ; this . onModification ( ) ; return extension ; } private removeRootType ( rootType ) { this . _roots . delete ( rootType . rootKind ) ; removeReferenceToType ( this , rootType . type ) ; } protected removeTypeReference ( toRemove ) { for ( const rootType of this . roots ( ) ) { if ( rootType . type == toRemove ) { this . _roots . delete ( rootType . rootKind ) ; } } } toString ( ) { return `schema[${ this . _roots . keys ( ) . join ( ', ' ) }]` ; } } export class ScalarType extends BaseNamedType < OutputTypeReferencer | InputTypeReferencer , ScalarType > { readonly kind = 'ScalarType' as const ; protected removeTypeReference ( type ) { assert ( false , `Scalar type ${ this } can't reference other types; shouldn't be asked to remove reference to ${ type }` ) ; } protected hasNonExtensionInnerElements ( ) { return false ; } protected removeInnerElements ( ) { } protected removeReferenceRecursive ( ref ) { ref . remove ( ) ; } } export class InterfaceImplementation < T extends ObjectType | InterfaceType > extends BaseExtensionMember < T > { readonly interface : InterfaceType ; constructor ( itf ) { super ( ) ; this . interface = itf ; } protected removeInner ( ) { FieldBasedType . prototype [ 'removeInterfaceImplementation' ] . call ( this . _parent , this . interface ) ; } toString ( ) { return `'implements ${ this . interface }'` ; } } abstract class FieldBasedType < T extends ( ObjectType | InterfaceType ) & NamedSchemaElement < T , Schema , R > , R > extends BaseNamedType < R , T > { private readonly _interfaceImplementations : MapWithCachedArrays < string , InterfaceImplementation < T > > = new MapWithCachedArrays ( ) ; private readonly _fields : MapWithCachedArrays < string , FieldDefinition < T > > = new MapWithCachedArrays ( ) ; private _cachedNonBuiltInFields ? : readonly FieldDefinition < T > [ ] ; protected onAttached ( ) { Schema . prototype [ 'runWithBuiltInModificationAllowed' ] . call ( this . schema ( ) , ( ) => { this . addField ( new FieldDefinition ( typenameFieldName , true ) , new NonNullType ( this . schema ( ) . stringType ( ) ) ) ; } ) ; } private removeFieldInternal ( field ) { this . _fields . delete ( field . name ) ; this . _cachedNonBuiltInFields = undefined ; } interfaceImplementations ( ) { return this . _interfaceImplementations . values ( ) ; } interfaceImplementation ( type ) { return this . _interfaceImplementations . get ( typeof type === 'string' ? type : type . name ) ; } interfaces ( ) { return this . interfaceImplementations ( ) . map ( impl => impl . interface ) ; } implementsInterface ( type ) { return this . _interfaceImplementations . has ( typeof type === 'string' ? type : type . name ) ; } addImplementedInterface ( nameOrItfOrItfImpl ) { let toAdd ; if ( nameOrItfOrItfImpl instanceof InterfaceImplementation ) { this . checkUpdate ( nameOrItfOrItfImpl ) ; toAdd = nameOrItfOrItfImpl ; } else { let itf ; if ( typeof nameOrItfOrItfImpl === 'string' ) { this . checkUpdate ( ) ; const maybeItf = this . schema ( ) . type ( nameOrItfOrItfImpl ) ; if ( ! maybeItf ) { throw new GraphQLError ( `Cannot implement unknown type ${ nameOrItfOrItfImpl }` ) ; } else if ( maybeItf . kind != 'InterfaceType' ) { throw new GraphQLError ( `Cannot implement non-interface type ${ nameOrItfOrItfImpl } (of type ${ maybeItf . kind })` ) ; } itf = maybeItf ; } else { itf = nameOrItfOrItfImpl ; } toAdd = new InterfaceImplementation < T > ( itf ) ; } const existing = this . _interfaceImplementations . get ( toAdd . interface . name ) ; if ( ! existing ) { this . _interfaceImplementations . set ( toAdd . interface . name , toAdd ) ; addReferenceToType ( this , toAdd . interface ) ; Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; this . onModification ( ) ; return toAdd ; } else { return existing ; } } /**
     * All the fields of this type, excluding the built-in ones.
     */ fields ( includeNonGraphQLBuiltIns = false ) { if ( includeNonGraphQLBuiltIns ) { return this . allFields ( ) . filter ( f => ! graphQLBuiltIns . isGraphQLBuiltIn ( f ) ) ; } if ( ! this . _cachedNonBuiltInFields ) { this . _cachedNonBuiltInFields = this . _fields . values ( ) . filter ( f => ! f . isBuiltIn ) ; } return this . _cachedNonBuiltInFields ; } hasFields ( includeNonGraphQLBuiltIns = false ) { return this . fields ( includeNonGraphQLBuiltIns ) . length > 0 ; } /**
     * All the built-in fields for this type (those that are not displayed when printing the schema).
     */ builtInFields ( ) { return this . allFields ( ) . filter ( f => f . isBuiltIn ) ; } /**
      * All the fields of this type, including the built-in ones.
      */ allFields ( ) { return this . _fields . values ( ) ; } field ( name ) { return this . _fields . get ( name ) ; } /**
     * A shortcut to access the __typename field.
     *
     * Note that an _attached_ (field-based) type will always have this field, but _detached_ types won't, so this method
     * will only return `undefined` on detached objects.
     */ typenameField ( ) { return this . field ( typenameFieldName ) ; } addField ( nameOrField , type ? ) { let toAdd ; if ( typeof nameOrField === 'string' ) { this . checkUpdate ( ) ; toAdd = new FieldDefinition < T > ( nameOrField ) ; } else { this . checkUpdate ( nameOrField ) ; toAdd = nameOrField ; } if ( this . field ( toAdd . name ) ) { throw error ( `Field ${ toAdd . name } already exists on ${ this }` ) ; } if ( type && ! isOutputType ( type ) ) { throw error ( `Invalid input type ${ type } for field ${ toAdd . name }: object and interface field types should be output types.` ) ; } this . _fields . set ( toAdd . name , toAdd ) ; this . _cachedNonBuiltInFields = undefined ; Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; if ( type ) { toAdd . type = type ; } this . onModification ( ) ; return toAdd ; } * allChildElements ( ) { for ( const field of this . _fields . values ( ) ) { yield field ; yield * field . arguments ( ) ; } } private removeInterfaceImplementation ( itf ) { this . _interfaceImplementations . delete ( itf . name ) ; removeReferenceToType ( this , itf ) ; } protected removeTypeReference ( type ) { this . _interfaceImplementations . delete ( type . name ) ; } protected removeInnerElements ( ) { for ( const interfaceImpl of this . interfaceImplementations ( ) ) { interfaceImpl . remove ( ) ; } for ( const field of this . allFields ( ) ) { if ( field . isBuiltIn ) { FieldDefinition . prototype [ 'removeParent' ] . call ( field ) ; } else { field . remove ( ) ; } } } protected hasNonExtensionInnerElements ( ) { return this . interfaceImplementations ( ) . some ( itf => itf . ofExtension ( ) === undefined ) || this . fields ( ) . some ( f => f . ofExtension ( ) === undefined ) ; } } export class ObjectType extends FieldBasedType < ObjectType , ObjectTypeReferencer > { readonly kind = 'ObjectType' as const ; /**
     *  Whether this type is one of the schema root type (will return false if the type is detached).
     */ isRootType ( ) { const schema = this . schema ( ) ; return schema . schemaDefinition . roots ( ) . some ( rt => rt . type == this ) ; } /**
     *  Whether this type is the "query" root type of the schema (will return false if the type is detached).
     */ isQueryRootType ( ) { const schema = this . schema ( ) ; return schema . schemaDefinition . root ( 'query' ) ?. type === this ; } protected removeReferenceRecursive ( ref ) { switch ( ref . kind ) { case 'FieldDefinition' : ref . removeRecursive ( ) ; break ; case 'UnionType' : if ( ref . membersCount ( ) === 0 ) { ref . removeRecursive ( ) ; } break ; } } } export class InterfaceType extends FieldBasedType < InterfaceType , InterfaceTypeReferencer > { readonly kind = 'InterfaceType' as const ; allImplementations ( ) { return setValues ( this . _referencers ) . filter ( ref => ref . kind === 'ObjectType' || ref . kind === 'InterfaceType' ) as ( ObjectType | InterfaceType ) [ ] ; } possibleRuntimeTypes ( ) { return this . allImplementations ( ) . filter ( impl => impl . kind === 'ObjectType' ) as ObjectType [ ] ; } isPossibleRuntimeType ( type ) { const typeName = typeof type === 'string' ? type : type . name ; return this . possibleRuntimeTypes ( ) . some ( t => t . name == typeName ) ; } protected removeReferenceRecursive ( ref ) { if ( ref . kind === 'FieldDefinition' ) { ref . removeRecursive ( ) ; } } } export class UnionMember extends BaseExtensionMember < UnionType > { constructor ( readonly type ) { super ( ) ; } protected removeInner ( ) { UnionType . prototype [ 'removeMember' ] . call ( this . _parent , this . type ) ; } } export class UnionType extends BaseNamedType < OutputTypeReferencer , UnionType > { readonly kind = 'UnionType' as const ; protected readonly _members : MapWithCachedArrays < string , UnionMember > = new MapWithCachedArrays ( ) ; private _typenameField ? : FieldDefinition < UnionType > ; protected onAttached ( ) { Schema . prototype [ 'runWithBuiltInModificationAllowed' ] . call ( this . schema ( ) , ( ) => { this . _typenameField = new FieldDefinition ( typenameFieldName , true ) ; Element . prototype [ 'setParent' ] . call ( this . _typenameField , this ) ; this . _typenameField . type = new NonNullType ( this . schema ( ) . stringType ( ) ) ; } ) ; } types ( ) { return this . members ( ) . map ( m => m . type ) ; } members ( ) { return this . _members . values ( ) ; } membersCount ( ) { return this . _members . size ; } hasTypeMember ( type ) { return this . _members . has ( typeof type === 'string' ? type : type . name ) ; } addType ( nameOrTypeOrMember ) { let toAdd ; if ( nameOrTypeOrMember instanceof UnionMember ) { this . checkUpdate ( nameOrTypeOrMember ) ; toAdd = nameOrTypeOrMember ; } else { let obj ; if ( typeof nameOrTypeOrMember === 'string' ) { this . checkUpdate ( ) ; const maybeObj = this . schema ( ) . type ( nameOrTypeOrMember ) ; if ( ! maybeObj ) { throw new GraphQLError ( `Cannot add unknown type ${ nameOrTypeOrMember } as member of union type ${ this . name }` ) ; } else if ( maybeObj . kind != 'ObjectType' ) { throw new GraphQLError ( `Cannot add non-object type ${ nameOrTypeOrMember } (of type ${ maybeObj . kind }) as member of union type ${ this . name }` ) ; } obj = maybeObj ; } else { this . checkUpdate ( nameOrTypeOrMember ) ; obj = nameOrTypeOrMember ; } toAdd = new UnionMember ( obj ) ; } const existing = this . _members . get ( toAdd . type . name ) ; if ( ! existing ) { this . _members . set ( toAdd . type . name , toAdd ) ; Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; addReferenceToType ( this , toAdd . type ) ; this . onModification ( ) ; return toAdd ; } else { return existing ; } } clearTypes ( ) { for ( const type of this . types ( ) ) { this . removeMember ( type ) ; } this . onModification ( ) ; } /**
     * Access a field of the union by name.
     * As the only field that can be accessed on an union is the __typename one, this method will always return undefined unless called
     * on "__typename". However, this exists to allow code working on CompositeType to be more generic.
     */ field ( name ) { if ( name === typenameFieldName && this . _typenameField ) { return this . _typenameField ; } return undefined ; } /**
     * The __typename field (and only field of a union).
     *
     * Note that _attached_ unions always have this field, so this method will only return `undefined` on detached objects.
     */ typenameField ( ) { return this . _typenameField ; } private removeMember ( type ) { this . _members . delete ( type . name ) ; removeReferenceToType ( this , type ) ; } protected removeTypeReference ( type ) { this . _members . delete ( type . name ) ; } protected removeInnerElements ( ) { for ( const member of this . members ( ) ) { member . remove ( ) ; } } protected hasNonExtensionInnerElements ( ) { return this . members ( ) . some ( m => m . ofExtension ( ) === undefined ) ; } protected removeReferenceRecursive ( ref ) { ref . removeRecursive ( ) ; } } export class EnumType extends BaseNamedType < OutputTypeReferencer , EnumType > { readonly kind = 'EnumType' as const ; protected readonly _values : EnumValue [ ] = [ ] ; get values ( ) : readonly EnumValue [ ] { return this . _values ; } value ( name ) { return this . _values . find ( v => v . name == name ) ; } addValue ( value ) ; addValue ( name ) ; addValue ( nameOrValue ) { let toAdd ; if ( typeof nameOrValue === 'string' ) { this . checkUpdate ( ) ; toAdd = new EnumValue ( nameOrValue ) ; } else { this . checkUpdate ( nameOrValue ) ; toAdd = nameOrValue ; } const existing = this . value ( toAdd . name ) ; if ( ! existing ) { this . _values . push ( toAdd ) ; Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; this . onModification ( ) ; return toAdd ; } else { return existing ; } } protected removeTypeReference ( type ) { assert ( false , `Eum type ${ this } can't reference other types; shouldn't be asked to remove reference to ${ type }` ) ; } private removeValueInternal ( value ) { const index = this . _values . indexOf ( value ) ; if ( index >= 0 ) { this . _values . splice ( index , 1 ) ; } } protected removeInnerElements ( ) { this . _values . splice ( 0 , this . _values . length ) ; } protected hasNonExtensionInnerElements ( ) { return this . _values . some ( v => v . ofExtension ( ) === undefined ) ; } protected removeReferenceRecursive ( ref ) { ref . removeRecursive ( ) ; } } export class InputObjectType extends BaseNamedType < InputTypeReferencer , InputObjectType > { readonly kind = 'InputObjectType' as const ; private readonly _fields : Map < string , InputFieldDefinition > = new Map ( ) ; private _cachedFieldsArray ? : InputFieldDefinition [ ] ; /**
     * All the fields of this input type.
     */ fields ( ) { if ( ! this . _cachedFieldsArray ) { this . _cachedFieldsArray = mapValues ( this . _fields ) ; } return this . _cachedFieldsArray ; } field ( name ) { return this . _fields . get ( name ) ; } addField ( field ) ; addField ( name , type ? ) ; addField ( nameOrField , type ? ) { const toAdd = typeof nameOrField === 'string' ? new InputFieldDefinition ( nameOrField ) : nameOrField ; this . checkUpdate ( toAdd ) ; if ( this . field ( toAdd . name ) ) { throw error ( `Field ${ toAdd . name } already exists on ${ this }` ) ; } if ( type && ! isInputType ( type ) ) { throw error ( `Invalid output type ${ type } for field ${ toAdd . name }: input field types should be input types.` ) ; } this . _fields . set ( toAdd . name , toAdd ) ; this . _cachedFieldsArray = undefined ; Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; if ( typeof nameOrField === 'string' && type ) { toAdd . type = type ; } this . onModification ( ) ; return toAdd ; } hasFields ( ) { return this . _fields . size > 0 ; } * allChildElements ( ) { yield * this . _fields . values ( ) ; } protected removeTypeReference ( type ) { assert ( false , `Input Object type ${ this } can't reference other types; shouldn't be asked to remove reference to ${ type }` ) ; } protected removeInnerElements ( ) { for ( const field of this . fields ( ) ) { field . remove ( ) ; } } private removeFieldInternal ( field ) { this . _fields . delete ( field . name ) ; this . _cachedFieldsArray = undefined ; } protected hasNonExtensionInnerElements ( ) { return this . fields ( ) . some ( f => f . ofExtension ( ) === undefined ) ; } protected removeReferenceRecursive ( ref ) { if ( ref . kind === 'ArgumentDefinition' ) { ref . parent ( ) . removeRecursive ( ) ; } else { ref . removeRecursive ( ) ; } } } class BaseWrapperType < T extends Type > { protected constructor ( protected _type ) { assert ( this . _type , 'Cannot wrap an undefined/null type' ) ; } schema ( ) { return this . baseType ( ) . schema ( ) ; } isAttached ( ) { return this . baseType ( ) . isAttached ( ) ; } get ofType ( ) : T { return this . _type ; } baseType ( ) { return baseType ( this . _type ) ; } } export class ListType < T extends Type > extends BaseWrapperType < T > { readonly kind = 'ListType' as const ; constructor ( type ) { super ( type ) ; } toString ( ) { return `[${ this . ofType }]` ; } } export class NonNullType < T extends NullableType > extends BaseWrapperType < T > { readonly kind = 'NonNullType' as const ; constructor ( type ) { super ( type ) ; } toString ( ) { return `${ this . ofType }!` ; } } export class FieldDefinition < TParent extends CompositeType > extends NamedSchemaElementWithType < OutputType , FieldDefinition < TParent > , TParent , never > { readonly kind = 'FieldDefinition' as const ; private readonly _args : MapWithCachedArrays < string , ArgumentDefinition < FieldDefinition < TParent > > > = new MapWithCachedArrays ( ) ; private _extension ? : Extension < TParent > ; constructor ( name , readonly isBuiltIn = false ) { super ( name ) ; } protected isElementBuiltIn ( ) { return this . isBuiltIn ; } get coordinate ( ) : string { const parent = this . _parent ; return `${ parent == undefined ? '<detached>' : parent . coordinate }.${ this . name }` ; } hasArguments ( ) { return this . _args . size > 0 ; } arguments ( ) { return this . _args . values ( ) ; } argument ( name ) { return this . _args . get ( name ) ; } addArgument ( arg ) ; addArgument ( name , type ? , defaultValue ? ) ; addArgument ( nameOrArg , type ? , defaultValue ? ) { let toAdd ; if ( typeof nameOrArg === 'string' ) { this . checkUpdate ( ) ; toAdd = new ArgumentDefinition < FieldDefinition < TParent > > ( nameOrArg ) ; toAdd . defaultValue = defaultValue ; } else { this . checkUpdate ( nameOrArg ) ; toAdd = nameOrArg ; } const existing = this . argument ( toAdd . name ) ; if ( existing ) { if ( type && existing . type && ! sameType ( type , existing . type ) ) { throw error ( `Argument ${ toAdd . name } already exists on field ${ this . name } with a different type (${ existing . type })` ) ; } if ( defaultValue && ( ! existing . defaultValue || ! valueEquals ( defaultValue , existing . defaultValue ) ) ) { throw error ( `Argument ${ toAdd . name } already exists on field ${ this . name } with a different default value (${ valueToString ( existing . defaultValue ) })` ) ; } return existing ; } if ( type && ! isInputType ( type ) ) { throw error ( `Invalid output type ${ type } for argument ${ toAdd . name } of ${ this }: arguments should be input types.` ) ; } this . _args . set ( toAdd . name , toAdd ) ; Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; if ( typeof nameOrArg === 'string' ) { toAdd . type = type ; } this . onModification ( ) ; return toAdd ; } ofExtension ( ) { return this . _extension ; } setOfExtension ( extension ) { this . checkUpdate ( ) ; if ( extension && ! this . _parent ?. extensions ( ) . has ( extension as any ) ) { throw error ( `Cannot mark field ${ this . name } as part of the provided extension: it is not an extension of field parent type ${ this . parent }` ) ; } this . _extension = extension ; this . onModification ( ) ; } isIntrospectionField ( ) { return isIntrospectionName ( this . name ) ; } isSchemaIntrospectionField ( ) { return introspectionFieldNames . includes ( this . name ) ; } private removeArgumentInternal ( name ) { this . _args . delete ( name ) ; } private removeParent ( ) { this . _parent = undefined ; } isDeprecated ( ) { return this . hasAppliedDirective ( 'deprecated' ) ; } /**
     * Removes this field definition from its parent type.
     *
     * After calling this method, this field definition will be "detached": it wil have no parent, schema, type,
     * arguments or directives.
     */ remove ( ) { if ( ! this . _parent ) { return [ ] ; } this . onModification ( ) ; this . removeAppliedDirectives ( ) ; this . type = undefined ; this . _extension = undefined ; for ( const arg of this . arguments ( ) ) { arg . remove ( ) ; } FieldBasedType . prototype [ 'removeFieldInternal' ] . call ( this . _parent , this ) ; this . _parent = undefined ; return [ ] ; } /**
     * Like `remove()`, but if this field was the last field of its parent type, the parent type is removed through its `removeRecursive` method.
     */ removeRecursive ( ) { const parent = this . _parent ; this . remove ( ) ; if ( parent && ! isUnionType ( parent ) && parent . fields ( ) . length === 0 ) { parent . removeRecursive ( ) ; } } toString ( ) { const args = this . _args . size == 0 ? "" : '(' + this . arguments ( ) . map ( arg => arg . toString ( ) ) . join ( ', ' ) + ')' ; return `${ this . name }${ args }: ${ this . type }` ; } } export class InputFieldDefinition extends NamedSchemaElementWithType < InputType , InputFieldDefinition , InputObjectType , never > { readonly kind = 'InputFieldDefinition' as const ; private _extension ? : Extension < InputObjectType > ; defaultValue ? : any ; get coordinate ( ) : string { const parent = this . _parent ; return `${ parent == undefined ? '<detached>' : parent . coordinate }.${ this . name }` ; } isRequired ( ) { return isNonNullType ( this . type ! ) && this . defaultValue === undefined ; } ofExtension ( ) { return this . _extension ; } setOfExtension ( extension ) { this . checkUpdate ( ) ; if ( extension && ! this . _parent ?. extensions ( ) . has ( extension as any ) ) { throw error ( `Cannot mark field ${ this . name } as part of the provided extension: it is not an extension of field parent type ${ this . parent }` ) ; } this . _extension = extension ; this . onModification ( ) ; } isDeprecated ( ) { return this . hasAppliedDirective ( 'deprecated' ) ; } /**
     * Removes this field definition from its parent type.
     *
     * After calling this method, this field definition will be "detached": it wil have no parent, schema, type,
     * arguments or directives.
     */ remove ( ) { if ( ! this . _parent ) { return [ ] ; } this . onModification ( ) ; InputObjectType . prototype [ 'removeFieldInternal' ] . call ( this . _parent , this ) ; this . _parent = undefined ; this . type = undefined ; return [ ] ; } /**
     * Like `remove()`, but if this field was the last field of its parent type, the parent type is removed through its `removeRecursive` method.
     */ removeRecursive ( ) { const parent = this . _parent ; this . remove ( ) ; if ( parent && parent . fields ( ) . length === 0 ) { parent . removeRecursive ( ) ; } } toString ( ) { const defaultStr = this . defaultValue === undefined ? "" : ` = ${ valueToString ( this . defaultValue , this . type ) }` ; return `${ this . name }: ${ this . type }${ defaultStr }` ; } } export class ArgumentDefinition < TParent extends FieldDefinition < any > | DirectiveDefinition > extends NamedSchemaElementWithType < InputType , ArgumentDefinition < TParent > , TParent , never > { readonly kind = 'ArgumentDefinition' as const ; defaultValue ? : any ; constructor ( name ) { super ( name ) ; } get coordinate ( ) : string { const parent = this . _parent ; return `${ parent == undefined ? '<detached>' : parent . coordinate }(${ this . name }:)` ; } isRequired ( ) { return isNonNullType ( this . type ! ) && this . defaultValue === undefined ; } isDeprecated ( ) { return this . hasAppliedDirective ( 'deprecated' ) ; } /**
     * Removes this argument definition from its parent element (field or directive).
     *
     * After calling this method, this argument definition will be "detached": it wil have no parent, schema, type,
     * default value or directives.
     */ remove ( ) { if ( ! this . _parent ) { return [ ] ; } this . onModification ( ) ; if ( this . _parent instanceof FieldDefinition ) { FieldDefinition . prototype [ 'removeArgumentInternal' ] . call ( this . _parent , this . name ) ; } else { DirectiveDefinition . prototype [ 'removeArgumentInternal' ] . call ( this . _parent , this . name ) ; } this . _parent = undefined ; this . type = undefined ; this . defaultValue = undefined ; return [ ] ; } toString ( ) { const defaultStr = this . defaultValue === undefined ? "" : ` = ${ valueToString ( this . defaultValue , this . type ) }` ; return `${ this . name }: ${ this . type }${ defaultStr }` ; } } export class EnumValue extends NamedSchemaElement < EnumValue , EnumType , never > { readonly kind = 'EnumValue' as const ; private _extension ? : Extension < EnumType > ; get coordinate ( ) : string { const parent = this . _parent ; return `${ parent == undefined ? '<detached>' : parent . coordinate }.${ this . name }` ; } ofExtension ( ) { return this . _extension ; } setOfExtension ( extension ) { this . checkUpdate ( ) ; if ( extension && ! this . _parent ?. extensions ( ) . has ( extension ) ) { throw error ( `Cannot mark field ${ this . name } as part of the provided extension: it is not an extension of field parent type ${ this . parent }` ) ; } this . _extension = extension ; this . onModification ( ) ; } isDeprecated ( ) { return this . hasAppliedDirective ( 'deprecated' ) ; } /**
     * Removes this field definition from its parent type.
     *
     * After calling this method, this field definition will be "detached": it wil have no parent, schema, type,
     * arguments or directives.
     */ remove ( ) { if ( ! this . _parent ) { return [ ] ; } this . onModification ( ) ; EnumType . prototype [ 'removeValueInternal' ] . call ( this . _parent , this ) ; this . _parent = undefined ; return [ ] ; } protected removeTypeReference ( type ) { assert ( false , `Enum value ${ this } can't reference other types; shouldn't be asked to remove reference to ${ type }` ) ; } toString ( ) { return `${ this . name }` ; } } export class DirectiveDefinition < TApplicationArgs extends { [ key ] : any ; } = { [ key ] : any ; } > extends NamedSchemaElement < DirectiveDefinition < TApplicationArgs > , Schema , Directive > { readonly kind = 'DirectiveDefinition' as const ; private readonly _args : MapWithCachedArrays < string , ArgumentDefinition < DirectiveDefinition > > = new MapWithCachedArrays ( ) ; repeatable : boolean = false ; private readonly _locations : DirectiveLocation [ ] = [ ] ; private readonly _referencers : Set < Directive < SchemaElement < any , any > , TApplicationArgs > > = new Set ( ) ; constructor ( name , readonly isBuiltIn = false ) { super ( name ) ; } get coordinate ( ) : string { return `@${ this . name }` ; } arguments ( ) { return this . _args . values ( ) ; } argument ( name ) { return this . _args . get ( name ) ; } addArgument ( arg ) ; addArgument ( name , type ? , defaultValue ? ) ; addArgument ( nameOrArg , type ? , defaultValue ? ) { let toAdd ; if ( typeof nameOrArg === 'string' ) { this . checkUpdate ( ) ; toAdd = new ArgumentDefinition < DirectiveDefinition > ( nameOrArg ) ; toAdd . defaultValue = defaultValue ; } else { this . checkUpdate ( nameOrArg ) ; toAdd = nameOrArg ; } if ( this . argument ( toAdd . name ) ) { throw error ( `Argument ${ toAdd . name } already exists on field ${ this . name }` ) ; } this . _args . set ( toAdd . name , toAdd ) ; Element . prototype [ 'setParent' ] . call ( toAdd , this ) ; if ( typeof nameOrArg === 'string' ) { toAdd . type = type ; } this . onModification ( ) ; return toAdd ; } private removeArgumentInternal ( name ) { this . _args . delete ( name ) ; } get locations ( ) : readonly DirectiveLocation [ ] { return this . _locations ; } addLocations ( ... locations ) { let modified = false ; for ( const location of locations ) { if ( ! this . _locations . includes ( location ) ) { this . _locations . push ( location ) ; modified = true ; } } if ( modified ) { this . onModification ( ) ; } return this ; } addAllLocations ( ) { return this . addLocations ( ... Object . values ( DirectiveLocation ) ) ; } addAllTypeLocations ( ) { return this . addLocations ( DirectiveLocation . SCALAR , DirectiveLocation . OBJECT , DirectiveLocation . INTERFACE , DirectiveLocation . UNION , DirectiveLocation . ENUM , DirectiveLocation . INPUT_OBJECT ) ; } removeLocations ( ... locations ) { let modified = false ; for ( const location of locations ) { const index = this . _locations . indexOf ( location ) ; if ( index >= 0 ) { this . _locations . splice ( index , 1 ) ; modified = true ; } } if ( modified ) { this . onModification ( ) ; } return this ; } applications ( ) { return setValues ( this . _referencers ) ; } private addReferencer ( referencer ) { assert ( referencer , 'Referencer should exists' ) ; this . _referencers . add ( referencer ) ; } private removeReferencer ( referencer ) { this . _referencers . delete ( referencer ) ; } protected removeTypeReference ( type ) { assert ( false , `Directive definition ${ this } can't reference other types (it's arguments can); shouldn't be asked to remove reference to ${ type }` ) ; } remove ( ) { if ( ! this . _parent ) { return [ ] ; } this . onModification ( ) ; Schema . prototype [ 'removeDirectiveInternal' ] . call ( this . _parent , this ) ; this . _parent = undefined ; assert ( this . _appliedDirectives . length === 0 , "Directive definition should not have directive applied to it" ) ; for ( const arg of this . arguments ( ) ) { arg . remove ( ) ; } const toReturn = setValues ( this . _referencers ) ; this . _referencers . clear ( ) ; return toReturn ; } /**
     * Removes this this directive definition _and_ all its applications.
     */ removeRecursive ( ) { this . remove ( ) . forEach ( ref => ref . remove ( ) ) ; } toString ( ) { return `@${ this . name }` ; } } export class Directive < TParent extends SchemaElement < any , any > | DirectiveTargetElement < any > = SchemaElement < any , any > , TArgs extends { [ key ] : any ; } = { [ key ] : any ; } > extends Element < TParent > implements Named { private _extension ? : Extension < any > ; constructor ( readonly name , private _args ) { super ( ) ; } schema ( ) { return this . parent . schema ( ) ; } get definition ( ) : DirectiveDefinition | undefined { const doc = this . schema ( ) ; return doc . directive ( this . name ) ; } arguments ( includeDefaultValues = false ) { if ( ! includeDefaultValues ) { return this . _args ; } const definition = this . definition ; if ( ! definition ) { throw error ( `Cannot include default values for arguments: cannot find directive definition for ${ this . name }` ) ; } const updated = Object . create ( null ) ; for ( const argDef of definition . arguments ( ) ) { updated [ argDef . name ] = withDefaultValues ( this . _args [ argDef . name ] , argDef ) ; } return updated ; } private onModification ( ) { if ( this . isAttachedToSchemaElement ( ) ) { Schema . prototype [ 'onModification' ] . call ( this . schema ( ) ) ; } } private isAttachedToSchemaElement ( ) { return this . isAttached ( ) ; } setArguments ( args ) { this . _args = args ; this . onModification ( ) ; } argumentType ( name ) { return this . definition ?. argument ( name ) ?. type ; } matchArguments ( expectedArgs ) { const entries = Object . entries ( this . _args ) ; if ( entries . length !== Object . keys ( expectedArgs ) . length ) { return false ; } for ( const [ key , val ] of entries ) { if ( ! ( key in expectedArgs ) ) { return false ; } const expectedVal = expectedArgs [ key ] ; if ( ! valueEquals ( expectedVal , val ) ) { return false ; } } return true ; } ofExtension ( ) { return this . _extension ; } setOfExtension ( extension ) { this . checkUpdate ( ) ; if ( extension ) { const parent = this . parent ; if ( parent instanceof SchemaDefinition || parent instanceof BaseNamedType ) { if ( ! parent . extensions ( ) . has ( extension ) ) { throw error ( `Cannot mark directive ${ this . name } as part of the provided extension: it is not an extension of parent ${ parent }` ) ; } } else { throw error ( `Can only mark directive parts of extensions when directly apply to type or schema definition.` ) ; } } this . _extension = extension ; this . onModification ( ) ; } argumentsToAST ( ) { const entries = Object . entries ( this . _args ) ; if ( entries . length === 0 ) { return undefined ; } const definition = this . definition ; assert ( definition , ( ) => `Cannot convert arguments of detached directive ${ this }` ) ; return entries . map ( ( [ n , v ] ) => { return { kind : Kind . ARGUMENT , name : { kind : Kind . NAME , value : n } , value : valueToAST ( v , definition . argument ( n ) ! . type ! ) ! as ConstValueNode , } ; } ) ; } returns remove ( ) { if ( ! this . _parent ) { return false ; } this . onModification ( ) ; const coreFeatures = this . schema ( ) . coreFeatures ; if ( coreFeatures && this . name === coreFeatures . coreItself . nameInSchema ) { const url = FeatureUrl . parse ( this . _args [ 'feature' ] ! ) ; if ( url . identity === coreFeatures . coreItself . url . identity ) { Schema . prototype [ 'unmarkAsCoreSchema' ] . call ( this . schema ( ) ) ; for ( const d of this . schema ( ) . schemaDefinition . appliedDirectivesOf ( coreFeatures . coreItself . nameInSchema ) ) { d . removeInternal ( ) ; } return true ; } else { CoreFeatures . prototype [ 'removeFeature' ] . call ( coreFeatures , url . identity ) ; } } return this . removeInternal ( ) ; } private removeInternal ( ) { if ( ! this . _parent ) { return false ; } const definition = this . definition ; if ( definition && this . isAttachedToSchemaElement ( ) ) { DirectiveDefinition . prototype [ 'removeReferencer' ] . call ( definition , this as Directive < SchemaElement < any , any > > ) ; } const parentDirectives = this . _parent . appliedDirectives as Directive < TParent > [ ] ; const index = parentDirectives . indexOf ( this ) ; assert ( index >= 0 , ( ) => `Directive ${ this } lists ${ this . _parent } as parent, but that parent doesn't list it as applied directive` ) ; parentDirectives . splice ( index , 1 ) ; this . _parent = undefined ; this . _extension = undefined ; return true ; } toString ( ) { const entries = Object . entries ( this . _args ) . filter ( ( [ _ , v ] ) => v !== undefined ) ; const args = entries . length == 0 ? '' : '(' + entries . map ( ( [ n , v ] ) => `${ n }: ${ valueToString ( v , this . argumentType ( n ) ) }` ) . join ( ', ' ) + ')' ; return `@${ this . name }${ args }` ; } } export class Variable { constructor ( readonly name ) { } toVariableNode ( ) { return { kind : Kind . VARIABLE , name : { kind : Kind . NAME , value : this . name } , } ; } toString ( ) { return '$' + this . name ; } } export type Variables = readonly Variable [ ] ; export function mergeVariables ( v1s , v2s ) { if ( v1s . length == 0 ) { return v2s ; } if ( v2s . length == 0 ) { return v1s ; } const res = v1s . concat ( ) ; for ( const v of v2s ) { if ( ! containsVariable ( v1s , v ) ) { res . push ( v ) ; } } return res ; } export function containsVariable ( variables , toCheck ) { return variables . some ( v => v . name == toCheck . name ) ; } export function isVariable ( v ) { return v instanceof Variable ; } export function variablesInArguments ( args ) { let variables = [ ] ; for ( const value of Object . values ( args ) ) { variables = mergeVariables ( variables , variablesInValue ( value ) ) ; } return variables ; } export class VariableDefinition extends DirectiveTargetElement < VariableDefinition > { constructor ( schema , readonly variable , readonly type , readonly defaultValue ? ) { super ( schema ) ; } toVariableDefinitionNode ( ) { const ast = valueToAST ( this . defaultValue , this . type ) ; return { kind : Kind . VARIABLE_DEFINITION , variable : this . variable . toVariableNode ( ) , type : typeToAST ( this . type ) , defaultValue : ( ast !== undefined ) ? valueNodeToConstValueNode ( ast ) : undefined , directives : this . appliedDirectivesToDirectiveNodes ( ) , } ; } toString ( ) { let base = this . variable + ': ' + this . type ; if ( this . defaultValue ) { base = base + ' = ' + valueToString ( this . defaultValue , this . type ) ; } return base + this . appliedDirectivesToString ( ) ; } } export class VariableDefinitions { private readonly _definitions : MapWithCachedArrays < string , VariableDefinition > = new MapWithCachedArrays ( ) ; add ( definition ) { if ( this . _definitions . has ( definition . variable . name ) ) { return false ; } this . _definitions . set ( definition . variable . name , definition ) ; return true ; } addAll ( definitions ) { for ( const definition of definitions . _definitions . values ( ) ) { this . add ( definition ) ; } } definition ( variable ) { const varName = typeof variable === 'string' ? variable : variable . name ; return this . _definitions . get ( varName ) ; } isEmpty ( ) { return this . _definitions . size === 0 ; } definitions ( ) { return this . _definitions . values ( ) ; } filter ( variables ) { if ( variables . length === 0 ) { return new VariableDefinitions ( ) ; } const newDefs = new VariableDefinitions ( ) ; for ( const variable of variables ) { const def = this . definition ( variable ) ; if ( ! def ) { throw new Error ( `Cannot find variable ${ variable } in definitions ${ this }` ) ; } newDefs . add ( def ) ; } return newDefs ; } toVariableDefinitionNodes ( ) { if ( this . _definitions . size === 0 ) { return undefined ; } return this . definitions ( ) . map ( def => def . toVariableDefinitionNode ( ) ) ; } toString ( ) { return '(' + this . definitions ( ) . join ( ', ' ) + ')' ; } } export function variableDefinitionsFromAST ( schema , definitionNodes ) { const definitions = new VariableDefinitions ( ) ; for ( const definitionNode of definitionNodes ) { if ( ! definitions . add ( variableDefinitionFromAST ( schema , definitionNode ) ) ) { const name = definitionNode . variable . name . value ; throw new GraphQLError ( `Duplicate definition for variable ${ name }` , definitionNodes . filter ( n => n . variable . name . value === name ) ) ; } } return definitions ; } export function variableDefinitionFromAST ( schema , definitionNode ) { const variable = new Variable ( definitionNode . variable . name . value ) ; const type = typeFromAST ( schema , definitionNode . type ) ; if ( ! isInputType ( type ) ) { throw new GraphQLError ( `Invalid type "${ type }" for variable $${ variable }: not an input type` , definitionNode . type ) ; } const def = new VariableDefinition ( schema , variable , type , definitionNode . defaultValue ? valueFromAST ( definitionNode . defaultValue , type ) : undefined ) ; return def ; } export const graphQLBuiltIns = new BuiltIns ( ) ; function addReferenceToType ( referencer , type ) { switch ( type . kind ) { case 'ListType' : addReferenceToType ( referencer , type . baseType ( ) ) ; break ; case 'NonNullType' : addReferenceToType ( referencer , type . baseType ( ) ) ; break ; default : BaseNamedType . prototype [ 'addReferencer' ] . call ( type , referencer ) ; break ; } } function removeReferenceToType ( referencer , type ) { switch ( type . kind ) { case 'ListType' : removeReferenceToType ( referencer , type . baseType ( ) ) ; break ; case 'NonNullType' : removeReferenceToType ( referencer , type . baseType ( ) ) ; break ; default : BaseNamedType . prototype [ 'removeReferencer' ] . call ( type , referencer ) ; break ; } } export function newNamedType ( kind , name ) { switch ( kind ) { case 'ScalarType' : return new ScalarType ( name ) ; case 'ObjectType' : return new ObjectType ( name ) ; case 'InterfaceType' : return new InterfaceType ( name ) ; case 'UnionType' : return new UnionType ( name ) ; case 'EnumType' : return new EnumType ( name ) ; case 'InputObjectType' : return new InputObjectType ( name ) ; default : assert ( false , `Unhandled kind ${ kind } for type ${ name }` ) ; } } function * typesToCopy ( source , dest ) { for ( const type of source . builtInTypes ( ) ) { if ( ! type . isIntrospectionType ( ) && ! dest . type ( type . name ) ?. isBuiltIn ) { yield type ; } } yield * source . types ( ) ; } function * directivesToCopy ( source , dest ) { for ( const directive of source . builtInDirectives ( ) ) { if ( ! dest . directive ( directive . name ) ?. isBuiltIn ) { yield directive ; } } yield * source . directives ( ) ; } function copy ( source , dest ) { for ( const type of typesToCopy ( source , dest ) ) { dest . addType ( newNamedType ( type . kind , type . name ) ) ; } for ( const directive of directivesToCopy ( source , dest ) ) { copyDirectiveDefinitionInner ( directive , dest . addDirectiveDefinition ( directive . name ) ) ; } copySchemaDefinitionInner ( source . schemaDefinition , dest . schemaDefinition ) ; for ( const type of typesToCopy ( source , dest ) ) { copyNamedTypeInner ( type , dest . type ( type . name ) ! ) ; } } function copyExtensions < T extends ExtendableElement > ( source , dest ) { const extensionMap = new Map < Extension < T > , Extension < T > > ( ) ; for ( const sourceExtension of source . extensions ( ) ) { const destExtension = new Extension < T > ( ) ; dest . addExtension ( destExtension as any ) ; extensionMap . set ( sourceExtension as any , destExtension ) ; } return extensionMap ; } function copyOfExtension < T extends ExtendableElement > ( extensionsMap , source , dest ) { const toCopy = source . ofExtension ( ) ; if ( toCopy ) { dest . setOfExtension ( extensionsMap . get ( toCopy ) ) ; } } function copySchemaDefinitionInner ( source , dest ) { const extensionsMap = copyExtensions ( source , dest ) ; for ( const rootType of source . roots ( ) ) { copyOfExtension ( extensionsMap , rootType , dest . setRoot ( rootType . rootKind , rootType . type . name ) ) ; } for ( const directive of source . appliedDirectives ) { copyOfExtension ( extensionsMap , directive , dest . applyDirective ( directive . name , { ... directive . arguments ( ) } ) ) ; } dest . description = source . description ; dest . sourceAST = source . sourceAST ; } function copyNamedTypeInner ( source , dest ) { const extensionsMap = copyExtensions ( source , dest ) ; for ( const directive of source . appliedDirectives ) { copyOfExtension ( extensionsMap , directive , dest . applyDirective ( directive . name , { ... directive . arguments ( ) } ) ) ; } dest . description = source . description ; dest . sourceAST = source . sourceAST ; switch ( source . kind ) { case 'ObjectType' : case 'InterfaceType' : const destFieldBasedType = dest as FieldBasedType < any , any > ; for ( const sourceField of source . fields ( ) ) { const destField = destFieldBasedType . addField ( new FieldDefinition ( sourceField . name ) ) ; copyOfExtension ( extensionsMap , sourceField , destField ) ; copyFieldDefinitionInner ( sourceField , destField ) ; } for ( const sourceImpl of source . interfaceImplementations ( ) ) { const destImpl = destFieldBasedType . addImplementedInterface ( sourceImpl . interface . name ) ; copyOfExtension ( extensionsMap , sourceImpl , destImpl ) ; } break ; case 'UnionType' : const destUnionType = dest as UnionType ; for ( const sourceType of source . members ( ) ) { const destType = destUnionType . addType ( sourceType . type . name ) ; copyOfExtension ( extensionsMap , sourceType , destType ) ; } break ; case 'EnumType' : const destEnumType = dest as EnumType ; for ( const sourceValue of source . values ) { const destValue = destEnumType . addValue ( sourceValue . name ) ; destValue . description = sourceValue . description ; copyOfExtension ( extensionsMap , sourceValue , destValue ) ; copyAppliedDirectives ( sourceValue , destValue ) ; } break ; case 'InputObjectType' : const destInputType = dest as InputObjectType ; for ( const sourceField of source . fields ( ) ) { const destField = destInputType . addField ( new InputFieldDefinition ( sourceField . name ) ) ; copyOfExtension ( extensionsMap , sourceField , destField ) ; copyInputFieldDefinitionInner ( sourceField , destField ) ; } } } function copyAppliedDirectives ( source , dest ) { for ( const directive of source . appliedDirectives ) { dest . applyDirective ( directive . name , { ... directive . arguments ( ) } ) ; } } function copyFieldDefinitionInner < P extends ObjectType | InterfaceType > ( source , dest ) { const type = copyWrapperTypeOrTypeRef ( source . type , dest . schema ( ) ) as OutputType ; dest . type = type ; for ( const arg of source . arguments ( ) ) { const argType = copyWrapperTypeOrTypeRef ( arg . type , dest . schema ( ) ) ; copyArgumentDefinitionInner ( arg , dest . addArgument ( arg . name , argType as InputType ) ) ; } copyAppliedDirectives ( source , dest ) ; dest . description = source . description ; dest . sourceAST = source . sourceAST ; } function copyInputFieldDefinitionInner ( source , dest ) { const type = copyWrapperTypeOrTypeRef ( source . type , dest . schema ( ) ) as InputType ; dest . type = type ; dest . defaultValue = source . defaultValue ; copyAppliedDirectives ( source , dest ) ; dest . description = source . description ; dest . sourceAST = source . sourceAST ; } function copyWrapperTypeOrTypeRef ( source , destParent ) { if ( ! source ) { return undefined ; } switch ( source . kind ) { case 'ListType' : return new ListType ( copyWrapperTypeOrTypeRef ( source . ofType , destParent ) ! ) ; case 'NonNullType' : return new NonNullType ( copyWrapperTypeOrTypeRef ( source . ofType , destParent ) ! as NullableType ) ; default : return destParent . type ( source . name ) ! ; } } function copyArgumentDefinitionInner < P extends FieldDefinition < any > | DirectiveDefinition > ( source , dest ) { const type = copyWrapperTypeOrTypeRef ( source . type , dest . schema ( ) ) as InputType ; dest . type = type ; dest . defaultValue = source . defaultValue ; copyAppliedDirectives ( source , dest ) ; dest . description = source . description ; dest . sourceAST = source . sourceAST ; } function copyDirectiveDefinitionInner ( source , dest ) { for ( const arg of source . arguments ( ) ) { const type = copyWrapperTypeOrTypeRef ( arg . type , dest . schema ( ) ) ; copyArgumentDefinitionInner ( arg , dest . addArgument ( arg . name , type as InputType ) ) ; } dest . repeatable = source . repeatable ; dest . addLocations ( ... source . locations ) ; dest . sourceAST = source . sourceAST ; dest . description = source . description ; }