UNPKG

12 kBJavaScriptView Raw
1import { devAssert } from '../jsutils/devAssert.mjs';
2import { inspect } from '../jsutils/inspect.mjs';
3import { isObjectLike } from '../jsutils/isObjectLike.mjs';
4import { keyValMap } from '../jsutils/keyValMap.mjs';
5import { parseValue } from '../language/parser.mjs';
6import {
7 assertInterfaceType,
8 assertNullableType,
9 assertObjectType,
10 GraphQLEnumType,
11 GraphQLInputObjectType,
12 GraphQLInterfaceType,
13 GraphQLList,
14 GraphQLNonNull,
15 GraphQLObjectType,
16 GraphQLScalarType,
17 GraphQLUnionType,
18 isInputType,
19 isOutputType,
20} from '../type/definition.mjs';
21import { GraphQLDirective } from '../type/directives.mjs';
22import { introspectionTypes, TypeKind } from '../type/introspection.mjs';
23import { specifiedScalarTypes } from '../type/scalars.mjs';
24import { GraphQLSchema } from '../type/schema.mjs';
25import { valueFromAST } from './valueFromAST.mjs';
26/**
27 * Build a GraphQLSchema for use by client tools.
28 *
29 * Given the result of a client running the introspection query, creates and
30 * returns a GraphQLSchema instance which can be then used with all graphql-js
31 * tools, but cannot be used to execute a query, as introspection does not
32 * represent the "resolver", "parse" or "serialize" functions or any other
33 * server-internal mechanisms.
34 *
35 * This function expects a complete introspection result. Don't forget to check
36 * the "errors" field of a server response before calling this function.
37 */
38
39export function buildClientSchema(introspection, options) {
40 (isObjectLike(introspection) && isObjectLike(introspection.__schema)) ||
41 devAssert(
42 false,
43 `Invalid or incomplete introspection result. Ensure that you are passing "data" property of introspection response and no "errors" was returned alongside: ${inspect(
44 introspection,
45 )}.`,
46 ); // Get the schema from the introspection result.
47
48 const schemaIntrospection = introspection.__schema; // Iterate through all types, getting the type definition for each.
49
50 const typeMap = keyValMap(
51 schemaIntrospection.types,
52 (typeIntrospection) => typeIntrospection.name,
53 (typeIntrospection) => buildType(typeIntrospection),
54 ); // Include standard types only if they are used.
55
56 for (const stdType of [...specifiedScalarTypes, ...introspectionTypes]) {
57 if (typeMap[stdType.name]) {
58 typeMap[stdType.name] = stdType;
59 }
60 } // Get the root Query, Mutation, and Subscription types.
61
62 const queryType = schemaIntrospection.queryType
63 ? getObjectType(schemaIntrospection.queryType)
64 : null;
65 const mutationType = schemaIntrospection.mutationType
66 ? getObjectType(schemaIntrospection.mutationType)
67 : null;
68 const subscriptionType = schemaIntrospection.subscriptionType
69 ? getObjectType(schemaIntrospection.subscriptionType)
70 : null; // Get the directives supported by Introspection, assuming empty-set if
71 // directives were not queried for.
72
73 const directives = schemaIntrospection.directives
74 ? schemaIntrospection.directives.map(buildDirective)
75 : []; // Then produce and return a Schema with these types.
76
77 return new GraphQLSchema({
78 description: schemaIntrospection.description,
79 query: queryType,
80 mutation: mutationType,
81 subscription: subscriptionType,
82 types: Object.values(typeMap),
83 directives,
84 assumeValid:
85 options === null || options === void 0 ? void 0 : options.assumeValid,
86 }); // Given a type reference in introspection, return the GraphQLType instance.
87 // preferring cached instances before building new instances.
88
89 function getType(typeRef) {
90 if (typeRef.kind === TypeKind.LIST) {
91 const itemRef = typeRef.ofType;
92
93 if (!itemRef) {
94 throw new Error('Decorated type deeper than introspection query.');
95 }
96
97 return new GraphQLList(getType(itemRef));
98 }
99
100 if (typeRef.kind === TypeKind.NON_NULL) {
101 const nullableRef = typeRef.ofType;
102
103 if (!nullableRef) {
104 throw new Error('Decorated type deeper than introspection query.');
105 }
106
107 const nullableType = getType(nullableRef);
108 return new GraphQLNonNull(assertNullableType(nullableType));
109 }
110
111 return getNamedType(typeRef);
112 }
113
114 function getNamedType(typeRef) {
115 const typeName = typeRef.name;
116
117 if (!typeName) {
118 throw new Error(`Unknown type reference: ${inspect(typeRef)}.`);
119 }
120
121 const type = typeMap[typeName];
122
123 if (!type) {
124 throw new Error(
125 `Invalid or incomplete schema, unknown type: ${typeName}. Ensure that a full introspection query is used in order to build a client schema.`,
126 );
127 }
128
129 return type;
130 }
131
132 function getObjectType(typeRef) {
133 return assertObjectType(getNamedType(typeRef));
134 }
135
136 function getInterfaceType(typeRef) {
137 return assertInterfaceType(getNamedType(typeRef));
138 } // Given a type's introspection result, construct the correct
139 // GraphQLType instance.
140
141 function buildType(type) {
142 // eslint-disable-next-line @typescript-eslint/prefer-optional-chain
143 if (type != null && type.name != null && type.kind != null) {
144 // FIXME: Properly type IntrospectionType, it's a breaking change so fix in v17
145 // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
146 switch (type.kind) {
147 case TypeKind.SCALAR:
148 return buildScalarDef(type);
149
150 case TypeKind.OBJECT:
151 return buildObjectDef(type);
152
153 case TypeKind.INTERFACE:
154 return buildInterfaceDef(type);
155
156 case TypeKind.UNION:
157 return buildUnionDef(type);
158
159 case TypeKind.ENUM:
160 return buildEnumDef(type);
161
162 case TypeKind.INPUT_OBJECT:
163 return buildInputObjectDef(type);
164 }
165 }
166
167 const typeStr = inspect(type);
168 throw new Error(
169 `Invalid or incomplete introspection result. Ensure that a full introspection query is used in order to build a client schema: ${typeStr}.`,
170 );
171 }
172
173 function buildScalarDef(scalarIntrospection) {
174 return new GraphQLScalarType({
175 name: scalarIntrospection.name,
176 description: scalarIntrospection.description,
177 specifiedByURL: scalarIntrospection.specifiedByURL,
178 });
179 }
180
181 function buildImplementationsList(implementingIntrospection) {
182 // TODO: Temporary workaround until GraphQL ecosystem will fully support
183 // 'interfaces' on interface types.
184 if (
185 implementingIntrospection.interfaces === null &&
186 implementingIntrospection.kind === TypeKind.INTERFACE
187 ) {
188 return [];
189 }
190
191 if (!implementingIntrospection.interfaces) {
192 const implementingIntrospectionStr = inspect(implementingIntrospection);
193 throw new Error(
194 `Introspection result missing interfaces: ${implementingIntrospectionStr}.`,
195 );
196 }
197
198 return implementingIntrospection.interfaces.map(getInterfaceType);
199 }
200
201 function buildObjectDef(objectIntrospection) {
202 return new GraphQLObjectType({
203 name: objectIntrospection.name,
204 description: objectIntrospection.description,
205 interfaces: () => buildImplementationsList(objectIntrospection),
206 fields: () => buildFieldDefMap(objectIntrospection),
207 });
208 }
209
210 function buildInterfaceDef(interfaceIntrospection) {
211 return new GraphQLInterfaceType({
212 name: interfaceIntrospection.name,
213 description: interfaceIntrospection.description,
214 interfaces: () => buildImplementationsList(interfaceIntrospection),
215 fields: () => buildFieldDefMap(interfaceIntrospection),
216 });
217 }
218
219 function buildUnionDef(unionIntrospection) {
220 if (!unionIntrospection.possibleTypes) {
221 const unionIntrospectionStr = inspect(unionIntrospection);
222 throw new Error(
223 `Introspection result missing possibleTypes: ${unionIntrospectionStr}.`,
224 );
225 }
226
227 return new GraphQLUnionType({
228 name: unionIntrospection.name,
229 description: unionIntrospection.description,
230 types: () => unionIntrospection.possibleTypes.map(getObjectType),
231 });
232 }
233
234 function buildEnumDef(enumIntrospection) {
235 if (!enumIntrospection.enumValues) {
236 const enumIntrospectionStr = inspect(enumIntrospection);
237 throw new Error(
238 `Introspection result missing enumValues: ${enumIntrospectionStr}.`,
239 );
240 }
241
242 return new GraphQLEnumType({
243 name: enumIntrospection.name,
244 description: enumIntrospection.description,
245 values: keyValMap(
246 enumIntrospection.enumValues,
247 (valueIntrospection) => valueIntrospection.name,
248 (valueIntrospection) => ({
249 description: valueIntrospection.description,
250 deprecationReason: valueIntrospection.deprecationReason,
251 }),
252 ),
253 });
254 }
255
256 function buildInputObjectDef(inputObjectIntrospection) {
257 if (!inputObjectIntrospection.inputFields) {
258 const inputObjectIntrospectionStr = inspect(inputObjectIntrospection);
259 throw new Error(
260 `Introspection result missing inputFields: ${inputObjectIntrospectionStr}.`,
261 );
262 }
263
264 return new GraphQLInputObjectType({
265 name: inputObjectIntrospection.name,
266 description: inputObjectIntrospection.description,
267 fields: () => buildInputValueDefMap(inputObjectIntrospection.inputFields),
268 });
269 }
270
271 function buildFieldDefMap(typeIntrospection) {
272 if (!typeIntrospection.fields) {
273 throw new Error(
274 `Introspection result missing fields: ${inspect(typeIntrospection)}.`,
275 );
276 }
277
278 return keyValMap(
279 typeIntrospection.fields,
280 (fieldIntrospection) => fieldIntrospection.name,
281 buildField,
282 );
283 }
284
285 function buildField(fieldIntrospection) {
286 const type = getType(fieldIntrospection.type);
287
288 if (!isOutputType(type)) {
289 const typeStr = inspect(type);
290 throw new Error(
291 `Introspection must provide output type for fields, but received: ${typeStr}.`,
292 );
293 }
294
295 if (!fieldIntrospection.args) {
296 const fieldIntrospectionStr = inspect(fieldIntrospection);
297 throw new Error(
298 `Introspection result missing field args: ${fieldIntrospectionStr}.`,
299 );
300 }
301
302 return {
303 description: fieldIntrospection.description,
304 deprecationReason: fieldIntrospection.deprecationReason,
305 type,
306 args: buildInputValueDefMap(fieldIntrospection.args),
307 };
308 }
309
310 function buildInputValueDefMap(inputValueIntrospections) {
311 return keyValMap(
312 inputValueIntrospections,
313 (inputValue) => inputValue.name,
314 buildInputValue,
315 );
316 }
317
318 function buildInputValue(inputValueIntrospection) {
319 const type = getType(inputValueIntrospection.type);
320
321 if (!isInputType(type)) {
322 const typeStr = inspect(type);
323 throw new Error(
324 `Introspection must provide input type for arguments, but received: ${typeStr}.`,
325 );
326 }
327
328 const defaultValue =
329 inputValueIntrospection.defaultValue != null
330 ? valueFromAST(parseValue(inputValueIntrospection.defaultValue), type)
331 : undefined;
332 return {
333 description: inputValueIntrospection.description,
334 type,
335 defaultValue,
336 deprecationReason: inputValueIntrospection.deprecationReason,
337 };
338 }
339
340 function buildDirective(directiveIntrospection) {
341 if (!directiveIntrospection.args) {
342 const directiveIntrospectionStr = inspect(directiveIntrospection);
343 throw new Error(
344 `Introspection result missing directive args: ${directiveIntrospectionStr}.`,
345 );
346 }
347
348 if (!directiveIntrospection.locations) {
349 const directiveIntrospectionStr = inspect(directiveIntrospection);
350 throw new Error(
351 `Introspection result missing directive locations: ${directiveIntrospectionStr}.`,
352 );
353 }
354
355 return new GraphQLDirective({
356 name: directiveIntrospection.name,
357 description: directiveIntrospection.description,
358 isRepeatable: directiveIntrospection.isRepeatable,
359 locations: directiveIntrospection.locations.slice(),
360 args: buildInputValueDefMap(directiveIntrospection.args),
361 });
362 }
363}