1 | import { GraphQLDeprecatedDirective, isEnumType, isInputObjectType, isInterfaceType, isIntrospectionType, isObjectType, isScalarType, isSpecifiedDirective, isSpecifiedScalarType, isUnionType, Kind, print, } from 'graphql';
|
2 | import { astFromType } from './astFromType.js';
|
3 | import { astFromValue } from './astFromValue.js';
|
4 | import { astFromValueUntyped } from './astFromValueUntyped.js';
|
5 | import { getDescriptionNode } from './descriptionFromObject.js';
|
6 | import { getDirectivesInExtensions, } from './get-directives.js';
|
7 | import { isSome } from './helpers.js';
|
8 | import { getRootTypeMap } from './rootTypes.js';
|
9 | export 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 |
|
56 |
|
57 | export function printSchemaWithDirectives(schema, options = {}) {
|
58 | const documentNode = getDocumentNodeFromSchema(schema, options);
|
59 | return print(documentNode);
|
60 | }
|
61 | export 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 |
|
109 | directives: directives,
|
110 | };
|
111 | const descriptionNode = getDescriptionNode(schema);
|
112 | if (descriptionNode) {
|
113 | schemaNode.description = descriptionNode;
|
114 | }
|
115 | return schemaNode;
|
116 | }
|
117 | export 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 | }
|
133 | export 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 | }
|
170 | export 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 |
|
180 | defaultValue: arg.defaultValue !== undefined
|
181 | ? (astFromValue(arg.defaultValue, arg.type) ?? undefined)
|
182 | : undefined,
|
183 | directives: getDirectiveNodes(arg, schema, pathToDirectivesInExtensions),
|
184 | };
|
185 | }
|
186 | export 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 | }
|
199 | export 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 | }
|
215 | export 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 |
|
224 | directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions),
|
225 | types: type.getTypes().map(type => astFromType(type)),
|
226 | };
|
227 | }
|
228 | export 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 |
|
238 | directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions),
|
239 | };
|
240 | }
|
241 | export 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 |
|
251 | directives: getDirectiveNodes(type, schema, pathToDirectivesInExtensions),
|
252 | };
|
253 | }
|
254 | export 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 |
|
274 | directives: directives,
|
275 | };
|
276 | }
|
277 | export 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 |
|
288 | directives: getDirectiveNodes(field, schema, pathToDirectivesInExtensions),
|
289 | };
|
290 | }
|
291 | export 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 |
|
301 | directives: getDirectiveNodes(field, schema, pathToDirectivesInExtensions),
|
302 | defaultValue: astFromValue(field.defaultValue, field.type) ?? undefined,
|
303 | };
|
304 | }
|
305 | export 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 | }
|
316 | export function makeDeprecatedDirective(deprecationReason) {
|
317 | return makeDirectiveNode('deprecated', { reason: deprecationReason }, GraphQLDeprecatedDirective);
|
318 | }
|
319 | export 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 | }
|
353 | export 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 | }
|