UNPKG

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