diff options
Diffstat (limited to 'school/node_modules/graphql/utilities/extendSchema.js.flow')
-rw-r--r-- | school/node_modules/graphql/utilities/extendSchema.js.flow | 782 |
1 files changed, 782 insertions, 0 deletions
diff --git a/school/node_modules/graphql/utilities/extendSchema.js.flow b/school/node_modules/graphql/utilities/extendSchema.js.flow new file mode 100644 index 0000000..2d5a229 --- /dev/null +++ b/school/node_modules/graphql/utilities/extendSchema.js.flow @@ -0,0 +1,782 @@ +// @flow strict +import objectValues from '../polyfills/objectValues'; + +import keyMap from '../jsutils/keyMap'; +import inspect from '../jsutils/inspect'; +import mapValue from '../jsutils/mapValue'; +import invariant from '../jsutils/invariant'; +import devAssert from '../jsutils/devAssert'; + +import type { DirectiveLocationEnum } from '../language/directiveLocation'; +import type { + Location, + DocumentNode, + StringValueNode, + TypeNode, + NamedTypeNode, + SchemaDefinitionNode, + SchemaExtensionNode, + TypeDefinitionNode, + InterfaceTypeDefinitionNode, + InterfaceTypeExtensionNode, + ObjectTypeDefinitionNode, + ObjectTypeExtensionNode, + UnionTypeDefinitionNode, + UnionTypeExtensionNode, + FieldDefinitionNode, + InputObjectTypeDefinitionNode, + InputObjectTypeExtensionNode, + InputValueDefinitionNode, + EnumTypeDefinitionNode, + EnumTypeExtensionNode, + EnumValueDefinitionNode, + DirectiveDefinitionNode, + ScalarTypeDefinitionNode, + ScalarTypeExtensionNode, +} from '../language/ast'; +import { Kind } from '../language/kinds'; +import { TokenKind } from '../language/tokenKind'; +import { dedentBlockStringValue } from '../language/blockString'; +import { + isTypeDefinitionNode, + isTypeExtensionNode, +} from '../language/predicates'; + +import { assertValidSDLExtension } from '../validation/validate'; + +import { getDirectiveValues } from '../execution/values'; + +import type { + GraphQLSchemaValidationOptions, + GraphQLSchemaNormalizedConfig, +} from '../type/schema'; +import type { + GraphQLType, + GraphQLNamedType, + GraphQLFieldConfig, + GraphQLFieldConfigMap, + GraphQLArgumentConfig, + GraphQLFieldConfigArgumentMap, + GraphQLEnumValueConfigMap, + GraphQLInputFieldConfigMap, +} from '../type/definition'; +import { assertSchema, GraphQLSchema } from '../type/schema'; +import { specifiedScalarTypes, isSpecifiedScalarType } from '../type/scalars'; +import { introspectionTypes, isIntrospectionType } from '../type/introspection'; +import { + GraphQLDirective, + GraphQLDeprecatedDirective, + GraphQLSpecifiedByDirective, +} from '../type/directives'; +import { + isScalarType, + isObjectType, + isInterfaceType, + isUnionType, + isListType, + isNonNullType, + isEnumType, + isInputObjectType, + GraphQLList, + GraphQLNonNull, + GraphQLScalarType, + GraphQLObjectType, + GraphQLInterfaceType, + GraphQLUnionType, + GraphQLEnumType, + GraphQLInputObjectType, +} from '../type/definition'; + +import { valueFromAST } from './valueFromAST'; + +type Options = {| + ...GraphQLSchemaValidationOptions, + + /** + * Descriptions are defined as preceding string literals, however an older + * experimental version of the SDL supported preceding comments as + * descriptions. Set to true to enable this deprecated behavior. + * This option is provided to ease adoption and will be removed in v16. + * + * Default: false + */ + commentDescriptions?: boolean, + + /** + * 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. + * + * Accepts options as a third argument: + * + * - commentDescriptions: + * Provide true to use preceding comments as the description. + * + */ +export function extendSchema( + schema: GraphQLSchema, + documentAST: DocumentNode, + options?: Options, +): GraphQLSchema { + 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: GraphQLSchemaNormalizedConfig, + documentAST: DocumentNode, + options?: Options, +): GraphQLSchemaNormalizedConfig { + // Collect the type definitions and extensions found in the document. + const typeDefs: Array<TypeDefinitionNode> = []; + const typeExtensionsMap = Object.create(null); + + // New directives and types are separate because a directives and types can + // have the same name. For example, a type named "skip". + const directiveDefs: Array<DirectiveDefinitionNode> = []; + + let schemaDef: ?SchemaDefinitionNode; + // Schema extensions are collected which may add additional operation types. + const schemaExtensions: Array<SchemaExtensionNode> = []; + + 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 this document contains no new types, extensions, or directives then + // return the same unmodified GraphQLSchema instance. + 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 = { + // Get the extended root operation types. + query: schemaConfig.query && replaceNamedType(schemaConfig.query), + mutation: schemaConfig.mutation && replaceNamedType(schemaConfig.mutation), + subscription: + schemaConfig.subscription && replaceNamedType(schemaConfig.subscription), + // Then, incorporate schema definition and all schema extensions. + ...(schemaDef && getOperationTypes([schemaDef])), + ...getOperationTypes(schemaExtensions), + }; + + // Then produce and return a Schema config with these types. + return { + description: schemaDef?.description?.value, + ...operationTypes, + types: objectValues(typeMap), + directives: [ + ...schemaConfig.directives.map(replaceDirective), + ...directiveDefs.map(buildDirective), + ], + extensions: undefined, + astNode: schemaDef ?? schemaConfig.astNode, + extensionASTNodes: schemaConfig.extensionASTNodes.concat(schemaExtensions), + assumeValid: options?.assumeValid ?? false, + }; + + // Below are functions used for producing this schema that have closed over + // this scope and have access to the schema, cache, and newly defined types. + + function replaceType<T: GraphQLType>(type: T): T { + if (isListType(type)) { + // $FlowFixMe[incompatible-return] + return new GraphQLList(replaceType(type.ofType)); + } + if (isNonNullType(type)) { + // $FlowFixMe[incompatible-return] + return new GraphQLNonNull(replaceType(type.ofType)); + } + return replaceNamedType(type); + } + + function replaceNamedType<T: GraphQLNamedType>(type: T): T { + // Note: While this could make early assertions to get the correctly + // typed values, that would throw immediately while type system + // validation with validateSchema() will produce more actionable results. + return ((typeMap[type.name]: any): T); + } + + function replaceDirective(directive: GraphQLDirective): GraphQLDirective { + const config = directive.toConfig(); + return new GraphQLDirective({ + ...config, + args: mapValue(config.args, extendArg), + }); + } + + function extendNamedType(type: GraphQLNamedType): GraphQLNamedType { + if (isIntrospectionType(type) || isSpecifiedScalarType(type)) { + // Builtin types are not extended. + 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); + } + // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618') + if (isInputObjectType(type)) { + return extendInputObjectType(type); + } + + // istanbul ignore next (Not reachable. All possible types have been considered) + invariant(false, 'Unexpected type: ' + inspect((type: empty))); + } + + function extendInputObjectType( + type: GraphQLInputObjectType, + ): GraphQLInputObjectType { + 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: GraphQLEnumType): GraphQLEnumType { + 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: GraphQLScalarType): GraphQLScalarType { + 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: GraphQLObjectType): GraphQLObjectType { + 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: GraphQLInterfaceType, + ): GraphQLInterfaceType { + 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: GraphQLUnionType): GraphQLUnionType { + 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: GraphQLFieldConfig<mixed, mixed>, + ): GraphQLFieldConfig<mixed, mixed> { + return { + ...field, + type: replaceType(field.type), + // $FlowFixMe[incompatible-call] + args: mapValue(field.args, extendArg), + }; + } + + function extendArg(arg: GraphQLArgumentConfig) { + return { + ...arg, + type: replaceType(arg.type), + }; + } + + function getOperationTypes( + nodes: $ReadOnlyArray<SchemaDefinitionNode | SchemaExtensionNode>, + ): {| + query: ?GraphQLObjectType, + mutation: ?GraphQLObjectType, + subscription: ?GraphQLObjectType, + |} { + const opTypes = {}; + for (const node of nodes) { + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') + const operationTypesNodes = node.operationTypes ?? []; + + for (const operationType of operationTypesNodes) { + opTypes[operationType.operation] = getNamedType(operationType.type); + } + } + + // Note: While this could make early assertions to get the correctly + // typed values below, that would throw immediately while type system + // validation with validateSchema() will produce more actionable results. + return (opTypes: any); + } + + function getNamedType(node: NamedTypeNode): GraphQLNamedType { + 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: TypeNode): GraphQLType { + 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: DirectiveDefinitionNode): GraphQLDirective { + const locations = node.locations.map( + ({ value }) => ((value: any): DirectiveLocationEnum), + ); + + return new GraphQLDirective({ + name: node.name.value, + description: getDescription(node, options), + locations, + isRepeatable: node.repeatable, + args: buildArgumentMap(node.arguments), + astNode: node, + }); + } + + function buildFieldMap( + nodes: $ReadOnlyArray< + | InterfaceTypeDefinitionNode + | InterfaceTypeExtensionNode + | ObjectTypeDefinitionNode + | ObjectTypeExtensionNode, + >, + ): GraphQLFieldConfigMap<mixed, mixed> { + const fieldConfigMap = Object.create(null); + for (const node of nodes) { + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') + const nodeFields = node.fields ?? []; + + for (const field of nodeFields) { + fieldConfigMap[field.name.value] = { + // Note: While this could make assertions to get the correctly typed + // value, that would throw immediately while type system validation + // with validateSchema() will produce more actionable results. + type: (getWrappedType(field.type): any), + description: getDescription(field, options), + args: buildArgumentMap(field.arguments), + deprecationReason: getDeprecationReason(field), + astNode: field, + }; + } + } + return fieldConfigMap; + } + + function buildArgumentMap( + args: ?$ReadOnlyArray<InputValueDefinitionNode>, + ): GraphQLFieldConfigArgumentMap { + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') + const argsNodes = args ?? []; + + const argConfigMap = Object.create(null); + for (const arg of argsNodes) { + // Note: While this could make assertions to get the correctly typed + // value, that would throw immediately while type system validation + // with validateSchema() will produce more actionable results. + const type: any = getWrappedType(arg.type); + + argConfigMap[arg.name.value] = { + type, + description: getDescription(arg, options), + defaultValue: valueFromAST(arg.defaultValue, type), + deprecationReason: getDeprecationReason(arg), + astNode: arg, + }; + } + return argConfigMap; + } + + function buildInputFieldMap( + nodes: $ReadOnlyArray< + InputObjectTypeDefinitionNode | InputObjectTypeExtensionNode, + >, + ): GraphQLInputFieldConfigMap { + const inputFieldMap = Object.create(null); + for (const node of nodes) { + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') + const fieldsNodes = node.fields ?? []; + + for (const field of fieldsNodes) { + // Note: While this could make assertions to get the correctly typed + // value, that would throw immediately while type system validation + // with validateSchema() will produce more actionable results. + const type: any = getWrappedType(field.type); + + inputFieldMap[field.name.value] = { + type, + description: getDescription(field, options), + defaultValue: valueFromAST(field.defaultValue, type), + deprecationReason: getDeprecationReason(field), + astNode: field, + }; + } + } + return inputFieldMap; + } + + function buildEnumValueMap( + nodes: $ReadOnlyArray<EnumTypeDefinitionNode | EnumTypeExtensionNode>, + ): GraphQLEnumValueConfigMap { + const enumValueMap = Object.create(null); + for (const node of nodes) { + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') + const valuesNodes = node.values ?? []; + + for (const value of valuesNodes) { + enumValueMap[value.name.value] = { + description: getDescription(value, options), + deprecationReason: getDeprecationReason(value), + astNode: value, + }; + } + } + return enumValueMap; + } + + function buildInterfaces( + nodes: $ReadOnlyArray< + | InterfaceTypeDefinitionNode + | InterfaceTypeExtensionNode + | ObjectTypeDefinitionNode + | ObjectTypeExtensionNode, + >, + ): Array<GraphQLInterfaceType> { + const interfaces = []; + for (const node of nodes) { + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') + const interfacesNodes = node.interfaces ?? []; + + for (const type of interfacesNodes) { + // Note: While this could make assertions to get the correctly typed + // values below, that would throw immediately while type system + // validation with validateSchema() will produce more actionable + // results. + interfaces.push((getNamedType(type): any)); + } + } + return interfaces; + } + + function buildUnionTypes( + nodes: $ReadOnlyArray<UnionTypeDefinitionNode | UnionTypeExtensionNode>, + ): Array<GraphQLObjectType> { + const types = []; + for (const node of nodes) { + // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203') + const typeNodes = node.types ?? []; + + for (const type of typeNodes) { + // Note: While this could make assertions to get the correctly typed + // values below, that would throw immediately while type system + // validation with validateSchema() will produce more actionable + // results. + types.push((getNamedType(type): any)); + } + } + return types; + } + + function buildType(astNode: TypeDefinitionNode): GraphQLNamedType { + const name = astNode.name.value; + const description = getDescription(astNode, options); + const extensionNodes = typeExtensionsMap[name] ?? []; + + switch (astNode.kind) { + case Kind.OBJECT_TYPE_DEFINITION: { + const extensionASTNodes = (extensionNodes: any); + const allNodes = [astNode, ...extensionASTNodes]; + + return new GraphQLObjectType({ + name, + description, + interfaces: () => buildInterfaces(allNodes), + fields: () => buildFieldMap(allNodes), + astNode, + extensionASTNodes, + }); + } + case Kind.INTERFACE_TYPE_DEFINITION: { + const extensionASTNodes = (extensionNodes: any); + const allNodes = [astNode, ...extensionASTNodes]; + + return new GraphQLInterfaceType({ + name, + description, + interfaces: () => buildInterfaces(allNodes), + fields: () => buildFieldMap(allNodes), + astNode, + extensionASTNodes, + }); + } + case Kind.ENUM_TYPE_DEFINITION: { + const extensionASTNodes = (extensionNodes: any); + const allNodes = [astNode, ...extensionASTNodes]; + + return new GraphQLEnumType({ + name, + description, + values: buildEnumValueMap(allNodes), + astNode, + extensionASTNodes, + }); + } + case Kind.UNION_TYPE_DEFINITION: { + const extensionASTNodes = (extensionNodes: any); + const allNodes = [astNode, ...extensionASTNodes]; + + return new GraphQLUnionType({ + name, + description, + types: () => buildUnionTypes(allNodes), + astNode, + extensionASTNodes, + }); + } + case Kind.SCALAR_TYPE_DEFINITION: { + const extensionASTNodes = (extensionNodes: any); + + return new GraphQLScalarType({ + name, + description, + specifiedByUrl: getSpecifiedByUrl(astNode), + astNode, + extensionASTNodes, + }); + } + case Kind.INPUT_OBJECT_TYPE_DEFINITION: { + const extensionASTNodes = (extensionNodes: any); + const allNodes = [astNode, ...extensionASTNodes]; + + return new GraphQLInputObjectType({ + name, + description, + fields: () => buildInputFieldMap(allNodes), + astNode, + extensionASTNodes, + }); + } + } + + // istanbul ignore next (Not reachable. All possible type definition nodes have been considered) + invariant( + false, + 'Unexpected type definition node: ' + inspect((astNode: empty)), + ); + } +} + +const stdTypeMap = keyMap( + specifiedScalarTypes.concat(introspectionTypes), + (type) => type.name, +); + +/** + * Given a field or enum value node, returns the string value for the + * deprecation reason. + */ +function getDeprecationReason( + node: + | EnumValueDefinitionNode + | FieldDefinitionNode + | InputValueDefinitionNode, +): ?string { + const deprecated = getDirectiveValues(GraphQLDeprecatedDirective, node); + return (deprecated?.reason: any); +} + +/** + * Given a scalar node, returns the string value for the specifiedByUrl. + */ +function getSpecifiedByUrl( + node: ScalarTypeDefinitionNode | ScalarTypeExtensionNode, +): ?string { + const specifiedBy = getDirectiveValues(GraphQLSpecifiedByDirective, node); + return (specifiedBy?.url: any); +} + +/** + * Given an ast node, returns its string description. + * @deprecated: provided to ease adoption and will be removed in v16. + * + * Accepts options as a second argument: + * + * - commentDescriptions: + * Provide true to use preceding comments as the description. + * + */ +export function getDescription( + node: { +description?: StringValueNode, +loc?: Location, ... }, + options: ?{ commentDescriptions?: boolean, ... }, +): void | string { + if (node.description) { + return node.description.value; + } + if (options?.commentDescriptions === true) { + const rawValue = getLeadingCommentBlock(node); + if (rawValue !== undefined) { + return dedentBlockStringValue('\n' + rawValue); + } + } +} + +function getLeadingCommentBlock(node): void | string { + const loc = node.loc; + if (!loc) { + return; + } + const comments = []; + let token = loc.startToken.prev; + while ( + token != null && + token.kind === TokenKind.COMMENT && + token.next && + token.prev && + token.line + 1 === token.next.line && + token.line !== token.prev.line + ) { + const value = String(token.value); + comments.push(value); + token = token.prev; + } + return comments.length > 0 ? comments.reverse().join('\n') : undefined; +} |