| // @flow strict |
| |
| import find from '../polyfills/find'; |
| import arrayFrom from '../polyfills/arrayFrom'; |
| import objectValues from '../polyfills/objectValues'; |
| import { SYMBOL_TO_STRING_TAG } from '../polyfills/symbols'; |
| |
| import inspect from '../jsutils/inspect'; |
| import toObjMap from '../jsutils/toObjMap'; |
| import devAssert from '../jsutils/devAssert'; |
| import instanceOf from '../jsutils/instanceOf'; |
| import isObjectLike from '../jsutils/isObjectLike'; |
| import { |
| type ObjMap, |
| type ReadOnlyObjMap, |
| type ReadOnlyObjMapLike, |
| } from '../jsutils/ObjMap'; |
| |
| import { type GraphQLError } from '../error/GraphQLError'; |
| import { |
| type SchemaDefinitionNode, |
| type SchemaExtensionNode, |
| } from '../language/ast'; |
| |
| import { __Schema } from './introspection'; |
| import { |
| GraphQLDirective, |
| isDirective, |
| specifiedDirectives, |
| } from './directives'; |
| import { |
| type GraphQLType, |
| type GraphQLNamedType, |
| type GraphQLAbstractType, |
| type GraphQLObjectType, |
| type GraphQLInterfaceType, |
| isObjectType, |
| isInterfaceType, |
| isUnionType, |
| isInputObjectType, |
| getNamedType, |
| } from './definition'; |
| |
| /** |
| * Test if the given value is a GraphQL schema. |
| */ |
| declare function isSchema(schema: mixed): boolean %checks(schema instanceof |
| GraphQLSchema); |
| // eslint-disable-next-line no-redeclare |
| export function isSchema(schema) { |
| return instanceOf(schema, GraphQLSchema); |
| } |
| |
| export function assertSchema(schema: mixed): GraphQLSchema { |
| if (!isSchema(schema)) { |
| throw new Error(`Expected ${inspect(schema)} to be a GraphQL schema.`); |
| } |
| return schema; |
| } |
| |
| /** |
| * Schema Definition |
| * |
| * A Schema is created by supplying the root types of each type of operation, |
| * query and mutation (optional). A schema definition is then supplied to the |
| * validator and executor. |
| * |
| * Example: |
| * |
| * const MyAppSchema = new GraphQLSchema({ |
| * query: MyAppQueryRootType, |
| * mutation: MyAppMutationRootType, |
| * }) |
| * |
| * Note: When the schema is constructed, by default only the types that are |
| * reachable by traversing the root types are included, other types must be |
| * explicitly referenced. |
| * |
| * Example: |
| * |
| * const characterInterface = new GraphQLInterfaceType({ |
| * name: 'Character', |
| * ... |
| * }); |
| * |
| * const humanType = new GraphQLObjectType({ |
| * name: 'Human', |
| * interfaces: [characterInterface], |
| * ... |
| * }); |
| * |
| * const droidType = new GraphQLObjectType({ |
| * name: 'Droid', |
| * interfaces: [characterInterface], |
| * ... |
| * }); |
| * |
| * const schema = new GraphQLSchema({ |
| * query: new GraphQLObjectType({ |
| * name: 'Query', |
| * fields: { |
| * hero: { type: characterInterface, ... }, |
| * } |
| * }), |
| * ... |
| * // Since this schema references only the `Character` interface it's |
| * // necessary to explicitly list the types that implement it if |
| * // you want them to be included in the final schema. |
| * types: [humanType, droidType], |
| * }) |
| * |
| * Note: If an array of `directives` are provided to GraphQLSchema, that will be |
| * the exact list of directives represented and allowed. If `directives` is not |
| * provided then a default set of the specified directives (e.g. @include and |
| * @skip) will be used. If you wish to provide *additional* directives to these |
| * specified directives, you must explicitly declare them. Example: |
| * |
| * const MyAppSchema = new GraphQLSchema({ |
| * ... |
| * directives: specifiedDirectives.concat([ myCustomDirective ]), |
| * }) |
| * |
| */ |
| export class GraphQLSchema { |
| description: ?string; |
| extensions: ?ReadOnlyObjMap<mixed>; |
| astNode: ?SchemaDefinitionNode; |
| extensionASTNodes: ?$ReadOnlyArray<SchemaExtensionNode>; |
| |
| _queryType: ?GraphQLObjectType; |
| _mutationType: ?GraphQLObjectType; |
| _subscriptionType: ?GraphQLObjectType; |
| _directives: $ReadOnlyArray<GraphQLDirective>; |
| _typeMap: TypeMap; |
| _subTypeMap: ObjMap<ObjMap<boolean>>; |
| _implementationsMap: ObjMap<{| |
| objects: Array<GraphQLObjectType>, |
| interfaces: Array<GraphQLInterfaceType>, |
| |}>; |
| |
| // Used as a cache for validateSchema(). |
| __validationErrors: ?$ReadOnlyArray<GraphQLError>; |
| |
| constructor(config: $ReadOnly<GraphQLSchemaConfig>): void { |
| // If this schema was built from a source known to be valid, then it may be |
| // marked with assumeValid to avoid an additional type system validation. |
| this.__validationErrors = config.assumeValid === true ? [] : undefined; |
| |
| // Check for common mistakes during construction to produce early errors. |
| devAssert(isObjectLike(config), 'Must provide configuration object.'); |
| devAssert( |
| !config.types || Array.isArray(config.types), |
| `"types" must be Array if provided but got: ${inspect(config.types)}.`, |
| ); |
| devAssert( |
| !config.directives || Array.isArray(config.directives), |
| '"directives" must be Array if provided but got: ' + |
| `${inspect(config.directives)}.`, |
| ); |
| |
| this.description = config.description; |
| this.extensions = config.extensions && toObjMap(config.extensions); |
| this.astNode = config.astNode; |
| this.extensionASTNodes = config.extensionASTNodes; |
| |
| this._queryType = config.query; |
| this._mutationType = config.mutation; |
| this._subscriptionType = config.subscription; |
| // Provide specified directives (e.g. @include and @skip) by default. |
| this._directives = config.directives ?? specifiedDirectives; |
| |
| // To preserve order of user-provided types, we add first to add them to |
| // the set of "collected" types, so `collectReferencedTypes` ignore them. |
| const allReferencedTypes: Set<GraphQLNamedType> = new Set(config.types); |
| if (config.types != null) { |
| for (const type of config.types) { |
| // When we ready to process this type, we remove it from "collected" types |
| // and then add it together with all dependent types in the correct position. |
| allReferencedTypes.delete(type); |
| collectReferencedTypes(type, allReferencedTypes); |
| } |
| } |
| |
| if (this._queryType != null) { |
| collectReferencedTypes(this._queryType, allReferencedTypes); |
| } |
| if (this._mutationType != null) { |
| collectReferencedTypes(this._mutationType, allReferencedTypes); |
| } |
| if (this._subscriptionType != null) { |
| collectReferencedTypes(this._subscriptionType, allReferencedTypes); |
| } |
| |
| for (const directive of this._directives) { |
| // Directives are not validated until validateSchema() is called. |
| if (isDirective(directive)) { |
| for (const arg of directive.args) { |
| collectReferencedTypes(arg.type, allReferencedTypes); |
| } |
| } |
| } |
| collectReferencedTypes(__Schema, allReferencedTypes); |
| |
| // Storing the resulting map for reference by the schema. |
| this._typeMap = Object.create(null); |
| this._subTypeMap = Object.create(null); |
| // Keep track of all implementations by interface name. |
| this._implementationsMap = Object.create(null); |
| |
| for (const namedType of arrayFrom(allReferencedTypes)) { |
| if (namedType == null) { |
| continue; |
| } |
| |
| const typeName = namedType.name; |
| if (this._typeMap[typeName] !== undefined) { |
| throw new Error( |
| `Schema must contain uniquely named types but contains multiple types named "${typeName}".`, |
| ); |
| } |
| this._typeMap[typeName] = namedType; |
| |
| if (isInterfaceType(namedType)) { |
| // Store implementations by interface. |
| for (const iface of namedType.getInterfaces()) { |
| if (isInterfaceType(iface)) { |
| let implementations = this._implementationsMap[iface.name]; |
| if (implementations === undefined) { |
| implementations = this._implementationsMap[iface.name] = { |
| objects: [], |
| interfaces: [], |
| }; |
| } |
| |
| implementations.interfaces.push(namedType); |
| } |
| } |
| } else if (isObjectType(namedType)) { |
| // Store implementations by objects. |
| for (const iface of namedType.getInterfaces()) { |
| if (isInterfaceType(iface)) { |
| let implementations = this._implementationsMap[iface.name]; |
| if (implementations === undefined) { |
| implementations = this._implementationsMap[iface.name] = { |
| objects: [], |
| interfaces: [], |
| }; |
| } |
| |
| implementations.objects.push(namedType); |
| } |
| } |
| } |
| } |
| } |
| |
| getQueryType(): ?GraphQLObjectType { |
| return this._queryType; |
| } |
| |
| getMutationType(): ?GraphQLObjectType { |
| return this._mutationType; |
| } |
| |
| getSubscriptionType(): ?GraphQLObjectType { |
| return this._subscriptionType; |
| } |
| |
| getTypeMap(): TypeMap { |
| return this._typeMap; |
| } |
| |
| getType(name: string): ?GraphQLNamedType { |
| return this.getTypeMap()[name]; |
| } |
| |
| getPossibleTypes( |
| abstractType: GraphQLAbstractType, |
| ): $ReadOnlyArray<GraphQLObjectType> { |
| return isUnionType(abstractType) |
| ? abstractType.getTypes() |
| : this.getImplementations(abstractType).objects; |
| } |
| |
| getImplementations( |
| interfaceType: GraphQLInterfaceType, |
| ): {| |
| objects: /* $ReadOnly */ Array<GraphQLObjectType>, |
| interfaces: /* $ReadOnly */ Array<GraphQLInterfaceType>, |
| |} { |
| const implementations = this._implementationsMap[interfaceType.name]; |
| return implementations ?? { objects: [], interfaces: [] }; |
| } |
| |
| // @deprecated: use isSubType instead - will be removed in v16. |
| isPossibleType( |
| abstractType: GraphQLAbstractType, |
| possibleType: GraphQLObjectType, |
| ): boolean { |
| return this.isSubType(abstractType, possibleType); |
| } |
| |
| isSubType( |
| abstractType: GraphQLAbstractType, |
| maybeSubType: GraphQLObjectType | GraphQLInterfaceType, |
| ): boolean { |
| let map = this._subTypeMap[abstractType.name]; |
| if (map === undefined) { |
| map = Object.create(null); |
| |
| if (isUnionType(abstractType)) { |
| for (const type of abstractType.getTypes()) { |
| map[type.name] = true; |
| } |
| } else { |
| const implementations = this.getImplementations(abstractType); |
| for (const type of implementations.objects) { |
| map[type.name] = true; |
| } |
| for (const type of implementations.interfaces) { |
| map[type.name] = true; |
| } |
| } |
| |
| this._subTypeMap[abstractType.name] = map; |
| } |
| return map[maybeSubType.name] !== undefined; |
| } |
| |
| getDirectives(): $ReadOnlyArray<GraphQLDirective> { |
| return this._directives; |
| } |
| |
| getDirective(name: string): ?GraphQLDirective { |
| return find(this.getDirectives(), directive => directive.name === name); |
| } |
| |
| toConfig(): GraphQLSchemaNormalizedConfig { |
| return { |
| description: this.description, |
| query: this.getQueryType(), |
| mutation: this.getMutationType(), |
| subscription: this.getSubscriptionType(), |
| types: objectValues(this.getTypeMap()), |
| directives: this.getDirectives().slice(), |
| extensions: this.extensions, |
| astNode: this.astNode, |
| extensionASTNodes: this.extensionASTNodes ?? [], |
| assumeValid: this.__validationErrors !== undefined, |
| }; |
| } |
| |
| // $FlowFixMe Flow doesn't support computed properties yet |
| get [SYMBOL_TO_STRING_TAG]() { |
| return 'GraphQLSchema'; |
| } |
| } |
| |
| type TypeMap = ObjMap<GraphQLNamedType>; |
| |
| export type GraphQLSchemaValidationOptions = {| |
| /** |
| * When building a schema from a GraphQL service's introspection result, it |
| * might be safe to assume the schema is valid. Set to true to assume the |
| * produced schema is valid. |
| * |
| * Default: false |
| */ |
| assumeValid?: boolean, |
| |}; |
| |
| export type GraphQLSchemaConfig = {| |
| description?: ?string, |
| query?: ?GraphQLObjectType, |
| mutation?: ?GraphQLObjectType, |
| subscription?: ?GraphQLObjectType, |
| types?: ?Array<GraphQLNamedType>, |
| directives?: ?Array<GraphQLDirective>, |
| extensions?: ?ReadOnlyObjMapLike<mixed>, |
| astNode?: ?SchemaDefinitionNode, |
| extensionASTNodes?: ?$ReadOnlyArray<SchemaExtensionNode>, |
| ...GraphQLSchemaValidationOptions, |
| |}; |
| |
| /** |
| * @internal |
| */ |
| export type GraphQLSchemaNormalizedConfig = {| |
| ...GraphQLSchemaConfig, |
| description: ?string, |
| types: Array<GraphQLNamedType>, |
| directives: Array<GraphQLDirective>, |
| extensions: ?ReadOnlyObjMap<mixed>, |
| extensionASTNodes: $ReadOnlyArray<SchemaExtensionNode>, |
| assumeValid: boolean, |
| |}; |
| |
| function collectReferencedTypes( |
| type: GraphQLType, |
| typeSet: Set<GraphQLNamedType>, |
| ): Set<GraphQLNamedType> { |
| const namedType = getNamedType(type); |
| |
| if (!typeSet.has(namedType)) { |
| typeSet.add(namedType); |
| if (isUnionType(namedType)) { |
| for (const memberType of namedType.getTypes()) { |
| collectReferencedTypes(memberType, typeSet); |
| } |
| } else if (isObjectType(namedType) || isInterfaceType(namedType)) { |
| for (const interfaceType of namedType.getInterfaces()) { |
| collectReferencedTypes(interfaceType, typeSet); |
| } |
| |
| for (const field of objectValues(namedType.getFields())) { |
| collectReferencedTypes(field.type, typeSet); |
| for (const arg of field.args) { |
| collectReferencedTypes(arg.type, typeSet); |
| } |
| } |
| } else if (isInputObjectType(namedType)) { |
| for (const field of objectValues(namedType.getFields())) { |
| collectReferencedTypes(field.type, typeSet); |
| } |
| } |
| } |
| |
| return typeSet; |
| } |