UNPKG

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