import { inspect } from '../jsutils/inspect.js';
import { instanceOf } from '../jsutils/instanceOf.js';
import type { Maybe } from '../jsutils/Maybe.js';
import type { ObjMap } from '../jsutils/ObjMap.js';
import { toObjMap } from '../jsutils/toObjMap.js';
import type { GraphQLError } from '../error/GraphQLError.js';
import type {
SchemaDefinitionNode,
SchemaExtensionNode,
} from '../language/ast.js';
import { OperationTypeNode } from '../language/ast.js';
import type {
GraphQLAbstractType,
GraphQLCompositeType,
GraphQLField,
GraphQLInterfaceType,
GraphQLNamedType,
GraphQLObjectType,
GraphQLType,
} from './definition.js';
import {
getNamedType,
isInputObjectType,
isInterfaceType,
isObjectType,
isUnionType,
} from './definition.js';
import type { GraphQLDirective } from './directives.js';
import { isDirective, specifiedDirectives } from './directives.js';
import {
__Schema,
SchemaMetaFieldDef,
TypeMetaFieldDef,
TypeNameMetaFieldDef,
} from './introspection.js';
export function isSchema(schema: unknown): schema is GraphQLSchema {
return instanceOf(schema, GraphQLSchema);
}
export function assertSchema(schema: unknown): GraphQLSchema {
if (!isSchema(schema)) {
throw new Error(`Expected ${inspect(schema)} to be a GraphQL schema.`);
}
return schema;
}
export interface GraphQLSchemaExtensions {
[attributeName: string]: unknown;
}
export class GraphQLSchema {
description: Maybe<string>;
extensions: Readonly<GraphQLSchemaExtensions>;
astNode: Maybe<SchemaDefinitionNode>;
extensionASTNodes: ReadonlyArray<SchemaExtensionNode>;
__validationErrors: Maybe<ReadonlyArray<GraphQLError>>;
private _queryType: Maybe<GraphQLObjectType>;
private _mutationType: Maybe<GraphQLObjectType>;
private _subscriptionType: Maybe<GraphQLObjectType>;
private _directives: ReadonlyArray<GraphQLDirective>;
private _typeMap: TypeMap;
private _subTypeMap: Map<
GraphQLAbstractType,
Set<GraphQLObjectType | GraphQLInterfaceType>
>;
private _implementationsMap: ObjMap<{
objects: Array<GraphQLObjectType>;
interfaces: Array<GraphQLInterfaceType>;
}>;
constructor(config: Readonly<GraphQLSchemaConfig>) {
this.__validationErrors = config.assumeValid === true ? [] : undefined;
this.description = config.description;
this.extensions = toObjMap(config.extensions);
this.astNode = config.astNode;
this.extensionASTNodes = config.extensionASTNodes ?? [];
this._queryType = config.query;
this._mutationType = config.mutation;
this._subscriptionType = config.subscription;
this._directives = config.directives ?? specifiedDirectives;
const allReferencedTypes = new Set<GraphQLNamedType>(config.types);
if (config.types != null) {
for (const type of config.types) {
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) {
if (isDirective(directive)) {
for (const arg of directive.args) {
collectReferencedTypes(arg.type, allReferencedTypes);
}
}
}
collectReferencedTypes(__Schema, allReferencedTypes);
this._typeMap = Object.create(null);
this._subTypeMap = new Map();
this._implementationsMap = Object.create(null);
for (const namedType of 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)) {
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)) {
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);
}
}
}
}
}
get [Symbol.toStringTag]() {
return 'GraphQLSchema';
}
getQueryType(): Maybe<GraphQLObjectType> {
return this._queryType;
}
getMutationType(): Maybe<GraphQLObjectType> {
return this._mutationType;
}
getSubscriptionType(): Maybe<GraphQLObjectType> {
return this._subscriptionType;
}
getRootType(operation: OperationTypeNode): Maybe<GraphQLObjectType> {
switch (operation) {
case OperationTypeNode.QUERY:
return this.getQueryType();
case OperationTypeNode.MUTATION:
return this.getMutationType();
case OperationTypeNode.SUBSCRIPTION:
return this.getSubscriptionType();
}
}
getTypeMap(): TypeMap {
return this._typeMap;
}
getType(name: string): GraphQLNamedType | undefined {
return this.getTypeMap()[name];
}
getPossibleTypes(
abstractType: GraphQLAbstractType,
): ReadonlyArray<GraphQLObjectType> {
return isUnionType(abstractType)
? abstractType.getTypes()
: this.getImplementations(abstractType).objects;
}
getImplementations(interfaceType: GraphQLInterfaceType): {
objects: ReadonlyArray<GraphQLObjectType>;
interfaces: ReadonlyArray<GraphQLInterfaceType>;
} {
const implementations = this._implementationsMap[interfaceType.name];
return implementations ?? { objects: [], interfaces: [] };
}
isSubType(
abstractType: GraphQLAbstractType,
maybeSubType: GraphQLObjectType | GraphQLInterfaceType,
): boolean {
let set = this._subTypeMap.get(abstractType);
if (set === undefined) {
if (isUnionType(abstractType)) {
set = new Set<GraphQLObjectType>(abstractType.getTypes());
} else {
const implementations = this.getImplementations(abstractType);
set = new Set<GraphQLObjectType | GraphQLInterfaceType>([
...implementations.objects,
...implementations.interfaces,
]);
}
this._subTypeMap.set(abstractType, set);
}
return set.has(maybeSubType);
}
getDirectives(): ReadonlyArray<GraphQLDirective> {
return this._directives;
}
getDirective(name: string): Maybe<GraphQLDirective> {
return this.getDirectives().find((directive) => directive.name === name);
}
getField(
parentType: GraphQLCompositeType,
fieldName: string,
): GraphQLField<unknown, unknown> | undefined {
switch (fieldName) {
case SchemaMetaFieldDef.name:
return this.getQueryType() === parentType
? SchemaMetaFieldDef
: undefined;
case TypeMetaFieldDef.name:
return this.getQueryType() === parentType
? TypeMetaFieldDef
: undefined;
case TypeNameMetaFieldDef.name:
return TypeNameMetaFieldDef;
}
if ('getFields' in parentType) {
return parentType.getFields()[fieldName];
}
return undefined;
}
toConfig(): GraphQLSchemaNormalizedConfig {
return {
description: this.description,
query: this.getQueryType(),
mutation: this.getMutationType(),
subscription: this.getSubscriptionType(),
types: Object.values(this.getTypeMap()),
directives: this.getDirectives(),
extensions: this.extensions,
astNode: this.astNode,
extensionASTNodes: this.extensionASTNodes,
assumeValid: this.__validationErrors !== undefined,
};
}
}
type TypeMap = ObjMap<GraphQLNamedType>;
export interface GraphQLSchemaValidationOptions {
assumeValid?: boolean | undefined;
}
export interface GraphQLSchemaConfig extends GraphQLSchemaValidationOptions {
description?: Maybe<string>;
query?: Maybe<GraphQLObjectType>;
mutation?: Maybe<GraphQLObjectType>;
subscription?: Maybe<GraphQLObjectType>;
types?: Maybe<ReadonlyArray<GraphQLNamedType>>;
directives?: Maybe<ReadonlyArray<GraphQLDirective>>;
extensions?: Maybe<Readonly<GraphQLSchemaExtensions>>;
astNode?: Maybe<SchemaDefinitionNode>;
extensionASTNodes?: Maybe<ReadonlyArray<SchemaExtensionNode>>;
}
export interface GraphQLSchemaNormalizedConfig extends GraphQLSchemaConfig {
description: Maybe<string>;
types: ReadonlyArray<GraphQLNamedType>;
directives: ReadonlyArray<GraphQLDirective>;
extensions: Readonly<GraphQLSchemaExtensions>;
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 Object.values(namedType.getFields())) {
collectReferencedTypes(field.type, typeSet);
for (const arg of field.args) {
collectReferencedTypes(arg.type, typeSet);
}
}
} else if (isInputObjectType(namedType)) {
for (const field of Object.values(namedType.getFields())) {
collectReferencedTypes(field.type, typeSet);
}
}
}
return typeSet;
}