UNPKG

15.2 kBJavaScriptView Raw
1import { GraphQLDeprecatedDirective, isEnumType, isInputObjectType, isInterfaceType, isIntrospectionType, isObjectType, isScalarType, isSpecifiedDirective, isSpecifiedScalarType, isUnionType, Kind, print, } from 'graphql';
2import { astFromType } from './astFromType.js';
3import { astFromValue } from './astFromValue.js';
4import { astFromValueUntyped } from './astFromValueUntyped.js';
5import { getDescriptionNode } from './descriptionFromObject.js';
6import { getDirectivesInExtensions, } from './get-directives.js';
7import { isSome } from './helpers.js';
8import { getRootTypeMap } from './rootTypes.js';
9export function getDocumentNodeFromSchema(schema, options = {}) {
10 const pathToDirectivesInExtensions = options.pathToDirectivesInExtensions;
11 const typesMap = schema.getTypeMap();
12 const schemaNode = astFromSchema(schema, pathToDirectivesInExtensions);
13 const definitions = schemaNode != null ? [schemaNode] : [];
14 const directives = schema.getDirectives();
15 for (const directive of directives) {
16 if (isSpecifiedDirective(directive)) {
17 continue;
18 }
19 definitions.push(astFromDirective(directive, schema, pathToDirectivesInExtensions));
20 }
21 for (const typeName in typesMap) {
22 const type = typesMap[typeName];
23 const isPredefinedScalar = isSpecifiedScalarType(type);
24 const isIntrospection = isIntrospectionType(type);
25 if (isPredefinedScalar || isIntrospection) {
26 continue;
27 }
28 if (isObjectType(type)) {
29 definitions.push(astFromObjectType(type, schema, pathToDirectivesInExtensions));
30 }
31 else if (isInterfaceType(type)) {
32 definitions.push(astFromInterfaceType(type, schema, pathToDirectivesInExtensions));
33 }
34 else if (isUnionType(type)) {
35 definitions.push(astFromUnionType(type, schema, pathToDirectivesInExtensions));
36 }
37 else if (isInputObjectType(type)) {
38 definitions.push(astFromInputObjectType(type, schema, pathToDirectivesInExtensions));
39 }
40 else if (isEnumType(type)) {
41 definitions.push(astFromEnumType(type, schema, pathToDirectivesInExtensions));
42 }
43 else if (isScalarType(type)) {
44 definitions.push(astFromScalarType(type, schema, pathToDirectivesInExtensions));
45 }
46 else {
47 throw new Error(`Unknown type ${type}.`);
48 }
49 }
50 return {
51 kind: Kind.DOCUMENT,
52 definitions,
53 };
54}
55// this approach uses the default schema printer rather than a custom solution, so may be more backwards compatible
56// currently does not allow customization of printSchema options having to do with comments.
57export function printSchemaWithDirectives(schema, options = {}) {
58 const documentNode = getDocumentNodeFromSchema(schema, options);
59 return print(documentNode);
60}
61export function astFromSchema(schema, pathToDirectivesInExtensions) {
62 const operationTypeMap = new Map([
63 ['query', undefined],
64 ['mutation', undefined],
65 ['subscription', undefined],
66 ]);
67 const nodes = [];
68 if (schema.astNode != null) {
69 nodes.push(schema.astNode);
70 }
71 if (schema.extensionASTNodes != null) {
72 for (const extensionASTNode of schema.extensionASTNodes) {
73 nodes.push(extensionASTNode);
74 }
75 }
76 for (const node of nodes) {
77 if (node.operationTypes) {
78 for (const operationTypeDefinitionNode of node.operationTypes) {
79 operationTypeMap.set(operationTypeDefinitionNode.operation, operationTypeDefinitionNode);
80 }
81 }
82 }
83 const rootTypeMap = getRootTypeMap(schema);
84 for (const [operationTypeNode, operationTypeDefinitionNode] of operationTypeMap) {
85 const rootType = rootTypeMap.get(operationTypeNode);
86 if (rootType != null) {
87 const rootTypeAST = astFromType(rootType);
88 if (operationTypeDefinitionNode != null) {
89 operationTypeDefinitionNode.type = rootTypeAST;
90 }
91 else {
92 operationTypeMap.set(operationTypeNode, {
93 kind: Kind.OPERATION_TYPE_DEFINITION,
94 operation: operationTypeNode,
95 type: rootTypeAST,
96 });
97 }
98 }
99 }
100 const operationTypes = [...operationTypeMap.values()].filter(isSome);
101 const directives = getDirectiveNodes(schema, schema, pathToDirectivesInExtensions);
102 if (!operationTypes.length && !directives.length) {
103 return null;
104 }
105 const schemaNode = {
106 kind: operationTypes != null ? Kind.SCHEMA_DEFINITION : Kind.SCHEMA_EXTENSION,
107 operationTypes,
108 // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility
109 directives: directives,
110 };
111 const descriptionNode = getDescriptionNode(schema);
112 if (descriptionNode) {
113 schemaNode.description = descriptionNode;
114 }
115 return schemaNode;
116}
117export function astFromDirective(directive, schema, pathToDirectivesInExtensions) {
118 return {
119 kind: Kind.DIRECTIVE_DEFINITION,
120 description: getDescriptionNode(directive),
121 name: {
122 kind: Kind.NAME,
123 value: directive.name,
124 },
125 arguments: directive.args?.map(arg => astFromArg(arg, schema, pathToDirectivesInExtensions)),
126 repeatable: directive.isRepeatable,
127 locations: directive.locations?.map(location => ({
128 kind: Kind.NAME,
129 value: location,
130 })) || [],
131 };
132}
133export function getDirectiveNodes(entity, schema, pathToDirectivesInExtensions) {
134 let directiveNodesBesidesDeprecatedAndSpecifiedBy = [];
135 const directivesInExtensions = getDirectivesInExtensions(entity, pathToDirectivesInExtensions);
136 let directives;
137 if (directivesInExtensions != null) {
138 directives = makeDirectiveNodes(schema, directivesInExtensions);
139 }
140 let deprecatedDirectiveNode = null;
141 let specifiedByDirectiveNode = null;
142 if (directives != null) {
143 directiveNodesBesidesDeprecatedAndSpecifiedBy = directives.filter(directive => directive.name.value !== 'deprecated' && directive.name.value !== 'specifiedBy');
144 if (entity.deprecationReason != null) {
145 deprecatedDirectiveNode = directives.filter(directive => directive.name.value === 'deprecated')?.[0];
146 }
147 if (entity.specifiedByUrl != null || entity.specifiedByURL != null) {
148 specifiedByDirectiveNode = directives.filter(directive => directive.name.value === 'specifiedBy')?.[0];
149 }
150 }
151 if (entity.deprecationReason != null && deprecatedDirectiveNode == null) {
152 deprecatedDirectiveNode = makeDeprecatedDirective(entity.deprecationReason);
153 }
154 if (entity.specifiedByUrl != null ||
155 (entity.specifiedByURL != null && specifiedByDirectiveNode == null)) {
156 const specifiedByValue = entity.specifiedByUrl || entity.specifiedByURL;
157 const specifiedByArgs = {
158 url: specifiedByValue,
159 };
160 specifiedByDirectiveNode = makeDirectiveNode('specifiedBy', specifiedByArgs);
161 }
162 if (deprecatedDirectiveNode != null) {
163 directiveNodesBesidesDeprecatedAndSpecifiedBy.push(deprecatedDirectiveNode);
164 }
165 if (specifiedByDirectiveNode != null) {
166 directiveNodesBesidesDeprecatedAndSpecifiedBy.push(specifiedByDirectiveNode);
167 }
168 return directiveNodesBesidesDeprecatedAndSpecifiedBy;
169}
170export function astFromArg(arg, schema, pathToDirectivesInExtensions) {
171 return {
172 kind: Kind.INPUT_VALUE_DEFINITION,
173 description: getDescriptionNode(arg),
174 name: {
175 kind: Kind.NAME,
176 value: arg.name,
177 },
178 type: astFromType(arg.type),
179 // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility
180 defaultValue: arg.defaultValue !== undefined
181 ? (astFromValue(arg.defaultValue, arg.type) ?? undefined)
182 : undefined,
183 directives: getDirectiveNodes(arg, schema, pathToDirectivesInExtensions),
184 };
185}
186export function astFromObjectType(type, schema, pathToDirectivesInExtensions) {
187 return {
188 kind: Kind.OBJECT_TYPE_DEFINITION,
189 description: getDescriptionNode(type),
190 name: {
191 kind: Kind.NAME,
192 value: type.name,
193 },
194 fields: Object.values(type.getFields()).map(field => astFromField(field, schema, pathToDirectivesInExtensions)),
195 interfaces: Object.values(type.getInterfaces()).map(iFace => astFromType(iFace)),
196 directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions),
197 };
198}
199export function astFromInterfaceType(type, schema, pathToDirectivesInExtensions) {
200 const node = {
201 kind: Kind.INTERFACE_TYPE_DEFINITION,
202 description: getDescriptionNode(type),
203 name: {
204 kind: Kind.NAME,
205 value: type.name,
206 },
207 fields: Object.values(type.getFields()).map(field => astFromField(field, schema, pathToDirectivesInExtensions)),
208 directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions),
209 };
210 if ('getInterfaces' in type) {
211 node.interfaces = Object.values(type.getInterfaces()).map(iFace => astFromType(iFace));
212 }
213 return node;
214}
215export function astFromUnionType(type, schema, pathToDirectivesInExtensions) {
216 return {
217 kind: Kind.UNION_TYPE_DEFINITION,
218 description: getDescriptionNode(type),
219 name: {
220 kind: Kind.NAME,
221 value: type.name,
222 },
223 // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility
224 directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions),
225 types: type.getTypes().map(type => astFromType(type)),
226 };
227}
228export function astFromInputObjectType(type, schema, pathToDirectivesInExtensions) {
229 return {
230 kind: Kind.INPUT_OBJECT_TYPE_DEFINITION,
231 description: getDescriptionNode(type),
232 name: {
233 kind: Kind.NAME,
234 value: type.name,
235 },
236 fields: Object.values(type.getFields()).map(field => astFromInputField(field, schema, pathToDirectivesInExtensions)),
237 // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility
238 directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions),
239 };
240}
241export function astFromEnumType(type, schema, pathToDirectivesInExtensions) {
242 return {
243 kind: Kind.ENUM_TYPE_DEFINITION,
244 description: getDescriptionNode(type),
245 name: {
246 kind: Kind.NAME,
247 value: type.name,
248 },
249 values: Object.values(type.getValues()).map(value => astFromEnumValue(value, schema, pathToDirectivesInExtensions)),
250 // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility
251 directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions),
252 };
253}
254export function astFromScalarType(type, schema, pathToDirectivesInExtensions) {
255 const directivesInExtensions = getDirectivesInExtensions(type, pathToDirectivesInExtensions);
256 const directives = makeDirectiveNodes(schema, directivesInExtensions);
257 const specifiedByValue = (type['specifiedByUrl'] ||
258 type['specifiedByURL']);
259 if (specifiedByValue &&
260 !directives.some(directiveNode => directiveNode.name.value === 'specifiedBy')) {
261 const specifiedByArgs = {
262 url: specifiedByValue,
263 };
264 directives.push(makeDirectiveNode('specifiedBy', specifiedByArgs));
265 }
266 return {
267 kind: Kind.SCALAR_TYPE_DEFINITION,
268 description: getDescriptionNode(type),
269 name: {
270 kind: Kind.NAME,
271 value: type.name,
272 },
273 // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility
274 directives: directives,
275 };
276}
277export function astFromField(field, schema, pathToDirectivesInExtensions) {
278 return {
279 kind: Kind.FIELD_DEFINITION,
280 description: getDescriptionNode(field),
281 name: {
282 kind: Kind.NAME,
283 value: field.name,
284 },
285 arguments: field.args.map(arg => astFromArg(arg, schema, pathToDirectivesInExtensions)),
286 type: astFromType(field.type),
287 // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility
288 directives: getDirectiveNodes(field, schema, pathToDirectivesInExtensions),
289 };
290}
291export function astFromInputField(field, schema, pathToDirectivesInExtensions) {
292 return {
293 kind: Kind.INPUT_VALUE_DEFINITION,
294 description: getDescriptionNode(field),
295 name: {
296 kind: Kind.NAME,
297 value: field.name,
298 },
299 type: astFromType(field.type),
300 // ConstXNode has been introduced in v16 but it is not compatible with XNode so we do `as any` for backwards compatibility
301 directives: getDirectiveNodes(field, schema, pathToDirectivesInExtensions),
302 defaultValue: astFromValue(field.defaultValue, field.type) ?? undefined,
303 };
304}
305export function astFromEnumValue(value, schema, pathToDirectivesInExtensions) {
306 return {
307 kind: Kind.ENUM_VALUE_DEFINITION,
308 description: getDescriptionNode(value),
309 name: {
310 kind: Kind.NAME,
311 value: value.name,
312 },
313 directives: getDirectiveNodes(value, schema, pathToDirectivesInExtensions),
314 };
315}
316export function makeDeprecatedDirective(deprecationReason) {
317 return makeDirectiveNode('deprecated', { reason: deprecationReason }, GraphQLDeprecatedDirective);
318}
319export function makeDirectiveNode(name, args, directive) {
320 const directiveArguments = [];
321 for (const argName in args) {
322 const argValue = args[argName];
323 let value;
324 if (directive != null) {
325 const arg = directive.args.find(arg => arg.name === argName);
326 if (arg) {
327 value = astFromValue(argValue, arg.type);
328 }
329 }
330 if (value == null) {
331 value = astFromValueUntyped(argValue);
332 }
333 if (value != null) {
334 directiveArguments.push({
335 kind: Kind.ARGUMENT,
336 name: {
337 kind: Kind.NAME,
338 value: argName,
339 },
340 value,
341 });
342 }
343 }
344 return {
345 kind: Kind.DIRECTIVE,
346 name: {
347 kind: Kind.NAME,
348 value: name,
349 },
350 arguments: directiveArguments,
351 };
352}
353export function makeDirectiveNodes(schema, directiveValues) {
354 const directiveNodes = [];
355 for (const { name, args } of directiveValues) {
356 const directive = schema?.getDirective(name);
357 directiveNodes.push(makeDirectiveNode(name, args, directive));
358 }
359 return directiveNodes;
360}