UNPKG

67.2 kBJavaScriptView Raw
1import { __awaiter } from 'tslib';
2import { Kind, TypeInfo, visit, visitWithTypeInfo, GraphQLError, getNamedType, isWrappingType, isListType, isNonNullType, isInterfaceType, isEnumType, isUnionType, isInputObjectType, isObjectType, isScalarType, parse, extendSchema, print, validate as validate$1, printType } from 'graphql';
3import inspect from 'object-inspect';
4import { DepGraph } from 'dependency-graph';
5
6function keyMap(list, keyFn) {
7 return list.reduce((map, item) => {
8 map[keyFn(item)] = item;
9 return map;
10 }, Object.create(null));
11}
12function isEqual(a, b) {
13 if (Array.isArray(a) && Array.isArray(b)) {
14 if (a.length !== b.length)
15 return false;
16 for (var index = 0; index < a.length; index++) {
17 if (a[index] !== b[index]) {
18 return false;
19 }
20 }
21 return true;
22 }
23 return a === b || (!a && !b);
24}
25function isNotEqual(a, b) {
26 return !isEqual(a, b);
27}
28function isVoid(a) {
29 return typeof a === 'undefined' || a === null;
30}
31function diffArrays(a, b) {
32 return a.filter((c) => !b.some((d) => d === c));
33}
34function compareLists(oldList, newList, callbacks) {
35 const oldMap = keyMap(oldList, ({ name }) => name);
36 const newMap = keyMap(newList, ({ name }) => name);
37 const added = [];
38 const removed = [];
39 const mutual = [];
40 for (const oldItem of oldList) {
41 const newItem = newMap[oldItem.name];
42 if (newItem === undefined) {
43 removed.push(oldItem);
44 }
45 else {
46 mutual.push({
47 newVersion: newItem,
48 oldVersion: oldItem,
49 });
50 }
51 }
52 for (const newItem of newList) {
53 if (oldMap[newItem.name] === undefined) {
54 added.push(newItem);
55 }
56 }
57 if (callbacks) {
58 if (callbacks.onAdded) {
59 added.forEach(callbacks.onAdded);
60 }
61 if (callbacks.onRemoved) {
62 removed.forEach(callbacks.onRemoved);
63 }
64 if (callbacks.onMutual) {
65 mutual.forEach(callbacks.onMutual);
66 }
67 }
68 return {
69 added,
70 removed,
71 mutual,
72 };
73}
74
75function safeChangeForField(oldType, newType) {
76 if (!isWrappingType(oldType) && !isWrappingType(newType)) {
77 return oldType.toString() === newType.toString();
78 }
79 if (isNonNullType(newType)) {
80 const ofType = isNonNullType(oldType) ? oldType.ofType : oldType;
81 return safeChangeForField(ofType, newType.ofType);
82 }
83 if (isListType(oldType)) {
84 return ((isListType(newType) &&
85 safeChangeForField(oldType.ofType, newType.ofType)) ||
86 (isNonNullType(newType) && safeChangeForField(oldType, newType.ofType)));
87 }
88 return false;
89}
90function safeChangeForInputValue(oldType, newType) {
91 if (!isWrappingType(oldType) && !isWrappingType(newType)) {
92 return oldType.toString() === newType.toString();
93 }
94 if (isListType(oldType) && isListType(newType)) {
95 return safeChangeForInputValue(oldType.ofType, newType.ofType);
96 }
97 if (isNonNullType(oldType)) {
98 const ofType = isNonNullType(newType) ? newType : newType;
99 return safeChangeForInputValue(oldType.ofType, ofType);
100 }
101 return false;
102}
103function getKind(type) {
104 const node = type.astNode;
105 return (node && node.kind) || '';
106}
107function getTypePrefix(type) {
108 const kind = getKind(type);
109 const kindsMap = {
110 [Kind.SCALAR_TYPE_DEFINITION]: 'scalar',
111 [Kind.OBJECT_TYPE_DEFINITION]: 'type',
112 [Kind.INTERFACE_TYPE_DEFINITION]: 'interface',
113 [Kind.UNION_TYPE_DEFINITION]: 'union',
114 [Kind.ENUM_TYPE_DEFINITION]: 'enum',
115 [Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
116 };
117 return kindsMap[kind];
118}
119function isPrimitive(type) {
120 return (['String', 'Int', 'Float', 'Boolean', 'ID'].indexOf(typeof type === 'string' ? type : type.name) !== -1);
121}
122function isForIntrospection(type) {
123 return ([
124 '__Schema',
125 '__Type',
126 '__TypeKind',
127 '__Field',
128 '__InputValue',
129 '__EnumValue',
130 '__Directive',
131 '__DirectiveLocation',
132 ].indexOf(typeof type === 'string' ? type : type.name) !== -1);
133}
134function findDeprecatedUsages(schema, ast) {
135 const errors = [];
136 const typeInfo = new TypeInfo(schema);
137 visit(ast, visitWithTypeInfo(typeInfo, {
138 Argument(node) {
139 const argument = typeInfo.getArgument();
140 if (argument) {
141 const reason = argument.deprecationReason;
142 if (reason) {
143 const fieldDef = typeInfo.getFieldDef();
144 if (fieldDef) {
145 errors.push(new GraphQLError(`The argument '${argument === null || argument === void 0 ? void 0 : argument.name}' of '${fieldDef.name}' is deprecated. ${reason}`, [node]));
146 }
147 }
148 }
149 },
150 Field(node) {
151 const fieldDef = typeInfo.getFieldDef();
152 if (fieldDef && fieldDef.isDeprecated) {
153 const parentType = typeInfo.getParentType();
154 if (parentType) {
155 const reason = fieldDef.deprecationReason;
156 errors.push(new GraphQLError(`The field '${parentType.name}.${fieldDef.name}' is deprecated.${reason ? ' ' + reason : ''}`, [node]));
157 }
158 }
159 },
160 EnumValue(node) {
161 const enumVal = typeInfo.getEnumValue();
162 if (enumVal && enumVal.isDeprecated) {
163 const type = getNamedType(typeInfo.getInputType());
164 if (type) {
165 const reason = enumVal.deprecationReason;
166 errors.push(new GraphQLError(`The enum value '${type.name}.${enumVal.name}' is deprecated.${reason ? ' ' + reason : ''}`, [node]));
167 }
168 }
169 },
170 }));
171 return errors;
172}
173function removeFieldIfDirectives(node, directiveNames) {
174 if (node.directives) {
175 if (node.directives.some((d) => directiveNames.indexOf(d.name.value) !== -1)) {
176 return null;
177 }
178 }
179 return node;
180}
181function removeDirectives(node, directiveNames) {
182 if (node.directives) {
183 return Object.assign(Object.assign({}, node), { directives: node.directives.filter((d) => directiveNames.indexOf(d.name.value) === -1) });
184 }
185 return node;
186}
187
188var ChangeType;
189(function (ChangeType) {
190 // Argument
191 ChangeType["FieldArgumentDescriptionChanged"] = "FIELD_ARGUMENT_DESCRIPTION_CHANGED";
192 ChangeType["FieldArgumentDefaultChanged"] = "FIELD_ARGUMENT_DEFAULT_CHANGED";
193 ChangeType["FieldArgumentTypeChanged"] = "FIELD_ARGUMENT_TYPE_CHANGED";
194 // Directive
195 ChangeType["DirectiveRemoved"] = "DIRECTIVE_REMOVED";
196 ChangeType["DirectiveAdded"] = "DIRECTIVE_ADDED";
197 ChangeType["DirectiveDescriptionChanged"] = "DIRECTIVE_DESCRIPTION_CHANGED";
198 ChangeType["DirectiveLocationAdded"] = "DIRECTIVE_LOCATION_ADDED";
199 ChangeType["DirectiveLocationRemoved"] = "DIRECTIVE_LOCATION_REMOVED";
200 ChangeType["DirectiveArgumentAdded"] = "DIRECTIVE_ARGUMENT_ADDED";
201 ChangeType["DirectiveArgumentRemoved"] = "DIRECTIVE_ARGUMENT_REMOVED";
202 ChangeType["DirectiveArgumentDescriptionChanged"] = "DIRECTIVE_ARGUMENT_DESCRIPTION_CHANGED";
203 ChangeType["DirectiveArgumentDefaultValueChanged"] = "DIRECTIVE_ARGUMENT_DEFAULT_VALUE_CHANGED";
204 ChangeType["DirectiveArgumentTypeChanged"] = "DIRECTIVE_ARGUMENT_TYPE_CHANGED";
205 // Enum
206 ChangeType["EnumValueRemoved"] = "ENUM_VALUE_REMOVED";
207 ChangeType["EnumValueAdded"] = "ENUM_VALUE_ADDED";
208 ChangeType["EnumValueDescriptionChanged"] = "ENUM_VALUE_DESCRIPTION_CHANGED";
209 ChangeType["EnumValueDeprecationReasonChanged"] = "ENUM_VALUE_DEPRECATION_REASON_CHANGED";
210 ChangeType["EnumValueDeprecationReasonAdded"] = "ENUM_VALUE_DEPRECATION_REASON_ADDED";
211 ChangeType["EnumValueDeprecationReasonRemoved"] = "ENUM_VALUE_DEPRECATION_REASON_REMOVED";
212 // Field
213 ChangeType["FieldRemoved"] = "FIELD_REMOVED";
214 ChangeType["FieldAdded"] = "FIELD_ADDED";
215 ChangeType["FieldDescriptionChanged"] = "FIELD_DESCRIPTION_CHANGED";
216 ChangeType["FieldDescriptionAdded"] = "FIELD_DESCRIPTION_ADDED";
217 ChangeType["FieldDescriptionRemoved"] = "FIELD_DESCRIPTION_REMOVED";
218 ChangeType["FieldDeprecationAdded"] = "FIELD_DEPRECATION_ADDED";
219 ChangeType["FieldDeprecationRemoved"] = "FIELD_DEPRECATION_REMOVED";
220 ChangeType["FieldDeprecationReasonChanged"] = "FIELD_DEPRECATION_REASON_CHANGED";
221 ChangeType["FieldDeprecationReasonAdded"] = "FIELD_DEPRECATION_REASON_ADDED";
222 ChangeType["FieldDeprecationReasonRemoved"] = "FIELD_DEPRECATION_REASON_REMOVED";
223 ChangeType["FieldTypeChanged"] = "FIELD_TYPE_CHANGED";
224 ChangeType["FieldArgumentAdded"] = "FIELD_ARGUMENT_ADDED";
225 ChangeType["FieldArgumentRemoved"] = "FIELD_ARGUMENT_REMOVED";
226 // Input
227 ChangeType["InputFieldRemoved"] = "INPUT_FIELD_REMOVED";
228 ChangeType["InputFieldAdded"] = "INPUT_FIELD_ADDED";
229 ChangeType["InputFieldDescriptionAdded"] = "INPUT_FIELD_DESCRIPTION_ADDED";
230 ChangeType["InputFieldDescriptionRemoved"] = "INPUT_FIELD_DESCRIPTION_REMOVED";
231 ChangeType["InputFieldDescriptionChanged"] = "INPUT_FIELD_DESCRIPTION_CHANGED";
232 ChangeType["InputFieldDefaultValueChanged"] = "INPUT_FIELD_DEFAULT_VALUE_CHANGED";
233 ChangeType["InputFieldTypeChanged"] = "INPUT_FIELD_TYPE_CHANGED";
234 // Type
235 ChangeType["ObjectTypeInterfaceAdded"] = "OBJECT_TYPE_INTERFACE_ADDED";
236 ChangeType["ObjectTypeInterfaceRemoved"] = "OBJECT_TYPE_INTERFACE_REMOVED";
237 // Schema
238 ChangeType["SchemaQueryTypeChanged"] = "SCHEMA_QUERY_TYPE_CHANGED";
239 ChangeType["SchemaMutationTypeChanged"] = "SCHEMA_MUTATION_TYPE_CHANGED";
240 ChangeType["SchemaSubscriptionTypeChanged"] = "SCHEMA_SUBSCRIPTION_TYPE_CHANGED";
241 // Type
242 ChangeType["TypeRemoved"] = "TYPE_REMOVED";
243 ChangeType["TypeAdded"] = "TYPE_ADDED";
244 ChangeType["TypeKindChanged"] = "TYPE_KIND_CHANGED";
245 ChangeType["TypeDescriptionChanged"] = "TYPE_DESCRIPTION_CHANGED";
246 // TODO
247 ChangeType["TypeDescriptionRemoved"] = "TYPE_DESCRIPTION_REMOVED";
248 // TODO
249 ChangeType["TypeDescriptionAdded"] = "TYPE_DESCRIPTION_ADDED";
250 // Union
251 ChangeType["UnionMemberRemoved"] = "UNION_MEMBER_REMOVED";
252 ChangeType["UnionMemberAdded"] = "UNION_MEMBER_ADDED";
253})(ChangeType || (ChangeType = {}));
254var CriticalityLevel;
255(function (CriticalityLevel) {
256 CriticalityLevel["Breaking"] = "BREAKING";
257 CriticalityLevel["NonBreaking"] = "NON_BREAKING";
258 CriticalityLevel["Dangerous"] = "DANGEROUS";
259})(CriticalityLevel || (CriticalityLevel = {}));
260
261function schemaQueryTypeChanged(oldSchema, newSchema) {
262 const oldName = (oldSchema.getQueryType() || {}).name || 'unknown';
263 const newName = (newSchema.getQueryType() || {}).name || 'unknown';
264 return {
265 criticality: {
266 level: CriticalityLevel.Breaking,
267 },
268 type: ChangeType.SchemaQueryTypeChanged,
269 message: `Schema query root has changed from '${oldName}' to '${newName}'`,
270 };
271}
272function schemaMutationTypeChanged(oldSchema, newSchema) {
273 const oldName = (oldSchema.getMutationType() || {}).name || 'unknown';
274 const newName = (newSchema.getMutationType() || {}).name || 'unknown';
275 return {
276 criticality: {
277 level: CriticalityLevel.Breaking,
278 },
279 type: ChangeType.SchemaMutationTypeChanged,
280 message: `Schema mutation root has changed from '${oldName}' to '${newName}'`,
281 };
282}
283function schemaSubscriptionTypeChanged(oldSchema, newSchema) {
284 const oldName = (oldSchema.getSubscriptionType() || {}).name || 'unknown';
285 const newName = (newSchema.getSubscriptionType() || {}).name || 'unknown';
286 return {
287 criticality: {
288 level: CriticalityLevel.Breaking,
289 },
290 type: ChangeType.SchemaSubscriptionTypeChanged,
291 message: `Schema subscription root has changed from '${oldName}' to '${newName}'`,
292 };
293}
294
295function typeRemoved(type) {
296 return {
297 criticality: {
298 level: CriticalityLevel.Breaking,
299 },
300 type: ChangeType.TypeRemoved,
301 message: `Type '${type.name}' was removed`,
302 path: type.name,
303 };
304}
305function typeAdded(type) {
306 return {
307 criticality: {
308 level: CriticalityLevel.NonBreaking,
309 },
310 type: ChangeType.TypeAdded,
311 message: `Type '${type.name}' was added`,
312 path: type.name,
313 };
314}
315function typeKindChanged(oldType, newType) {
316 return {
317 criticality: {
318 level: CriticalityLevel.Breaking,
319 reason: `Changing the kind of a type is a breaking change because it can cause existing queries to error. For example, turning an object type to a scalar type would break queries that define a selection set for this type.`,
320 },
321 type: ChangeType.TypeKindChanged,
322 message: `'${oldType.name}' kind changed from '${getKind(oldType)}' to '${getKind(newType)}'`,
323 path: oldType.name,
324 };
325}
326function typeDescriptionChanged(oldType, newType) {
327 return {
328 criticality: {
329 level: CriticalityLevel.NonBreaking,
330 },
331 type: ChangeType.TypeDescriptionChanged,
332 message: `Description '${oldType.description}' on type '${oldType.name}' has changed to '${newType.description}'`,
333 path: oldType.name,
334 };
335}
336function typeDescriptionRemoved(type) {
337 return {
338 criticality: {
339 level: CriticalityLevel.NonBreaking,
340 },
341 type: ChangeType.TypeDescriptionRemoved,
342 message: `Description '${type.description}' was removed from object type '${type.name}'`,
343 path: type.name,
344 };
345}
346function typeDescriptionAdded(type) {
347 return {
348 criticality: {
349 level: CriticalityLevel.NonBreaking,
350 },
351 type: ChangeType.TypeDescriptionAdded,
352 message: `Object type '${type.name}' has description '${type.description}'`,
353 path: type.name,
354 };
355}
356
357function directiveRemoved(directive) {
358 return {
359 criticality: {
360 level: CriticalityLevel.Breaking,
361 },
362 type: ChangeType.DirectiveRemoved,
363 message: `Directive '${directive.name}' was removed`,
364 path: `@${directive.name}`,
365 };
366}
367function directiveAdded(directive) {
368 return {
369 criticality: {
370 level: CriticalityLevel.NonBreaking,
371 },
372 type: ChangeType.DirectiveAdded,
373 message: `Directive '${directive.name}' was added`,
374 path: `@${directive.name}`,
375 };
376}
377function directiveDescriptionChanged(oldDirective, newDirective) {
378 return {
379 criticality: {
380 level: CriticalityLevel.NonBreaking,
381 },
382 type: ChangeType.DirectiveDescriptionChanged,
383 message: `Directive '${oldDirective.name}' description changed from '${oldDirective.description}' to '${newDirective.description}'`,
384 path: `@${oldDirective.name}`,
385 };
386}
387function directiveLocationAdded(directive, location) {
388 return {
389 criticality: {
390 level: CriticalityLevel.NonBreaking,
391 },
392 type: ChangeType.DirectiveLocationAdded,
393 message: `Location '${location}' was added to directive '${directive.name}'`,
394 path: `@${directive.name}`,
395 };
396}
397function directiveLocationRemoved(directive, location) {
398 return {
399 criticality: {
400 level: CriticalityLevel.Breaking,
401 },
402 type: ChangeType.DirectiveLocationRemoved,
403 message: `Location '${location}' was removed from directive '${directive.name}'`,
404 path: `@${directive.name}`,
405 };
406}
407function directiveArgumentAdded(directive, arg) {
408 return {
409 criticality: {
410 level: isNonNullType(arg.type)
411 ? CriticalityLevel.Breaking
412 : CriticalityLevel.NonBreaking,
413 },
414 type: ChangeType.DirectiveArgumentAdded,
415 message: `Argument '${arg.name}' was added to directive '${directive.name}'`,
416 path: `@${directive.name}`,
417 };
418}
419function directiveArgumentRemoved(directive, arg) {
420 return {
421 criticality: {
422 level: CriticalityLevel.Breaking,
423 },
424 type: ChangeType.DirectiveArgumentRemoved,
425 message: `Argument '${arg.name}' was removed from directive '${directive.name}'`,
426 path: `@${directive.name}.${arg.name}`,
427 };
428}
429function directiveArgumentDescriptionChanged(directive, oldArg, newArg) {
430 return {
431 criticality: {
432 level: CriticalityLevel.NonBreaking,
433 },
434 type: ChangeType.DirectiveArgumentDescriptionChanged,
435 message: `Description for argument '${oldArg.name}' on directive '${directive.name}' changed from '${oldArg.description}' to '${newArg.description}'`,
436 path: `@${directive.name}.${oldArg.name}`,
437 };
438}
439function directiveArgumentDefaultValueChanged(directive, oldArg, newArg) {
440 return {
441 criticality: {
442 level: CriticalityLevel.Dangerous,
443 reason: 'Changing the default value for an argument may change the runtime behaviour of a field if it was never provided.',
444 },
445 type: ChangeType.DirectiveArgumentDefaultValueChanged,
446 message: typeof oldArg.defaultValue === 'undefined'
447 ? `Default value '${newArg.defaultValue}' was added to argument '${newArg.name}' on directive '${directive.name}'`
448 : `Default value for argument '${oldArg.name}' on directive '${directive.name}' changed from '${oldArg.defaultValue}' to '${newArg.defaultValue}'`,
449 path: `@${directive.name}.${oldArg.name}`,
450 };
451}
452function directiveArgumentTypeChanged(directive, oldArg, newArg) {
453 return {
454 criticality: safeChangeForInputValue(oldArg.type, newArg.type)
455 ? {
456 level: CriticalityLevel.NonBreaking,
457 reason: 'Changing an input field from non-null to null is considered non-breaking.',
458 }
459 : {
460 level: CriticalityLevel.Breaking,
461 },
462 type: ChangeType.DirectiveArgumentTypeChanged,
463 message: `Type for argument '${oldArg.name}' on directive '${directive.name}' changed from '${oldArg.type}' to '${newArg.type}'`,
464 path: `@${directive.name}.${oldArg.name}`,
465 };
466}
467
468function enumValueRemoved(oldEnum, value) {
469 return {
470 criticality: {
471 level: CriticalityLevel.Breaking,
472 reason: `Removing an enum value will cause existing queries that use this enum value to error.`,
473 },
474 type: ChangeType.EnumValueRemoved,
475 message: `Enum value '${value.name}' ${value.isDeprecated ? '(deprecated) ' : ''}was removed from enum '${oldEnum.name}'`,
476 path: [oldEnum.name, value.name].join('.'),
477 };
478}
479function enumValueAdded(newEnum, value) {
480 return {
481 criticality: {
482 level: CriticalityLevel.Dangerous,
483 reason: `Adding an enum value may break existing clients that were not programming defensively against an added case when querying an enum.`,
484 },
485 type: ChangeType.EnumValueAdded,
486 message: `Enum value '${value.name}' was added to enum '${newEnum.name}'`,
487 path: [newEnum.name, value.name].join('.'),
488 };
489}
490function enumValueDescriptionChanged(newEnum, oldValue, newValue) {
491 return {
492 criticality: {
493 level: CriticalityLevel.NonBreaking,
494 },
495 type: ChangeType.EnumValueDescriptionChanged,
496 message: typeof oldValue.description === 'undefined'
497 ? `Description '${newValue.description}' was added to enum value '${newEnum.name}.${newValue.name}'`
498 : `Description for enum value '${newEnum.name}.${newValue.name}' changed from '${oldValue.description}' to '${newValue.description}'`,
499 path: [newEnum.name, oldValue.name].join('.'),
500 };
501}
502function enumValueDeprecationReasonChanged(newEnum, oldValue, newValue) {
503 return {
504 criticality: {
505 level: CriticalityLevel.NonBreaking,
506 },
507 type: ChangeType.EnumValueDeprecationReasonChanged,
508 message: `Enum value '${newEnum.name}.${newValue.name}' deprecation reason changed from '${oldValue.deprecationReason}' to '${newValue.deprecationReason}'`,
509 path: [newEnum.name, oldValue.name].join('.'),
510 };
511}
512function enumValueDeprecationReasonAdded(newEnum, oldValue, newValue) {
513 return {
514 criticality: {
515 level: CriticalityLevel.NonBreaking,
516 },
517 type: ChangeType.EnumValueDeprecationReasonAdded,
518 message: `Enum value '${newEnum.name}.${newValue.name}' was deprecated with reason '${newValue.deprecationReason}'`,
519 path: [newEnum.name, oldValue.name].join('.'),
520 };
521}
522function enumValueDeprecationReasonRemoved(newEnum, oldValue, newValue) {
523 return {
524 criticality: {
525 level: CriticalityLevel.NonBreaking,
526 },
527 type: ChangeType.EnumValueDeprecationReasonRemoved,
528 message: `Deprecation reason was removed from enum value '${newEnum.name}.${newValue.name}'`,
529 path: [newEnum.name, oldValue.name].join('.'),
530 };
531}
532
533function changesInEnum(oldEnum, newEnum, addChange) {
534 compareLists(oldEnum.getValues(), newEnum.getValues(), {
535 onAdded(value) {
536 addChange(enumValueAdded(newEnum, value));
537 },
538 onRemoved(value) {
539 addChange(enumValueRemoved(oldEnum, value));
540 },
541 onMutual(value) {
542 const oldValue = value.oldVersion;
543 const newValue = value.newVersion;
544 if (isNotEqual(oldValue.description, newValue.description)) {
545 addChange(enumValueDescriptionChanged(newEnum, oldValue, newValue));
546 }
547 if (isNotEqual(oldValue.deprecationReason, newValue.deprecationReason)) {
548 if (isVoid(oldValue.deprecationReason)) {
549 addChange(enumValueDeprecationReasonAdded(newEnum, oldValue, newValue));
550 }
551 else if (isVoid(newValue.deprecationReason)) {
552 addChange(enumValueDeprecationReasonRemoved(newEnum, oldValue, newValue));
553 }
554 else {
555 addChange(enumValueDeprecationReasonChanged(newEnum, oldValue, newValue));
556 }
557 }
558 }
559 });
560}
561
562function unionMemberRemoved(union, type) {
563 return {
564 criticality: {
565 level: CriticalityLevel.Breaking,
566 reason: 'Removing a union member from a union can cause existing queries that use this union member in a fragment spread to error.',
567 },
568 type: ChangeType.UnionMemberRemoved,
569 message: `Member '${type.name}' was removed from Union type '${union.name}'`,
570 path: union.name,
571 };
572}
573function unionMemberAdded(union, type) {
574 return {
575 criticality: {
576 level: CriticalityLevel.Dangerous,
577 reason: 'Adding a possible type to Unions may break existing clients that were not programming defensively against a new possible type.',
578 },
579 type: ChangeType.UnionMemberAdded,
580 message: `Member '${type.name}' was added to Union type '${union.name}'`,
581 path: union.name,
582 };
583}
584
585function changesInUnion(oldUnion, newUnion, addChange) {
586 const oldTypes = oldUnion.getTypes();
587 const newTypes = newUnion.getTypes();
588 compareLists(oldTypes, newTypes, {
589 onAdded(t) {
590 addChange(unionMemberAdded(newUnion, t));
591 },
592 onRemoved(t) {
593 addChange(unionMemberRemoved(oldUnion, t));
594 },
595 });
596}
597
598function inputFieldRemoved(input, field) {
599 return {
600 criticality: {
601 level: CriticalityLevel.Breaking,
602 reason: 'Removing an input field will cause existing queries that use this input field to error.',
603 },
604 type: ChangeType.InputFieldRemoved,
605 message: `Input field '${field.name}' was removed from input object type '${input.name}'`,
606 path: [input.name, field.name].join('.'),
607 };
608}
609function inputFieldAdded(input, field) {
610 return {
611 criticality: isNonNullType(field.type)
612 ? {
613 level: CriticalityLevel.Breaking,
614 reason: 'Adding a required input field to an existing input object type is a breaking change because it will cause existing uses of this input object type to error.',
615 }
616 : {
617 level: CriticalityLevel.Dangerous,
618 },
619 type: ChangeType.InputFieldAdded,
620 message: `Input field '${field.name}' was added to input object type '${input.name}'`,
621 path: [input.name, field.name].join('.'),
622 };
623}
624function inputFieldDescriptionAdded(type, field) {
625 return {
626 criticality: {
627 level: CriticalityLevel.NonBreaking,
628 },
629 type: ChangeType.InputFieldDescriptionAdded,
630 message: `Input field '${type.name}.${field.name}' has description '${field.description}'`,
631 path: [type.name, field.name].join('.'),
632 };
633}
634function inputFieldDescriptionRemoved(type, field) {
635 return {
636 criticality: {
637 level: CriticalityLevel.NonBreaking,
638 },
639 type: ChangeType.InputFieldDescriptionRemoved,
640 message: `Description was removed from input field '${type.name}.${field.name}'`,
641 path: [type.name, field.name].join('.'),
642 };
643}
644function inputFieldDescriptionChanged(input, oldField, newField) {
645 return {
646 criticality: {
647 level: CriticalityLevel.NonBreaking,
648 },
649 type: ChangeType.InputFieldDescriptionChanged,
650 message: `Input field '${input.name}.${oldField.name}' description changed from '${oldField.description}' to '${newField.description}'`,
651 path: [input.name, oldField.name].join('.'),
652 };
653}
654function inputFieldDefaultValueChanged(input, oldField, newField) {
655 return {
656 criticality: {
657 level: CriticalityLevel.Dangerous,
658 reason: 'Changing the default value for an argument may change the runtime behaviour of a field if it was never provided.',
659 },
660 type: ChangeType.InputFieldDefaultValueChanged,
661 message: `Input field '${input.name}.${oldField.name}' default value changed from '${oldField.defaultValue}' to '${newField.defaultValue}'`,
662 path: [input.name, oldField.name].join('.'),
663 };
664}
665function inputFieldTypeChanged(input, oldField, newField) {
666 return {
667 criticality: safeChangeForInputValue(oldField.type, newField.type)
668 ? {
669 level: CriticalityLevel.NonBreaking,
670 reason: 'Changing an input field from non-null to null is considered non-breaking.',
671 }
672 : {
673 level: CriticalityLevel.Breaking,
674 reason: 'Changing the type of an input field can cause existing queries that use this field to error.',
675 },
676 type: ChangeType.InputFieldTypeChanged,
677 message: `Input field '${input.name}.${oldField.name}' changed type from '${oldField.type.toString()}' to '${newField.type.toString()}'`,
678 path: [input.name, oldField.name].join('.'),
679 };
680}
681
682function changesInInputObject(oldInput, newInput, addChange) {
683 const oldFields = oldInput.getFields();
684 const newFields = newInput.getFields();
685 compareLists(Object.values(oldFields), Object.values(newFields), {
686 onAdded(field) {
687 addChange(inputFieldAdded(newInput, field));
688 },
689 onRemoved(field) {
690 addChange(inputFieldRemoved(oldInput, field));
691 },
692 onMutual(field) {
693 changesInInputField(oldInput, field.oldVersion, field.newVersion, addChange);
694 },
695 });
696}
697function changesInInputField(input, oldField, newField, addChange) {
698 if (isNotEqual(oldField.description, newField.description)) {
699 if (isVoid(oldField.description)) {
700 addChange(inputFieldDescriptionAdded(input, newField));
701 }
702 else if (isVoid(newField.description)) {
703 addChange(inputFieldDescriptionRemoved(input, oldField));
704 }
705 else {
706 addChange(inputFieldDescriptionChanged(input, oldField, newField));
707 }
708 }
709 if (isNotEqual(oldField.defaultValue, newField.defaultValue)) {
710 if (Array.isArray(oldField.defaultValue) &&
711 Array.isArray(newField.defaultValue)) {
712 if (diffArrays(oldField.defaultValue, newField.defaultValue).length > 0) {
713 addChange(inputFieldDefaultValueChanged(input, oldField, newField));
714 }
715 }
716 else if (JSON.stringify(oldField.defaultValue) !==
717 JSON.stringify(newField.defaultValue)) {
718 addChange(inputFieldDefaultValueChanged(input, oldField, newField));
719 }
720 }
721 if (isNotEqual(oldField.type.toString(), newField.type.toString())) {
722 addChange(inputFieldTypeChanged(input, oldField, newField));
723 }
724}
725
726function objectTypeInterfaceAdded(iface, type) {
727 return {
728 criticality: {
729 level: CriticalityLevel.Dangerous,
730 reason: 'Adding an interface to an object type may break existing clients that were not programming defensively against a new possible type.',
731 },
732 type: ChangeType.ObjectTypeInterfaceAdded,
733 message: `'${type.name}' object implements '${iface.name}' interface`,
734 path: type.name,
735 };
736}
737function objectTypeInterfaceRemoved(iface, type) {
738 return {
739 criticality: {
740 level: CriticalityLevel.Breaking,
741 reason: 'Removing an interface from an object type can cause existing queries that use this in a fragment spread to error.',
742 },
743 type: ChangeType.ObjectTypeInterfaceRemoved,
744 message: `'${type.name}' object type no longer implements '${iface.name}' interface`,
745 path: type.name,
746 };
747}
748
749function fieldRemoved(type, field) {
750 const entity = isInterfaceType(type) ? 'interface' : 'object type';
751 return {
752 criticality: {
753 level: CriticalityLevel.Breaking,
754 reason: field.deprecationReason
755 ? `Removing a deprecated field is a breaking change. Before removing it, you may want to look at the field's usage to see the impact of removing the field.`
756 : `Removing a field is a breaking change. It is preferable to deprecate the field before removing it.`,
757 },
758 type: ChangeType.FieldRemoved,
759 message: `Field '${field.name}' ${field.isDeprecated ? '(deprecated) ' : ''}was removed from ${entity} '${type.name}'`,
760 path: [type.name, field.name].join('.'),
761 };
762}
763function fieldAdded(type, field) {
764 const entity = isInterfaceType(type) ? 'interface' : 'object type';
765 return {
766 criticality: {
767 level: CriticalityLevel.NonBreaking,
768 },
769 type: ChangeType.FieldAdded,
770 message: `Field '${field.name}' was added to ${entity} '${type.name}'`,
771 path: [type.name, field.name].join('.'),
772 };
773}
774function fieldDescriptionChanged(type, oldField, newField) {
775 return {
776 criticality: {
777 level: CriticalityLevel.NonBreaking,
778 },
779 type: ChangeType.FieldDescriptionChanged,
780 message: `Field '${type.name}.${oldField.name}' description changed from '${oldField.description}' to '${newField.description}'`,
781 path: [type.name, oldField.name].join('.'),
782 };
783}
784function fieldDescriptionAdded(type, field) {
785 return {
786 criticality: {
787 level: CriticalityLevel.NonBreaking,
788 },
789 type: ChangeType.FieldDescriptionAdded,
790 message: `Field '${type.name}.${field.name}' has description '${field.description}'`,
791 path: [type.name, field.name].join('.'),
792 };
793}
794function fieldDescriptionRemoved(type, field) {
795 return {
796 criticality: {
797 level: CriticalityLevel.NonBreaking,
798 },
799 type: ChangeType.FieldDescriptionRemoved,
800 message: `Description was removed from field '${type.name}.${field.name}'`,
801 path: [type.name, field.name].join('.'),
802 };
803}
804function fieldDeprecationAdded(type, field) {
805 return {
806 criticality: {
807 level: CriticalityLevel.NonBreaking,
808 },
809 type: ChangeType.FieldDeprecationAdded,
810 message: `Field '${type.name}.${field.name}' is deprecated`,
811 path: [type.name, field.name].join('.'),
812 };
813}
814function fieldDeprecationRemoved(type, field) {
815 return {
816 criticality: {
817 level: CriticalityLevel.Dangerous,
818 },
819 type: ChangeType.FieldDeprecationRemoved,
820 message: `Field '${type.name}.${field.name}' is no longer deprecated`,
821 path: [type.name, field.name].join('.'),
822 };
823}
824function fieldDeprecationReasonChanged(type, oldField, newField) {
825 return {
826 criticality: {
827 level: CriticalityLevel.NonBreaking,
828 },
829 type: ChangeType.FieldDeprecationReasonChanged,
830 message: `Deprecation reason on field '${type.name}.${newField.name}' has changed from '${oldField.deprecationReason}' to '${newField.deprecationReason}'`,
831 path: [type.name, oldField.name].join('.'),
832 };
833}
834function fieldDeprecationReasonAdded(type, field) {
835 return {
836 criticality: {
837 level: CriticalityLevel.NonBreaking,
838 },
839 type: ChangeType.FieldDeprecationReasonAdded,
840 message: `Field '${type.name}.${field.name}' has deprecation reason '${field.deprecationReason}'`,
841 path: [type.name, field.name].join('.'),
842 };
843}
844function fieldDeprecationReasonRemoved(type, field) {
845 return {
846 criticality: {
847 level: CriticalityLevel.NonBreaking,
848 },
849 type: ChangeType.FieldDeprecationReasonRemoved,
850 message: `Deprecation reason was removed from field '${type.name}.${field.name}'`,
851 path: [type.name, field.name].join('.'),
852 };
853}
854function fieldTypeChanged(type, oldField, newField) {
855 return {
856 criticality: {
857 level: safeChangeForField(oldField.type, newField.type)
858 ? CriticalityLevel.NonBreaking
859 : CriticalityLevel.Breaking,
860 },
861 type: ChangeType.FieldTypeChanged,
862 message: `Field '${type}.${oldField.name}' changed type from '${oldField.type}' to '${newField.type}'`,
863 path: [type.name, oldField.name].join('.'),
864 };
865}
866function fieldArgumentAdded(type, field, arg) {
867 return {
868 criticality: isNonNullType(arg.type)
869 ? {
870 level: CriticalityLevel.Breaking,
871 reason: `Adding a required argument to an existing field is a breaking change because it will cause existing uses of this field to error.`,
872 }
873 : {
874 level: CriticalityLevel.Dangerous,
875 reason: `Adding a new argument to an existing field may involve a change in resolve function logic that potentially may cause some side effects.`,
876 },
877 type: ChangeType.FieldArgumentAdded,
878 message: `Argument '${arg.name}: ${arg.type}' added to field '${type.name}.${field.name}'`,
879 path: [type.name, field.name, arg.name].join('.'),
880 };
881}
882function fieldArgumentRemoved(type, field, arg) {
883 return {
884 criticality: {
885 level: CriticalityLevel.Breaking,
886 reason: `Removing a field argument is a breaking change because it will cause existing queries that use this argument to error.`,
887 },
888 type: ChangeType.FieldArgumentRemoved,
889 message: `Argument '${arg.name}: ${arg.type}' was removed from field '${type.name}.${field.name}'`,
890 path: [type.name, field.name, arg.name].join('.'),
891 };
892}
893
894function compareTwoStrings(str1, str2) {
895 if (!str1.length && !str2.length)
896 return 1;
897 if (!str1.length || !str2.length)
898 return 0;
899 if (str1.toUpperCase() === str2.toUpperCase())
900 return 1;
901 if (str1.length === 1 && str2.length === 1)
902 return 0;
903 const pairs1 = wordLetterPairs(str1);
904 const pairs2 = wordLetterPairs(str2);
905 const union = pairs1.length + pairs2.length;
906 let intersection = 0;
907 pairs1.forEach((pair1) => {
908 for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
909 if (pair1 !== pair2)
910 continue;
911 intersection++;
912 pairs2.splice(i, 1);
913 break;
914 }
915 });
916 return (intersection * 2) / union;
917}
918function findBestMatch(mainString, targetStrings) {
919 if (!areArgsValid(mainString, targetStrings))
920 throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
921 const ratings = targetStrings.map((target) => ({
922 target,
923 rating: compareTwoStrings(mainString, target.value),
924 }));
925 const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
926 return { ratings, bestMatch };
927}
928function flattenDeep(arr) {
929 return Array.isArray(arr)
930 ? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
931 : [arr];
932}
933function areArgsValid(mainString, targetStrings) {
934 if (typeof mainString !== 'string')
935 return false;
936 if (!Array.isArray(targetStrings))
937 return false;
938 if (!targetStrings.length)
939 return false;
940 if (targetStrings.find((s) => typeof s.value !== 'string'))
941 return false;
942 return true;
943}
944function letterPairs(str) {
945 const pairs = [];
946 for (let i = 0, max = str.length - 1; i < max; i++)
947 pairs[i] = str.substring(i, i + 2);
948 return pairs;
949}
950function wordLetterPairs(str) {
951 const pairs = str.toUpperCase().split(' ').map(letterPairs);
952 return flattenDeep(pairs);
953}
954function safeString(obj) {
955 if (obj != null && typeof obj.toString === 'function') {
956 return `${obj}`;
957 }
958 return inspect(obj);
959}
960
961function fieldArgumentDescriptionChanged(type, field, oldArg, newArg) {
962 return {
963 criticality: {
964 level: CriticalityLevel.NonBreaking,
965 },
966 type: ChangeType.FieldArgumentDescriptionChanged,
967 message: `Description for argument '${newArg.name}' on field '${type.name}.${field.name}' changed from '${oldArg.description}' to '${newArg.description}'`,
968 path: [type.name, field.name, oldArg.name].join('.'),
969 };
970}
971function fieldArgumentDefaultChanged(type, field, oldArg, newArg) {
972 return {
973 criticality: {
974 level: CriticalityLevel.Dangerous,
975 reason: 'Changing the default value for an argument may change the runtime behaviour of a field if it was never provided.',
976 },
977 type: ChangeType.FieldArgumentDefaultChanged,
978 message: typeof oldArg.defaultValue === 'undefined'
979 ? `Default value '${safeString(newArg.defaultValue)}' was added to argument '${newArg.name}' on field '${type.name}.${field.name}'`
980 : `Default value for argument '${newArg.name}' on field '${type.name}.${field.name}' changed from '${safeString(oldArg.defaultValue)}' to '${safeString(newArg.defaultValue)}'`,
981 path: [type.name, field.name, oldArg.name].join('.'),
982 };
983}
984function fieldArgumentTypeChanged(type, field, oldArg, newArg) {
985 return {
986 criticality: safeChangeForInputValue(oldArg.type, newArg.type)
987 ? {
988 level: CriticalityLevel.NonBreaking,
989 reason: `Changing an input field from non-null to null is considered non-breaking.`,
990 }
991 : {
992 level: CriticalityLevel.Breaking,
993 reason: `Changing the type of a field's argument can cause existing queries that use this argument to error.`,
994 },
995 type: ChangeType.FieldArgumentTypeChanged,
996 message: `Type for argument '${newArg.name}' on field '${type.name}.${field.name}' changed from '${oldArg.type}' to '${newArg.type}'`,
997 path: [type.name, field.name, oldArg.name].join('.'),
998 };
999}
1000
1001function changesInArgument(type, field, oldArg, newArg, addChange) {
1002 if (isNotEqual(oldArg.description, newArg.description)) {
1003 addChange(fieldArgumentDescriptionChanged(type, field, oldArg, newArg));
1004 }
1005 if (isNotEqual(oldArg.defaultValue, newArg.defaultValue)) {
1006 if (Array.isArray(oldArg.defaultValue) &&
1007 Array.isArray(newArg.defaultValue)) {
1008 const diff = diffArrays(oldArg.defaultValue, newArg.defaultValue);
1009 if (diff.length > 0) {
1010 addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
1011 }
1012 }
1013 else if (JSON.stringify(oldArg.defaultValue) !==
1014 JSON.stringify(newArg.defaultValue)) {
1015 addChange(fieldArgumentDefaultChanged(type, field, oldArg, newArg));
1016 }
1017 }
1018 if (isNotEqual(oldArg.type.toString(), newArg.type.toString())) {
1019 addChange(fieldArgumentTypeChanged(type, field, oldArg, newArg));
1020 }
1021}
1022
1023function changesInField(type, oldField, newField, addChange) {
1024 if (isNotEqual(oldField.description, newField.description)) {
1025 if (isVoid(oldField.description)) {
1026 addChange(fieldDescriptionAdded(type, newField));
1027 }
1028 else if (isVoid(newField.description)) {
1029 addChange(fieldDescriptionRemoved(type, oldField));
1030 }
1031 else {
1032 addChange(fieldDescriptionChanged(type, oldField, newField));
1033 }
1034 }
1035 if (isNotEqual(oldField.isDeprecated, newField.isDeprecated)) {
1036 if (newField.isDeprecated) {
1037 addChange(fieldDeprecationAdded(type, newField));
1038 }
1039 else {
1040 addChange(fieldDeprecationRemoved(type, oldField));
1041 }
1042 }
1043 if (isNotEqual(oldField.deprecationReason, newField.deprecationReason)) {
1044 if (isVoid(oldField.deprecationReason)) {
1045 addChange(fieldDeprecationReasonAdded(type, newField));
1046 }
1047 else if (isVoid(newField.deprecationReason)) {
1048 addChange(fieldDeprecationReasonRemoved(type, oldField));
1049 }
1050 else {
1051 addChange(fieldDeprecationReasonChanged(type, oldField, newField));
1052 }
1053 }
1054 if (isNotEqual(oldField.type.toString(), newField.type.toString())) {
1055 addChange(fieldTypeChanged(type, oldField, newField));
1056 }
1057 compareLists(oldField.args, newField.args, {
1058 onAdded(arg) {
1059 addChange(fieldArgumentAdded(type, newField, arg));
1060 },
1061 onRemoved(arg) {
1062 addChange(fieldArgumentRemoved(type, oldField, arg));
1063 },
1064 onMutual(arg) {
1065 changesInArgument(type, oldField, arg.oldVersion, arg.newVersion, addChange);
1066 }
1067 });
1068}
1069
1070function changesInObject(oldType, newType, addChange) {
1071 const oldInterfaces = oldType.getInterfaces();
1072 const newInterfaces = newType.getInterfaces();
1073 const oldFields = oldType.getFields();
1074 const newFields = newType.getFields();
1075 compareLists(oldInterfaces, newInterfaces, {
1076 onAdded(i) {
1077 addChange(objectTypeInterfaceAdded(i, newType));
1078 },
1079 onRemoved(i) {
1080 addChange(objectTypeInterfaceRemoved(i, oldType));
1081 },
1082 });
1083 compareLists(Object.values(oldFields), Object.values(newFields), {
1084 onAdded(f) {
1085 addChange(fieldAdded(newType, f));
1086 },
1087 onRemoved(f) {
1088 addChange(fieldRemoved(oldType, f));
1089 },
1090 onMutual(f) {
1091 changesInField(oldType, f.oldVersion, f.newVersion, addChange);
1092 },
1093 });
1094}
1095
1096function changesInInterface(oldInterface, newInterface, addChange) {
1097 compareLists(Object.values(oldInterface.getFields()), Object.values(newInterface.getFields()), {
1098 onAdded(field) {
1099 addChange(fieldAdded(newInterface, field));
1100 },
1101 onRemoved(field) {
1102 addChange(fieldRemoved(oldInterface, field));
1103 },
1104 onMutual(field) {
1105 changesInField(oldInterface, field.oldVersion, field.newVersion, addChange);
1106 },
1107 });
1108}
1109
1110function changesInDirective(oldDirective, newDirective, addChange) {
1111 if (isNotEqual(oldDirective.description, newDirective.description)) {
1112 addChange(directiveDescriptionChanged(oldDirective, newDirective));
1113 }
1114 const locations = {
1115 added: diffArrays(newDirective.locations, oldDirective.locations),
1116 removed: diffArrays(oldDirective.locations, newDirective.locations),
1117 };
1118 // locations added
1119 locations.added.forEach((location) => addChange(directiveLocationAdded(newDirective, location)));
1120 // locations removed
1121 locations.removed.forEach((location) => addChange(directiveLocationRemoved(oldDirective, location)));
1122 compareLists(oldDirective.args, newDirective.args, {
1123 onAdded(arg) {
1124 addChange(directiveArgumentAdded(newDirective, arg));
1125 },
1126 onRemoved(arg) {
1127 addChange(directiveArgumentRemoved(oldDirective, arg));
1128 },
1129 onMutual(arg) {
1130 changesInDirectiveArgument(oldDirective, arg.oldVersion, arg.newVersion, addChange);
1131 },
1132 });
1133}
1134function changesInDirectiveArgument(directive, oldArg, newArg, addChange) {
1135 if (isNotEqual(oldArg.description, newArg.description)) {
1136 addChange(directiveArgumentDescriptionChanged(directive, oldArg, newArg));
1137 }
1138 if (isNotEqual(oldArg.defaultValue, newArg.defaultValue)) {
1139 addChange(directiveArgumentDefaultValueChanged(directive, oldArg, newArg));
1140 }
1141 if (isNotEqual(oldArg.type.toString(), newArg.type.toString())) {
1142 addChange(directiveArgumentTypeChanged(directive, oldArg, newArg));
1143 }
1144}
1145
1146function diffSchema(oldSchema, newSchema) {
1147 const changes = [];
1148 function addChange(change) {
1149 changes.push(change);
1150 }
1151 changesInSchema(oldSchema, newSchema, addChange);
1152 compareLists(Object.values(oldSchema.getTypeMap()).filter((t) => !isPrimitive(t)), Object.values(newSchema.getTypeMap()).filter((t) => !isPrimitive(t)), {
1153 onAdded(type) {
1154 addChange(typeAdded(type));
1155 },
1156 onRemoved(type) {
1157 addChange(typeRemoved(type));
1158 },
1159 onMutual(type) {
1160 changesInType(type.oldVersion, type.newVersion, addChange);
1161 },
1162 });
1163 compareLists(oldSchema.getDirectives(), newSchema.getDirectives(), {
1164 onAdded(directive) {
1165 addChange(directiveAdded(directive));
1166 },
1167 onRemoved(directive) {
1168 addChange(directiveRemoved(directive));
1169 },
1170 onMutual(directive) {
1171 changesInDirective(directive.oldVersion, directive.newVersion, addChange);
1172 },
1173 });
1174 return changes;
1175}
1176function changesInSchema(oldSchema, newSchema, addChange) {
1177 var _a, _b, _c, _d, _e, _f;
1178 const defaultNames = {
1179 query: 'Query',
1180 mutation: 'Mutation',
1181 subscription: 'Subscription',
1182 };
1183 const oldRoot = {
1184 query: (_a = (oldSchema.getQueryType() || {}).name) !== null && _a !== void 0 ? _a : defaultNames.query,
1185 mutation: (_b = (oldSchema.getMutationType() || {}).name) !== null && _b !== void 0 ? _b : defaultNames.mutation,
1186 subscription: (_c = (oldSchema.getSubscriptionType() || {}).name) !== null && _c !== void 0 ? _c : defaultNames.subscription,
1187 };
1188 const newRoot = {
1189 query: (_d = (newSchema.getQueryType() || {}).name) !== null && _d !== void 0 ? _d : defaultNames.query,
1190 mutation: (_e = (newSchema.getMutationType() || {}).name) !== null && _e !== void 0 ? _e : defaultNames.mutation,
1191 subscription: (_f = (newSchema.getSubscriptionType() || {}).name) !== null && _f !== void 0 ? _f : defaultNames.subscription,
1192 };
1193 if (isNotEqual(oldRoot.query, newRoot.query)) {
1194 addChange(schemaQueryTypeChanged(oldSchema, newSchema));
1195 }
1196 if (isNotEqual(oldRoot.mutation, newRoot.mutation)) {
1197 addChange(schemaMutationTypeChanged(oldSchema, newSchema));
1198 }
1199 if (isNotEqual(oldRoot.subscription, newRoot.subscription)) {
1200 addChange(schemaSubscriptionTypeChanged(oldSchema, newSchema));
1201 }
1202}
1203function changesInType(oldType, newType, addChange) {
1204 if (isEnumType(oldType) && isEnumType(newType)) {
1205 changesInEnum(oldType, newType, addChange);
1206 }
1207 else if (isUnionType(oldType) && isUnionType(newType)) {
1208 changesInUnion(oldType, newType, addChange);
1209 }
1210 else if (isInputObjectType(oldType) && isInputObjectType(newType)) {
1211 changesInInputObject(oldType, newType, addChange);
1212 }
1213 else if (isObjectType(oldType) && isObjectType(newType)) {
1214 changesInObject(oldType, newType, addChange);
1215 }
1216 else if (isInterfaceType(oldType) && isInterfaceType(newType)) {
1217 changesInInterface(oldType, newType, addChange);
1218 }
1219 else if (isScalarType(oldType) && isScalarType(newType)) ;
1220 else {
1221 addChange(typeKindChanged(oldType, newType));
1222 }
1223 if (isNotEqual(oldType.description, newType.description)) {
1224 if (isVoid(oldType.description)) {
1225 addChange(typeDescriptionAdded(newType));
1226 }
1227 else if (isVoid(newType.description)) {
1228 addChange(typeDescriptionRemoved(oldType));
1229 }
1230 else {
1231 addChange(typeDescriptionChanged(oldType, newType));
1232 }
1233 }
1234}
1235
1236const dangerousBreaking = ({ changes }) => {
1237 return changes.map((change) => {
1238 if (change.criticality.level === CriticalityLevel.Dangerous) {
1239 return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Breaking }) });
1240 }
1241 return change;
1242 });
1243};
1244
1245function parsePath(path) {
1246 return path.split('.');
1247}
1248
1249const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
1250 return changes.map((change) => {
1251 if (change.type === ChangeType.FieldRemoved &&
1252 change.criticality.level === CriticalityLevel.Breaking &&
1253 change.path) {
1254 const [typeName, fieldName] = parsePath(change.path);
1255 const type = oldSchema.getType(typeName);
1256 if (isObjectType(type) || isInterfaceType(type)) {
1257 const field = type.getFields()[fieldName];
1258 if (field.isDeprecated) {
1259 return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }) });
1260 }
1261 }
1262 }
1263 if (change.type === ChangeType.EnumValueRemoved &&
1264 change.criticality.level === CriticalityLevel.Breaking &&
1265 change.path) {
1266 const [enumName, enumItem] = parsePath(change.path);
1267 const type = oldSchema.getType(enumName);
1268 if (isEnumType(type)) {
1269 const item = type.getValue(enumItem);
1270 if (item && item.isDeprecated) {
1271 return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }) });
1272 }
1273 }
1274 }
1275 return change;
1276 });
1277};
1278
1279const descriptionChangeTypes = [
1280 ChangeType.FieldArgumentDescriptionChanged,
1281 ChangeType.DirectiveDescriptionChanged,
1282 ChangeType.DirectiveArgumentDescriptionChanged,
1283 ChangeType.EnumValueDescriptionChanged,
1284 ChangeType.FieldDescriptionChanged,
1285 ChangeType.FieldDescriptionAdded,
1286 ChangeType.FieldDescriptionRemoved,
1287 ChangeType.InputFieldDescriptionAdded,
1288 ChangeType.InputFieldDescriptionRemoved,
1289 ChangeType.InputFieldDescriptionChanged,
1290 ChangeType.TypeDescriptionChanged,
1291];
1292const ignoreDescriptionChanges = ({ changes }) => {
1293 return changes.filter((change) => descriptionChangeTypes.indexOf(change.type) === -1);
1294};
1295
1296const considerUsage = ({ changes, config, }) => __awaiter(void 0, void 0, void 0, function* () {
1297 if (!config) {
1298 throw new Error(`considerUsage rule is missing config`);
1299 }
1300 const collectedBreakingField = [];
1301 changes.forEach((change) => {
1302 if (change.criticality.level === CriticalityLevel.Breaking && change.path) {
1303 const [typeName, fieldName, argumentName] = parsePath(change.path);
1304 collectedBreakingField.push({
1305 type: typeName,
1306 field: fieldName,
1307 argument: argumentName,
1308 });
1309 }
1310 });
1311 // True if safe to break, false otherwise
1312 const usageList = yield config.checkUsage(collectedBreakingField);
1313 // turns an array of booleans into an array of `Type.Field` strings
1314 // includes only those that are safe to break the api
1315 const suppressedPaths = collectedBreakingField
1316 .filter((_, i) => usageList[i] === true)
1317 .map(({ type, field, argument }) => [type, field, argument].filter(Boolean).join('.'));
1318 return changes.map((change) => {
1319 // Turns those "safe to break" changes into "dangerous"
1320 if (change.criticality.level === CriticalityLevel.Breaking &&
1321 change.path &&
1322 suppressedPaths.some((p) => change.path.startsWith(p))) {
1323 return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }), message: `${change.message} (non-breaking based on usage)` });
1324 }
1325 return change;
1326 });
1327});
1328
1329const rules = /*#__PURE__*/Object.freeze({
1330 __proto__: null,
1331 dangerousBreaking: dangerousBreaking,
1332 suppressRemovalOfDeprecatedField: suppressRemovalOfDeprecatedField,
1333 ignoreDescriptionChanges: ignoreDescriptionChanges,
1334 considerUsage: considerUsage
1335});
1336
1337const DiffRule = rules;
1338function diff(oldSchema, newSchema, rules = [], config) {
1339 const changes = diffSchema(oldSchema, newSchema);
1340 return rules.reduce((prev, rule) => __awaiter(this, void 0, void 0, function* () {
1341 const prevChanges = yield prev;
1342 return rule({
1343 changes: prevChanges,
1344 oldSchema,
1345 newSchema,
1346 config,
1347 });
1348 }), Promise.resolve(changes));
1349}
1350
1351function readDocument(source) {
1352 const result = {
1353 source,
1354 fragments: [],
1355 operations: [],
1356 hasFragments: false,
1357 hasOperations: false,
1358 };
1359 const documentNode = parse(source.body);
1360 const filepath = source.name;
1361 const definitions = documentNode.definitions || [];
1362 definitions.forEach((node) => {
1363 if (isOperation(node)) {
1364 result.operations.push({
1365 node,
1366 source: filepath,
1367 });
1368 }
1369 else if (isFragment(node)) {
1370 result.fragments.push({
1371 node,
1372 source: filepath,
1373 });
1374 }
1375 });
1376 result.hasFragments = result.fragments.length > 0;
1377 result.hasOperations = result.operations.length > 0;
1378 return result;
1379}
1380function isOperation(node) {
1381 return node.kind === Kind.OPERATION_DEFINITION;
1382}
1383function isFragment(node) {
1384 return node.kind === Kind.FRAGMENT_DEFINITION;
1385}
1386
1387function validateQueryDepth({ source, doc, maxDepth, fragmentGraph, }) {
1388 try {
1389 calculateDepth({
1390 node: doc,
1391 currentDepth: 0,
1392 maxDepth,
1393 getFragment(name) {
1394 return fragmentGraph.getNodeData(name);
1395 },
1396 });
1397 }
1398 catch (errorOrNode) {
1399 if (errorOrNode instanceof Error) {
1400 throw errorOrNode;
1401 }
1402 const node = errorOrNode;
1403 return new GraphQLError(`Query exceeds maximum depth of ${maxDepth}`, node, source, node.loc && node.loc.start ? [node.loc.start] : undefined);
1404 }
1405}
1406function calculateDepth({ node, currentDepth, maxDepth, getFragment, }) {
1407 if (maxDepth && currentDepth > maxDepth) {
1408 throw node;
1409 }
1410 switch (node.kind) {
1411 case Kind.FIELD: {
1412 if (node.name.value.startsWith('__') || !node.selectionSet) {
1413 return 0;
1414 }
1415 const maxInnerDepth = calculateDepth({
1416 node: node.selectionSet,
1417 currentDepth: currentDepth + 1,
1418 maxDepth,
1419 getFragment,
1420 });
1421 return 1 + maxInnerDepth;
1422 }
1423 case Kind.SELECTION_SET: {
1424 return Math.max(...node.selections.map((selection) => {
1425 return calculateDepth({
1426 node: selection,
1427 currentDepth: currentDepth,
1428 maxDepth,
1429 getFragment,
1430 });
1431 }));
1432 }
1433 case Kind.DOCUMENT: {
1434 return Math.max(...node.definitions.map((def) => {
1435 return calculateDepth({
1436 node: def,
1437 currentDepth: currentDepth,
1438 maxDepth,
1439 getFragment,
1440 });
1441 }));
1442 }
1443 case Kind.OPERATION_DEFINITION:
1444 case Kind.INLINE_FRAGMENT:
1445 case Kind.FRAGMENT_DEFINITION: {
1446 return Math.max(...node.selectionSet.selections.map((selection) => {
1447 return calculateDepth({
1448 node: selection,
1449 currentDepth,
1450 maxDepth,
1451 getFragment,
1452 });
1453 }));
1454 }
1455 case Kind.FRAGMENT_SPREAD:
1456 return calculateDepth({
1457 node: getFragment(node.name.value),
1458 currentDepth,
1459 maxDepth,
1460 getFragment,
1461 });
1462 default: {
1463 throw new Error(`Couldn't handle ${node.kind}`);
1464 }
1465 }
1466}
1467
1468function transformDocumentWithApollo(doc, { keepClientFields }) {
1469 return visit(doc, {
1470 Field(node) {
1471 return keepClientFields
1472 ? removeDirectives(node, ['client'])
1473 : removeFieldIfDirectives(node, ['client']);
1474 },
1475 });
1476}
1477function transformSchemaWithApollo(schema) {
1478 return extendSchema(schema, parse(/* GraphQL */ `
1479 directive @connection(key: String!, filter: [String]) on FIELD
1480 `));
1481}
1482
1483function validate(schema, sources, options) {
1484 const config = Object.assign({ strictDeprecated: true, strictFragments: true, keepClientFields: false, apollo: false }, options);
1485 const invalidDocuments = [];
1486 // read documents
1487 const documents = sources.map(readDocument);
1488 // keep all named fragments
1489 const fragments = [];
1490 const fragmentNames = [];
1491 const graph = new DepGraph({ circular: true });
1492 documents.forEach((doc) => {
1493 doc.fragments.forEach((fragment) => {
1494 fragmentNames.push(fragment.node.name.value);
1495 fragments.push(fragment);
1496 graph.addNode(fragment.node.name.value, fragment.node);
1497 });
1498 });
1499 fragments.forEach((fragment) => {
1500 const depends = extractFragments(print(fragment.node));
1501 if (depends) {
1502 depends.forEach((name) => {
1503 graph.addDependency(fragment.node.name.value, name);
1504 });
1505 }
1506 });
1507 documents
1508 // since we include fragments, validate only operations
1509 .filter((doc) => doc.hasOperations)
1510 .forEach((doc) => {
1511 const docWithOperations = {
1512 kind: 'Document',
1513 definitions: doc.operations.map((d) => d.node),
1514 };
1515 const extractedFragments = (extractFragments(print(docWithOperations)) || [])
1516 // resolve all nested fragments
1517 .map((fragmentName) => resolveFragment(graph.getNodeData(fragmentName), graph))
1518 // flatten arrays
1519 .reduce((list, current) => list.concat(current), [])
1520 // remove duplicates
1521 .filter((def, i, all) => all.findIndex((item) => item.name.value === def.name.value) === i);
1522 const merged = {
1523 kind: 'Document',
1524 definitions: [...docWithOperations.definitions, ...extractedFragments],
1525 };
1526 let transformedSchema = config.apollo
1527 ? transformSchemaWithApollo(schema)
1528 : schema;
1529 const transformedDoc = config.apollo
1530 ? transformDocumentWithApollo(merged, {
1531 keepClientFields: config.keepClientFields,
1532 })
1533 : merged;
1534 const errors = validate$1(transformedSchema, transformedDoc) || [];
1535 if (config.maxDepth) {
1536 const depthError = validateQueryDepth({
1537 source: doc.source,
1538 doc: transformedDoc,
1539 maxDepth: config.maxDepth,
1540 fragmentGraph: graph,
1541 });
1542 if (depthError) {
1543 errors.push(depthError);
1544 }
1545 }
1546 const deprecated = config.strictDeprecated
1547 ? findDeprecatedUsages(transformedSchema, transformedDoc)
1548 : [];
1549 const duplicatedFragments = config.strictFragments
1550 ? findDuplicatedFragments(fragmentNames)
1551 : [];
1552 if (sumLengths(errors, duplicatedFragments, deprecated) > 0) {
1553 invalidDocuments.push({
1554 source: doc.source,
1555 errors: [...errors, ...duplicatedFragments],
1556 deprecated,
1557 });
1558 }
1559 });
1560 return invalidDocuments;
1561}
1562function findDuplicatedFragments(fragmentNames) {
1563 return fragmentNames
1564 .filter((name, i, all) => all.indexOf(name) !== i)
1565 .map((name) => new GraphQLError(`Name of '${name}' fragment is not unique`));
1566}
1567//
1568// PostInfo -> AuthorInfo
1569// AuthorInfo -> None
1570//
1571function resolveFragment(fragment, graph) {
1572 return graph
1573 .dependenciesOf(fragment.name.value)
1574 .reduce((list, current) => [
1575 ...list,
1576 ...resolveFragment(graph.getNodeData(current), graph),
1577 ], [fragment]);
1578}
1579function extractFragments(document) {
1580 return (document.match(/[\.]{3}[a-z0-9\_]+\b/gi) || []).map((name) => name.replace('...', ''));
1581}
1582function sumLengths(...arrays) {
1583 return arrays.reduce((sum, { length }) => sum + length, 0);
1584}
1585
1586function similar(schema, typeName, threshold = 0.4) {
1587 const typeMap = schema.getTypeMap();
1588 const targets = Object.keys(schema.getTypeMap())
1589 .filter((name) => !isPrimitive(name) && !isForIntrospection(name))
1590 .map((name) => ({
1591 typeId: name,
1592 value: stripType(typeMap[name]),
1593 }));
1594 const results = {};
1595 if (typeof typeName !== 'undefined' &&
1596 !targets.some((t) => t.typeId === typeName)) {
1597 throw new Error(`Type '${typeName}' doesn't exist`);
1598 }
1599 (typeName ? [{ typeId: typeName, value: '' }] : targets).forEach((source) => {
1600 const sourceType = schema.getType(source.typeId);
1601 const matchWith = targets.filter((target) => schema.getType(target.typeId).astNode.kind ===
1602 sourceType.astNode.kind && target.typeId !== source.typeId);
1603 if (matchWith.length > 0) {
1604 const found = similarTo(sourceType, matchWith, threshold);
1605 if (found) {
1606 results[source.typeId] = found;
1607 }
1608 }
1609 });
1610 return results;
1611}
1612function similarTo(type, targets, threshold) {
1613 const types = targets.filter((target) => target.typeId !== type.name);
1614 const result = findBestMatch(stripType(type), types);
1615 if (result.bestMatch.rating < threshold) {
1616 return;
1617 }
1618 return {
1619 bestMatch: result.bestMatch,
1620 ratings: result.ratings
1621 .filter((r) => r.rating >= threshold && r.target !== result.bestMatch.target)
1622 .sort((a, b) => a.rating - b.rating)
1623 .reverse(),
1624 };
1625}
1626function stripType(type) {
1627 return printType(type)
1628 .trim()
1629 .replace(/^[a-z]+ [^\{]+\{/g, '')
1630 .replace(/\}$/g, '')
1631 .trim()
1632 .split('\n')
1633 .map((s) => s.trim())
1634 .sort((a, b) => a.localeCompare(b))
1635 .join(' ');
1636}
1637
1638function coverage(schema, sources) {
1639 const coverage = {
1640 sources,
1641 types: {},
1642 };
1643 const typeMap = schema.getTypeMap();
1644 const typeInfo = new TypeInfo(schema);
1645 const visitor = (source) => ({
1646 Field(node) {
1647 const fieldDef = typeInfo.getFieldDef();
1648 const parent = typeInfo.getParentType();
1649 if (parent &&
1650 parent.name &&
1651 !isForIntrospection(parent.name) &&
1652 fieldDef &&
1653 fieldDef.name &&
1654 fieldDef.name !== '__typename' &&
1655 fieldDef.name !== '__schema') {
1656 const sourceName = source.name;
1657 const typeCoverage = coverage.types[parent.name];
1658 const fieldCoverage = typeCoverage.children[fieldDef.name];
1659 const locations = fieldCoverage.locations[sourceName];
1660 typeCoverage.hits++;
1661 fieldCoverage.hits++;
1662 if (node.loc) {
1663 fieldCoverage.locations[sourceName] = [
1664 node.loc,
1665 ...(locations || []),
1666 ];
1667 }
1668 if (node.arguments) {
1669 for (const argNode of node.arguments) {
1670 const argCoverage = fieldCoverage.children[argNode.name.value];
1671 argCoverage.hits++;
1672 if (argNode.loc) {
1673 argCoverage.locations[sourceName] = [
1674 argNode.loc,
1675 ...(argCoverage.locations[sourceName] || []),
1676 ];
1677 }
1678 }
1679 }
1680 }
1681 },
1682 });
1683 for (const typename in typeMap) {
1684 if (!isForIntrospection(typename) && !isPrimitive(typename)) {
1685 const type = typeMap[typename];
1686 if (isObjectType(type) || isInterfaceType(type)) {
1687 const typeCoverage = {
1688 hits: 0,
1689 type,
1690 children: {},
1691 };
1692 const fieldMap = type.getFields();
1693 for (const fieldname in fieldMap) {
1694 const field = fieldMap[fieldname];
1695 typeCoverage.children[field.name] = {
1696 hits: 0,
1697 locations: {},
1698 children: {},
1699 };
1700 for (const arg of field.args) {
1701 typeCoverage.children[field.name].children[arg.name] = {
1702 hits: 0,
1703 locations: {},
1704 };
1705 }
1706 }
1707 coverage.types[type.name] = typeCoverage;
1708 }
1709 }
1710 }
1711 const documents = coverage.sources.map(readDocument);
1712 documents.forEach((doc, i) => {
1713 const source = coverage.sources[i];
1714 doc.operations.forEach((op) => {
1715 visit(op.node, visitWithTypeInfo(typeInfo, visitor(source)));
1716 });
1717 doc.fragments.forEach((fr) => {
1718 visit(fr.node, visitWithTypeInfo(typeInfo, visitor(source)));
1719 });
1720 });
1721 return coverage;
1722}
1723
1724export { ChangeType, CriticalityLevel, DiffRule, coverage, diff, getTypePrefix, similar, validate };