UNPKG

31.8 kBJavaScriptView Raw
1import { printSchemaWithDirectives, createSchemaDefinition, compareNodes, isNotEqual, isValidPath, parseGraphQLSDL } from '@graphql-tools/utils';
2import { isAbsolute, resolve } from 'path';
3import { pathExists, pathExistsSync, readFile, readFileSync } from 'fs-extra';
4import { cwd } from 'process';
5import { Kind, visit, isSchema, parse, Source, getDescription, print, isExecutableDefinitionNode } from 'graphql';
6import { processImport } from '@graphql-tools/import';
7
8function mergeArguments(args1, args2, config) {
9 const result = deduplicateArguments([].concat(args2, args1).filter(a => a));
10 if (config && config.sort) {
11 result.sort(compareNodes);
12 }
13 return result;
14}
15function deduplicateArguments(args) {
16 return args.reduce((acc, current) => {
17 const dup = acc.find(arg => arg.name.value === current.name.value);
18 if (!dup) {
19 return acc.concat([current]);
20 }
21 return acc;
22 }, []);
23}
24
25let commentsRegistry = {};
26function resetComments() {
27 commentsRegistry = {};
28}
29function collectComment(node) {
30 const entityName = node.name.value;
31 pushComment(node, entityName);
32 switch (node.kind) {
33 case 'EnumTypeDefinition':
34 node.values.forEach(value => {
35 pushComment(value, entityName, value.name.value);
36 });
37 break;
38 case 'ObjectTypeDefinition':
39 case 'InputObjectTypeDefinition':
40 case 'InterfaceTypeDefinition':
41 if (node.fields) {
42 node.fields.forEach((field) => {
43 pushComment(field, entityName, field.name.value);
44 if (isFieldDefinitionNode(field) && field.arguments) {
45 field.arguments.forEach(arg => {
46 pushComment(arg, entityName, field.name.value, arg.name.value);
47 });
48 }
49 });
50 }
51 break;
52 }
53}
54function pushComment(node, entity, field, argument) {
55 const comment = getDescription(node, { commentDescriptions: true });
56 if (typeof comment !== 'string' || comment.length === 0) {
57 return;
58 }
59 const keys = [entity];
60 if (field) {
61 keys.push(field);
62 if (argument) {
63 keys.push(argument);
64 }
65 }
66 const path = keys.join('.');
67 if (!commentsRegistry[path]) {
68 commentsRegistry[path] = [];
69 }
70 commentsRegistry[path].push(comment);
71}
72function printComment(comment) {
73 return '\n# ' + comment.replace(/\n/g, '\n# ');
74}
75/**
76 * Copyright (c) 2015-present, Facebook, Inc.
77 *
78 * This source code is licensed under the MIT license found in the
79 * LICENSE file in the root directory of this source tree.
80 */
81/**
82 * NOTE: ==> This file has been modified just to add comments to the printed AST
83 * This is a temp measure, we will move to using the original non modified printer.js ASAP.
84 */
85// import { visit, VisitFn } from 'graphql/language/visitor';
86/**
87 * Given maybeArray, print an empty string if it is null or empty, otherwise
88 * print all items together separated by separator if provided
89 */
90function join(maybeArray, separator) {
91 return maybeArray ? maybeArray.filter(x => x).join(separator || '') : '';
92}
93function addDescription(cb) {
94 return (node, _key, _parent, path, ancestors) => {
95 const keys = [];
96 const parent = path.reduce((prev, key) => {
97 if (['fields', 'arguments', 'values'].includes(key)) {
98 keys.push(prev.name.value);
99 }
100 return prev[key];
101 }, ancestors[0]);
102 const key = [...keys, parent.name.value].join('.');
103 const items = [];
104 if (commentsRegistry[key]) {
105 items.push(...commentsRegistry[key]);
106 }
107 return join([...items.map(printComment), node.description, cb(node)], '\n');
108 };
109}
110function indent(maybeString) {
111 return maybeString && ` ${maybeString.replace(/\n/g, '\n ')}`;
112}
113/**
114 * Given array, print each item on its own line, wrapped in an
115 * indented "{ }" block.
116 */
117function block(array) {
118 return array && array.length !== 0 ? `{\n${indent(join(array, '\n'))}\n}` : '';
119}
120/**
121 * If maybeString is not null or empty, then wrap with start and end, otherwise
122 * print an empty string.
123 */
124function wrap(start, maybeString, end) {
125 return maybeString ? start + maybeString + (end || '') : '';
126}
127/**
128 * Print a block string in the indented block form by adding a leading and
129 * trailing blank line. However, if a block string starts with whitespace and is
130 * a single-line, adding a leading blank line would strip that whitespace.
131 */
132function printBlockString(value, isDescription) {
133 const escaped = value.replace(/"""/g, '\\"""');
134 return (value[0] === ' ' || value[0] === '\t') && value.indexOf('\n') === -1
135 ? `"""${escaped.replace(/"$/, '"\n')}"""`
136 : `"""\n${isDescription ? escaped : indent(escaped)}\n"""`;
137}
138/**
139 * Converts an AST into a string, using one set of reasonable
140 * formatting rules.
141 */
142function printWithComments(ast) {
143 return visit(ast, {
144 leave: {
145 Name: node => node.value,
146 Variable: node => `$${node.name}`,
147 // Document
148 Document: node => `${node.definitions
149 .map(defNode => `${defNode}\n${defNode[0] === '#' ? '' : '\n'}`)
150 .join('')
151 .trim()}\n`,
152 OperationTypeDefinition: node => `${node.operation}: ${node.type}`,
153 VariableDefinition: ({ variable, type, defaultValue }) => `${variable}: ${type}${wrap(' = ', defaultValue)}`,
154 SelectionSet: ({ selections }) => block(selections),
155 Field: ({ alias, name, arguments: args, directives, selectionSet }) => join([wrap('', alias, ': ') + name + wrap('(', join(args, ', '), ')'), join(directives, ' '), selectionSet], ' '),
156 Argument: addDescription(({ name, value }) => `${name}: ${value}`),
157 // Value
158 IntValue: ({ value }) => value,
159 FloatValue: ({ value }) => value,
160 StringValue: ({ value, block: isBlockString }, key) => isBlockString ? printBlockString(value, key === 'description') : JSON.stringify(value),
161 BooleanValue: ({ value }) => (value ? 'true' : 'false'),
162 NullValue: () => 'null',
163 EnumValue: ({ value }) => value,
164 ListValue: ({ values }) => `[${join(values, ', ')}]`,
165 ObjectValue: ({ fields }) => `{${join(fields, ', ')}}`,
166 ObjectField: ({ name, value }) => `${name}: ${value}`,
167 // Directive
168 Directive: ({ name, arguments: args }) => `@${name}${wrap('(', join(args, ', '), ')')}`,
169 // Type
170 NamedType: ({ name }) => name,
171 ListType: ({ type }) => `[${type}]`,
172 NonNullType: ({ type }) => `${type}!`,
173 // Type System Definitions
174 SchemaDefinition: ({ directives, operationTypes }) => join(['schema', join(directives, ' '), block(operationTypes)], ' '),
175 ScalarTypeDefinition: addDescription(({ name, directives }) => join(['scalar', name, join(directives, ' ')], ' ')),
176 ObjectTypeDefinition: addDescription(({ name, interfaces, directives, fields }) => join(['type', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], ' ')),
177 FieldDefinition: addDescription(({ name, arguments: args, type, directives }) => `${name + wrap('(', join(args, ', '), ')')}: ${type}${wrap(' ', join(directives, ' '))}`),
178 InputValueDefinition: addDescription(({ name, type, defaultValue, directives }) => join([`${name}: ${type}`, wrap('= ', defaultValue), join(directives, ' ')], ' ')),
179 InterfaceTypeDefinition: addDescription(({ name, directives, fields }) => join(['interface', name, join(directives, ' '), block(fields)], ' ')),
180 UnionTypeDefinition: addDescription(({ name, directives, types }) => join(['union', name, join(directives, ' '), types && types.length !== 0 ? `= ${join(types, ' | ')}` : ''], ' ')),
181 EnumTypeDefinition: addDescription(({ name, directives, values }) => join(['enum', name, join(directives, ' '), block(values)], ' ')),
182 EnumValueDefinition: addDescription(({ name, directives }) => join([name, join(directives, ' ')], ' ')),
183 InputObjectTypeDefinition: addDescription(({ name, directives, fields }) => join(['input', name, join(directives, ' '), block(fields)], ' ')),
184 ScalarTypeExtension: ({ name, directives }) => join(['extend scalar', name, join(directives, ' ')], ' '),
185 ObjectTypeExtension: ({ name, interfaces, directives, fields }) => join(['extend type', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], ' '),
186 InterfaceTypeExtension: ({ name, directives, fields }) => join(['extend interface', name, join(directives, ' '), block(fields)], ' '),
187 UnionTypeExtension: ({ name, directives, types }) => join(['extend union', name, join(directives, ' '), types && types.length !== 0 ? `= ${join(types, ' | ')}` : ''], ' '),
188 EnumTypeExtension: ({ name, directives, values }) => join(['extend enum', name, join(directives, ' '), block(values)], ' '),
189 InputObjectTypeExtension: ({ name, directives, fields }) => join(['extend input', name, join(directives, ' '), block(fields)], ' '),
190 DirectiveDefinition: addDescription(({ name, arguments: args, locations }) => `directive @${name}${wrap('(', join(args, ', '), ')')} on ${join(locations, ' | ')}`),
191 },
192 });
193}
194function isFieldDefinitionNode(node) {
195 return node.kind === 'FieldDefinition';
196}
197
198function directiveAlreadyExists(directivesArr, otherDirective) {
199 return !!directivesArr.find(directive => directive.name.value === otherDirective.name.value);
200}
201function nameAlreadyExists(name, namesArr) {
202 return namesArr.some(({ value }) => value === name.value);
203}
204function mergeArguments$1(a1, a2) {
205 const result = [...a2];
206 for (const argument of a1) {
207 const existingIndex = result.findIndex(a => a.name.value === argument.name.value);
208 if (existingIndex > -1) {
209 const existingArg = result[existingIndex];
210 if (existingArg.value.kind === 'ListValue') {
211 const source = existingArg.value.values;
212 const target = argument.value.values;
213 // merge values of two lists
214 existingArg.value.values = deduplicateLists(source, target, (targetVal, source) => {
215 const value = targetVal.value;
216 return !value || !source.some((sourceVal) => sourceVal.value === value);
217 });
218 }
219 else {
220 existingArg.value = argument.value;
221 }
222 }
223 else {
224 result.push(argument);
225 }
226 }
227 return result;
228}
229function deduplicateDirectives(directives) {
230 return directives
231 .map((directive, i, all) => {
232 const firstAt = all.findIndex(d => d.name.value === directive.name.value);
233 if (firstAt !== i) {
234 const dup = all[firstAt];
235 directive.arguments = mergeArguments$1(directive.arguments, dup.arguments);
236 return null;
237 }
238 return directive;
239 })
240 .filter(d => d);
241}
242function mergeDirectives(d1 = [], d2 = [], config) {
243 const reverseOrder = config && config.reverseDirectives;
244 const asNext = reverseOrder ? d1 : d2;
245 const asFirst = reverseOrder ? d2 : d1;
246 const result = deduplicateDirectives([...asNext]);
247 for (const directive of asFirst) {
248 if (directiveAlreadyExists(result, directive)) {
249 const existingDirectiveIndex = result.findIndex(d => d.name.value === directive.name.value);
250 const existingDirective = result[existingDirectiveIndex];
251 result[existingDirectiveIndex].arguments = mergeArguments$1(directive.arguments || [], existingDirective.arguments || []);
252 }
253 else {
254 result.push(directive);
255 }
256 }
257 return result;
258}
259function validateInputs(node, existingNode) {
260 const printedNode = print(node);
261 const printedExistingNode = print(existingNode);
262 const leaveInputs = new RegExp('(directive @w*d*)|( on .*$)', 'g');
263 const sameArguments = printedNode.replace(leaveInputs, '') === printedExistingNode.replace(leaveInputs, '');
264 if (!sameArguments) {
265 throw new Error(`Unable to merge GraphQL directive "${node.name.value}". \nExisting directive: \n\t${printedExistingNode} \nReceived directive: \n\t${printedNode}`);
266 }
267}
268function mergeDirective(node, existingNode) {
269 if (existingNode) {
270 validateInputs(node, existingNode);
271 return {
272 ...node,
273 locations: [
274 ...existingNode.locations,
275 ...node.locations.filter(name => !nameAlreadyExists(name, existingNode.locations)),
276 ],
277 };
278 }
279 return node;
280}
281function deduplicateLists(source, target, filterFn) {
282 return source.concat(target.filter(val => filterFn(val, source)));
283}
284
285function mergeEnumValues(first, second, config) {
286 const enumValueMap = new Map();
287 for (const firstValue of first) {
288 enumValueMap.set(firstValue.name.value, firstValue);
289 }
290 for (const secondValue of second) {
291 const enumValue = secondValue.name.value;
292 if (enumValueMap.has(enumValue)) {
293 const firstValue = enumValueMap.get(enumValue);
294 firstValue.description = secondValue.description || firstValue.description;
295 firstValue.directives = mergeDirectives(secondValue.directives, firstValue.directives);
296 }
297 else {
298 enumValueMap.set(enumValue, secondValue);
299 }
300 }
301 const result = [...enumValueMap.values()];
302 if (config && config.sort) {
303 result.sort(compareNodes);
304 }
305 return result;
306}
307
308function mergeEnum(e1, e2, config) {
309 if (e2) {
310 return {
311 name: e1.name,
312 description: e1['description'] || e2['description'],
313 kind: (config && config.convertExtensions) || e1.kind === 'EnumTypeDefinition' || e2.kind === 'EnumTypeDefinition'
314 ? 'EnumTypeDefinition'
315 : 'EnumTypeExtension',
316 loc: e1.loc,
317 directives: mergeDirectives(e1.directives, e2.directives, config),
318 values: mergeEnumValues(e1.values, e2.values, config),
319 };
320 }
321 return config && config.convertExtensions
322 ? {
323 ...e1,
324 kind: 'EnumTypeDefinition',
325 }
326 : e1;
327}
328
329function isStringTypes(types) {
330 return typeof types === 'string';
331}
332function isSourceTypes(types) {
333 return types instanceof Source;
334}
335function isGraphQLType(definition) {
336 return definition.kind === 'ObjectTypeDefinition';
337}
338function isGraphQLTypeExtension(definition) {
339 return definition.kind === 'ObjectTypeExtension';
340}
341function isGraphQLEnum(definition) {
342 return definition.kind === 'EnumTypeDefinition';
343}
344function isGraphQLEnumExtension(definition) {
345 return definition.kind === 'EnumTypeExtension';
346}
347function isGraphQLUnion(definition) {
348 return definition.kind === 'UnionTypeDefinition';
349}
350function isGraphQLUnionExtension(definition) {
351 return definition.kind === 'UnionTypeExtension';
352}
353function isGraphQLScalar(definition) {
354 return definition.kind === 'ScalarTypeDefinition';
355}
356function isGraphQLScalarExtension(definition) {
357 return definition.kind === 'ScalarTypeExtension';
358}
359function isGraphQLInputType(definition) {
360 return definition.kind === 'InputObjectTypeDefinition';
361}
362function isGraphQLInputTypeExtension(definition) {
363 return definition.kind === 'InputObjectTypeExtension';
364}
365function isGraphQLInterface(definition) {
366 return definition.kind === 'InterfaceTypeDefinition';
367}
368function isGraphQLInterfaceExtension(definition) {
369 return definition.kind === 'InterfaceTypeExtension';
370}
371function isGraphQLDirective(definition) {
372 return definition.kind === 'DirectiveDefinition';
373}
374function extractType(type) {
375 let visitedType = type;
376 while (visitedType.kind === 'ListType' || visitedType.kind === 'NonNullType') {
377 visitedType = visitedType.type;
378 }
379 return visitedType;
380}
381function isSchemaDefinition(node) {
382 return node.kind === 'SchemaDefinition';
383}
384function isWrappingTypeNode(type) {
385 return type.kind !== Kind.NAMED_TYPE;
386}
387function isListTypeNode(type) {
388 return type.kind === Kind.LIST_TYPE;
389}
390function isNonNullTypeNode(type) {
391 return type.kind === Kind.NON_NULL_TYPE;
392}
393function printTypeNode(type) {
394 if (isListTypeNode(type)) {
395 return `[${printTypeNode(type.type)}]`;
396 }
397 if (isNonNullTypeNode(type)) {
398 return `${printTypeNode(type.type)}!`;
399 }
400 return type.name.value;
401}
402
403function fieldAlreadyExists(fieldsArr, otherField) {
404 const result = fieldsArr.find(field => field.name.value === otherField.name.value);
405 if (result) {
406 const t1 = extractType(result.type);
407 const t2 = extractType(otherField.type);
408 if (t1.name.value !== t2.name.value) {
409 throw new Error(`Field "${otherField.name.value}" already defined with a different type. Declared as "${t1.name.value}", but you tried to override with "${t2.name.value}"`);
410 }
411 }
412 return !!result;
413}
414function mergeFields(type, f1, f2, config) {
415 const result = [...f2];
416 for (const field of f1) {
417 if (fieldAlreadyExists(result, field)) {
418 const existing = result.find((f) => f.name.value === field.name.value);
419 if (config && config.throwOnConflict) {
420 preventConflicts(type, existing, field, false);
421 }
422 else {
423 preventConflicts(type, existing, field, true);
424 }
425 if (isNonNullTypeNode(field.type) && !isNonNullTypeNode(existing.type)) {
426 existing.type = field.type;
427 }
428 existing.arguments = mergeArguments(field['arguments'] || [], existing.arguments || [], config);
429 existing.directives = mergeDirectives(field.directives, existing.directives, config);
430 existing.description = field.description || existing.description;
431 }
432 else {
433 result.push(field);
434 }
435 }
436 if (config && config.sort) {
437 result.sort(compareNodes);
438 }
439 if (config && config.exclusions) {
440 return result.filter(field => !config.exclusions.includes(`${type.name.value}.${field.name.value}`));
441 }
442 return result;
443}
444function preventConflicts(type, a, b, ignoreNullability = false) {
445 const aType = printTypeNode(a.type);
446 const bType = printTypeNode(b.type);
447 if (isNotEqual(aType, bType)) {
448 if (safeChangeForFieldType(a.type, b.type, ignoreNullability) === false) {
449 throw new Error(`Field '${type.name.value}.${a.name.value}' changed type from '${aType}' to '${bType}'`);
450 }
451 }
452}
453function safeChangeForFieldType(oldType, newType, ignoreNullability = false) {
454 // both are named
455 if (!isWrappingTypeNode(oldType) && !isWrappingTypeNode(newType)) {
456 return oldType.toString() === newType.toString();
457 }
458 // new is non-null
459 if (isNonNullTypeNode(newType)) {
460 const ofType = isNonNullTypeNode(oldType) ? oldType.type : oldType;
461 return safeChangeForFieldType(ofType, newType.type);
462 }
463 // old is non-null
464 if (isNonNullTypeNode(oldType)) {
465 return safeChangeForFieldType(newType, oldType, ignoreNullability);
466 }
467 // old is list
468 if (isListTypeNode(oldType)) {
469 return ((isListTypeNode(newType) && safeChangeForFieldType(oldType.type, newType.type)) ||
470 (isNonNullTypeNode(newType) && safeChangeForFieldType(oldType, newType['type'])));
471 }
472 return false;
473}
474
475function mergeInputType(node, existingNode, config) {
476 if (existingNode) {
477 try {
478 return {
479 name: node.name,
480 description: node['description'] || existingNode['description'],
481 kind: (config && config.convertExtensions) ||
482 node.kind === 'InputObjectTypeDefinition' ||
483 existingNode.kind === 'InputObjectTypeDefinition'
484 ? 'InputObjectTypeDefinition'
485 : 'InputObjectTypeExtension',
486 loc: node.loc,
487 fields: mergeFields(node, node.fields, existingNode.fields, config),
488 directives: mergeDirectives(node.directives, existingNode.directives, config),
489 };
490 }
491 catch (e) {
492 throw new Error(`Unable to merge GraphQL input type "${node.name.value}": ${e.message}`);
493 }
494 }
495 return config && config.convertExtensions
496 ? {
497 ...node,
498 kind: 'InputObjectTypeDefinition',
499 }
500 : node;
501}
502
503function mergeInterface(node, existingNode, config) {
504 if (existingNode) {
505 try {
506 return {
507 name: node.name,
508 description: node['description'] || existingNode['description'],
509 kind: (config && config.convertExtensions) ||
510 node.kind === 'InterfaceTypeDefinition' ||
511 existingNode.kind === 'InterfaceTypeDefinition'
512 ? 'InterfaceTypeDefinition'
513 : 'InterfaceTypeExtension',
514 loc: node.loc,
515 fields: mergeFields(node, node.fields, existingNode.fields, config),
516 directives: mergeDirectives(node.directives, existingNode.directives, config),
517 };
518 }
519 catch (e) {
520 throw new Error(`Unable to merge GraphQL interface "${node.name.value}": ${e.message}`);
521 }
522 }
523 return config && config.convertExtensions
524 ? {
525 ...node,
526 kind: 'InterfaceTypeDefinition',
527 }
528 : node;
529}
530
531function alreadyExists(arr, other) {
532 return !!arr.find(i => i.name.value === other.name.value);
533}
534function mergeNamedTypeArray(first, second, config) {
535 const result = [...second, ...first.filter(d => !alreadyExists(second, d))];
536 if (config && config.sort) {
537 result.sort(compareNodes);
538 }
539 return result;
540}
541
542function mergeType(node, existingNode, config) {
543 if (existingNode) {
544 try {
545 return {
546 name: node.name,
547 description: node['description'] || existingNode['description'],
548 kind: (config && config.convertExtensions) ||
549 node.kind === 'ObjectTypeDefinition' ||
550 existingNode.kind === 'ObjectTypeDefinition'
551 ? 'ObjectTypeDefinition'
552 : 'ObjectTypeExtension',
553 loc: node.loc,
554 fields: mergeFields(node, node.fields, existingNode.fields, config),
555 directives: mergeDirectives(node.directives, existingNode.directives, config),
556 interfaces: mergeNamedTypeArray(node.interfaces, existingNode.interfaces, config),
557 };
558 }
559 catch (e) {
560 throw new Error(`Unable to merge GraphQL type "${node.name.value}": ${e.message}`);
561 }
562 }
563 return config && config.convertExtensions
564 ? {
565 ...node,
566 kind: 'ObjectTypeDefinition',
567 }
568 : node;
569}
570
571function mergeUnion(first, second, config) {
572 if (second) {
573 return {
574 name: first.name,
575 description: first['description'] || second['description'],
576 directives: mergeDirectives(first.directives, second.directives, config),
577 kind: (config && config.convertExtensions) ||
578 first.kind === 'UnionTypeDefinition' ||
579 second.kind === 'UnionTypeDefinition'
580 ? 'UnionTypeDefinition'
581 : 'UnionTypeExtension',
582 loc: first.loc,
583 types: mergeNamedTypeArray(first.types, second.types, config),
584 };
585 }
586 return config && config.convertExtensions
587 ? {
588 ...first,
589 kind: 'UnionTypeDefinition',
590 }
591 : first;
592}
593
594function mergeGraphQLNodes(nodes, config) {
595 return nodes.reduce((prev, nodeDefinition) => {
596 const node = nodeDefinition;
597 if (node && node.name && node.name.value) {
598 const name = node.name.value;
599 if (config && config.commentDescriptions) {
600 collectComment(node);
601 }
602 if (config &&
603 config.exclusions &&
604 (config.exclusions.includes(name + '.*') || config.exclusions.includes(name))) {
605 delete prev[name];
606 }
607 else if (isGraphQLType(nodeDefinition) || isGraphQLTypeExtension(nodeDefinition)) {
608 prev[name] = mergeType(nodeDefinition, prev[name], config);
609 }
610 else if (isGraphQLEnum(nodeDefinition) || isGraphQLEnumExtension(nodeDefinition)) {
611 prev[name] = mergeEnum(nodeDefinition, prev[name], config);
612 }
613 else if (isGraphQLUnion(nodeDefinition) || isGraphQLUnionExtension(nodeDefinition)) {
614 prev[name] = mergeUnion(nodeDefinition, prev[name], config);
615 }
616 else if (isGraphQLScalar(nodeDefinition) || isGraphQLScalarExtension(nodeDefinition)) {
617 prev[name] = nodeDefinition;
618 }
619 else if (isGraphQLInputType(nodeDefinition) || isGraphQLInputTypeExtension(nodeDefinition)) {
620 prev[name] = mergeInputType(nodeDefinition, prev[name], config);
621 }
622 else if (isGraphQLInterface(nodeDefinition) || isGraphQLInterfaceExtension(nodeDefinition)) {
623 prev[name] = mergeInterface(nodeDefinition, prev[name], config);
624 }
625 else if (isGraphQLDirective(nodeDefinition)) {
626 prev[name] = mergeDirective(nodeDefinition, prev[name]);
627 }
628 }
629 return prev;
630 }, {});
631}
632
633function mergeTypeDefs(types, config) {
634 resetComments();
635 const doc = {
636 kind: Kind.DOCUMENT,
637 definitions: mergeGraphQLTypes(types, {
638 useSchemaDefinition: true,
639 forceSchemaDefinition: false,
640 throwOnConflict: false,
641 commentDescriptions: false,
642 ...config,
643 }),
644 };
645 let result;
646 if (config && config.commentDescriptions) {
647 result = printWithComments(doc);
648 }
649 else {
650 result = doc;
651 }
652 resetComments();
653 return result;
654}
655function mergeGraphQLTypes(types, config) {
656 resetComments();
657 const allNodes = types
658 .map(type => {
659 if (Array.isArray(type)) {
660 type = mergeTypeDefs(type);
661 }
662 if (isSchema(type)) {
663 return parse(printSchemaWithDirectives(type));
664 }
665 else if (isStringTypes(type) || isSourceTypes(type)) {
666 return parse(type);
667 }
668 return type;
669 })
670 .map(ast => ast.definitions)
671 .reduce((defs, newDef = []) => [...defs, ...newDef], []);
672 // XXX: right now we don't handle multiple schema definitions
673 let schemaDef = allNodes.filter(isSchemaDefinition).reduce((def, node) => {
674 node.operationTypes
675 .filter(op => op.type.name.value)
676 .forEach(op => {
677 def[op.operation] = op.type.name.value;
678 });
679 return def;
680 }, {
681 query: null,
682 mutation: null,
683 subscription: null,
684 });
685 const mergedNodes = mergeGraphQLNodes(allNodes, config);
686 const allTypes = Object.keys(mergedNodes);
687 if (config && config.sort) {
688 allTypes.sort(typeof config.sort === 'function' ? config.sort : undefined);
689 }
690 if (config && config.useSchemaDefinition) {
691 const queryType = schemaDef.query ? schemaDef.query : allTypes.find(t => t === 'Query');
692 const mutationType = schemaDef.mutation ? schemaDef.mutation : allTypes.find(t => t === 'Mutation');
693 const subscriptionType = schemaDef.subscription ? schemaDef.subscription : allTypes.find(t => t === 'Subscription');
694 schemaDef = {
695 query: queryType,
696 mutation: mutationType,
697 subscription: subscriptionType,
698 };
699 }
700 const schemaDefinition = createSchemaDefinition(schemaDef, {
701 force: config.forceSchemaDefinition,
702 });
703 if (!schemaDefinition) {
704 return Object.values(mergedNodes);
705 }
706 return [...Object.values(mergedNodes), parse(schemaDefinition).definitions[0]];
707}
708
709const FILE_EXTENSIONS = ['.gql', '.gqls', '.graphql', '.graphqls'];
710function isGraphQLImportFile(rawSDL) {
711 const trimmedRawSDL = rawSDL.trim();
712 return trimmedRawSDL.startsWith('# import') || trimmedRawSDL.startsWith('#import');
713}
714/**
715 * This loader loads documents and type definitions from `.graphql` files.
716 *
717 * You can load a single source:
718 *
719 * ```js
720 * const schema = await loadSchema('schema.graphql', {
721 * loaders: [
722 * new GraphQLFileLoader()
723 * ]
724 * });
725 * ```
726 *
727 * Or provide a glob pattern to load multiple sources:
728 *
729 * ```js
730 * const schema = await loadSchema('graphql/*.graphql', {
731 * loaders: [
732 * new GraphQLFileLoader()
733 * ]
734 * });
735 * ```
736 */
737class GraphQLFileLoader {
738 loaderId() {
739 return 'graphql-file';
740 }
741 async canLoad(pointer, options) {
742 if (isValidPath(pointer)) {
743 if (FILE_EXTENSIONS.find(extension => pointer.endsWith(extension))) {
744 const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer);
745 return pathExists(normalizedFilePath);
746 }
747 }
748 return false;
749 }
750 canLoadSync(pointer, options) {
751 if (isValidPath(pointer)) {
752 if (FILE_EXTENSIONS.find(extension => pointer.endsWith(extension))) {
753 const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer);
754 return pathExistsSync(normalizedFilePath);
755 }
756 }
757 return false;
758 }
759 async load(pointer, options) {
760 const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer);
761 const rawSDL = await readFile(normalizedFilePath, { encoding: 'utf8' });
762 return this.handleFileContent(rawSDL, pointer, options);
763 }
764 loadSync(pointer, options) {
765 const normalizedFilePath = isAbsolute(pointer) ? pointer : resolve(options.cwd || cwd(), pointer);
766 const rawSDL = readFileSync(normalizedFilePath, { encoding: 'utf8' });
767 return this.handleFileContent(rawSDL, pointer, options);
768 }
769 handleFileContent(rawSDL, pointer, options) {
770 if (!options.skipGraphQLImport && isGraphQLImportFile(rawSDL)) {
771 const document = processImport(pointer, options.cwd);
772 const typeSystemDefinitions = document.definitions
773 .filter(d => !isExecutableDefinitionNode(d))
774 .map(definition => ({
775 kind: Kind.DOCUMENT,
776 definitions: [definition],
777 }));
778 const mergedTypeDefs = mergeTypeDefs(typeSystemDefinitions, { useSchemaDefinition: false });
779 const executableDefinitions = document.definitions.filter(isExecutableDefinitionNode);
780 return {
781 location: pointer,
782 document: {
783 ...mergedTypeDefs,
784 definitions: [...mergedTypeDefs.definitions, ...executableDefinitions],
785 },
786 };
787 }
788 return parseGraphQLSDL(pointer, rawSDL, options);
789 }
790}
791
792export { GraphQLFileLoader };
793//# sourceMappingURL=index.esm.js.map