@graphql-inspector/core 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,3 +2,4 @@ export * from './dangerous-breaking';
2
2
  export * from './suppress-removal-of-deprecated-field';
3
3
  export * from './ignore-description-changes';
4
4
  export * from './consider-usage';
5
+ export * from './safe-unreachable';
package/index.js CHANGED
@@ -20,7 +20,21 @@ function isEqual(a, b) {
20
20
  if (a.length !== b.length)
21
21
  return false;
22
22
  for (var index = 0; index < a.length; index++) {
23
- if (a[index] !== b[index]) {
23
+ if (!isEqual(a[index], b[index])) {
24
+ return false;
25
+ }
26
+ }
27
+ return true;
28
+ }
29
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
30
+ const aRecord = a;
31
+ const bRecord = b;
32
+ const aKeys = Object.keys(aRecord);
33
+ const bKeys = Object.keys(bRecord);
34
+ if (aKeys.length !== bKeys.length)
35
+ return false;
36
+ for (const key of aKeys) {
37
+ if (!isEqual(aRecord[key], bRecord[key])) {
24
38
  return false;
25
39
  }
26
40
  }
@@ -35,7 +49,7 @@ function isVoid(a) {
35
49
  return typeof a === 'undefined' || a === null;
36
50
  }
37
51
  function diffArrays(a, b) {
38
- return a.filter((c) => !b.some((d) => d === c));
52
+ return a.filter((c) => !b.some((d) => isEqual(d, c)));
39
53
  }
40
54
  function compareLists(oldList, newList, callbacks) {
41
55
  const oldMap = keyMap(oldList, ({ name }) => name);
@@ -115,7 +129,7 @@ function safeChangeForInputValue(oldType, newType) {
115
129
  return safeChangeForInputValue(oldType.ofType, newType.ofType);
116
130
  }
117
131
  if (graphql.isNonNullType(oldType)) {
118
- const ofType = graphql.isNonNullType(newType) ? newType : newType;
132
+ const ofType = graphql.isNonNullType(newType) ? newType.ofType : newType;
119
133
  return safeChangeForInputValue(oldType.ofType, ofType);
120
134
  }
121
135
  return false;
@@ -204,6 +218,75 @@ function removeDirectives(node, directiveNames) {
204
218
  }
205
219
  return node;
206
220
  }
221
+ function getReachableTypes(schema) {
222
+ const reachableTypes = new Set();
223
+ const collect = (type) => {
224
+ const typeName = type.name;
225
+ if (reachableTypes.has(typeName)) {
226
+ return;
227
+ }
228
+ reachableTypes.add(typeName);
229
+ if (graphql.isScalarType(type)) {
230
+ return;
231
+ }
232
+ else if (graphql.isInterfaceType(type) || graphql.isObjectType(type)) {
233
+ if (graphql.isInterfaceType(type)) {
234
+ const { objects, interfaces } = schema.getImplementations(type);
235
+ for (const child of objects) {
236
+ collect(child);
237
+ }
238
+ for (const child of interfaces) {
239
+ collect(child);
240
+ }
241
+ }
242
+ const fields = type.getFields();
243
+ for (const fieldName in fields) {
244
+ const field = fields[fieldName];
245
+ collect(resolveOutputType(field.type));
246
+ const args = field.args;
247
+ for (const argName in args) {
248
+ const arg = args[argName];
249
+ collect(resolveInputType(arg.type));
250
+ }
251
+ }
252
+ }
253
+ else if (graphql.isUnionType(type)) {
254
+ const types = type.getTypes();
255
+ for (const child of types) {
256
+ collect(child);
257
+ }
258
+ }
259
+ else if (graphql.isInputObjectType(type)) {
260
+ const fields = type.getFields();
261
+ for (const fieldName in fields) {
262
+ const field = fields[fieldName];
263
+ collect(resolveInputType(field.type));
264
+ }
265
+ }
266
+ };
267
+ for (const type of [
268
+ schema.getQueryType(),
269
+ schema.getMutationType(),
270
+ schema.getSubscriptionType(),
271
+ ]) {
272
+ if (type) {
273
+ collect(type);
274
+ }
275
+ }
276
+ return reachableTypes;
277
+ }
278
+ function resolveOutputType(output) {
279
+ if (graphql.isListType(output) || graphql.isNonNullType(output)) {
280
+ return resolveOutputType(output.ofType);
281
+ }
282
+ return output;
283
+ }
284
+ function resolveInputType(input) {
285
+ if (graphql.isListType(input) || graphql.isNonNullType(input)) {
286
+ return resolveInputType(input.ofType);
287
+ }
288
+ return input;
289
+ }
207
290
 
208
291
  (function (ChangeType) {
209
292
  // Argument
@@ -620,7 +703,7 @@ function inputFieldRemoved(input, field) {
620
703
  reason: 'Removing an input field will cause existing queries that use this input field to error.',
621
704
  },
622
705
  type: exports.ChangeType.InputFieldRemoved,
623
- message: `Input field '${field.name}' was removed from input object type '${input.name}'`,
706
+ message: `Input field '${field.name}' ${isDeprecated(field) ? '(deprecated) ' : ''}was removed from input object type '${input.name}'`,
624
707
  path: [input.name, field.name].join('.'),
625
708
  };
626
709
  }
@@ -1287,6 +1370,18 @@ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
1287
1370
  }
1288
1371
  }
1289
1372
  }
1373
+ if (change.type === exports.ChangeType.InputFieldRemoved &&
1374
+ change.criticality.level === exports.CriticalityLevel.Breaking &&
1375
+ change.path) {
1376
+ const [inputName, inputItem] = parsePath(change.path);
1377
+ const type = oldSchema.getType(inputName);
1378
+ if (graphql.isInputObjectType(type)) {
1379
+ const item = type.getFields()[inputItem];
1380
+ if (item && isDeprecated(item)) {
1381
+ return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.Dangerous }) });
1382
+ }
1383
+ }
1384
+ }
1290
1385
  return change;
1291
1386
  });
1292
1387
  };
@@ -1341,12 +1436,26 @@ const considerUsage = ({ changes, config, }) => tslib.__awaiter(void 0, void 0,
1341
1436
  });
1342
1437
  });
1343
1438
 
1439
+ const safeUnreachable = ({ changes, oldSchema }) => {
1440
+ const reachable = getReachableTypes(oldSchema);
1441
+ return changes.map((change) => {
1442
+ if (change.criticality.level === exports.CriticalityLevel.Breaking && change.path) {
1443
+ const [typeName] = parsePath(change.path);
1444
+ if (!reachable.has(typeName)) {
1445
+ return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: exports.CriticalityLevel.NonBreaking }), message: 'Unreachable from root' });
1446
+ }
1447
+ }
1448
+ return change;
1449
+ });
1450
+ };
1451
+
1344
1452
  const rules = /*#__PURE__*/Object.freeze({
1345
1453
  __proto__: null,
1346
1454
  dangerousBreaking: dangerousBreaking,
1347
1455
  suppressRemovalOfDeprecatedField: suppressRemovalOfDeprecatedField,
1348
1456
  ignoreDescriptionChanges: ignoreDescriptionChanges,
1349
- considerUsage: considerUsage
1457
+ considerUsage: considerUsage,
1458
+ safeUnreachable: safeUnreachable
1350
1459
  });
1351
1460
 
1352
1461
  const DiffRule = rules;
package/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { __awaiter } from 'tslib';
2
- import { Kind, TypeInfo, visit, visitWithTypeInfo, GraphQLError, getNamedType, isWrappingType, isListType, isNonNullType, isInterfaceType, isEnumType, isUnionType, isInputObjectType, isObjectType, isScalarType, parse, extendSchema, print, validate as validate$1, printType } from 'graphql';
2
+ import { Kind, TypeInfo, visit, visitWithTypeInfo, GraphQLError, getNamedType, isScalarType, isInterfaceType, isObjectType, isUnionType, isInputObjectType, isListType, isNonNullType, isWrappingType, isEnumType, parse, extendSchema, print, validate as validate$1, printType } from 'graphql';
3
3
  import inspect from 'object-inspect';
4
4
  import { DepGraph } from 'dependency-graph';
5
5
 
@@ -14,7 +14,21 @@ function isEqual(a, b) {
14
14
  if (a.length !== b.length)
15
15
  return false;
16
16
  for (var index = 0; index < a.length; index++) {
17
- if (a[index] !== b[index]) {
17
+ if (!isEqual(a[index], b[index])) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ }
23
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
24
+ const aRecord = a;
25
+ const bRecord = b;
26
+ const aKeys = Object.keys(aRecord);
27
+ const bKeys = Object.keys(bRecord);
28
+ if (aKeys.length !== bKeys.length)
29
+ return false;
30
+ for (const key of aKeys) {
31
+ if (!isEqual(aRecord[key], bRecord[key])) {
18
32
  return false;
19
33
  }
20
34
  }
@@ -29,7 +43,7 @@ function isVoid(a) {
29
43
  return typeof a === 'undefined' || a === null;
30
44
  }
31
45
  function diffArrays(a, b) {
32
- return a.filter((c) => !b.some((d) => d === c));
46
+ return a.filter((c) => !b.some((d) => isEqual(d, c)));
33
47
  }
34
48
  function compareLists(oldList, newList, callbacks) {
35
49
  const oldMap = keyMap(oldList, ({ name }) => name);
@@ -109,7 +123,7 @@ function safeChangeForInputValue(oldType, newType) {
109
123
  return safeChangeForInputValue(oldType.ofType, newType.ofType);
110
124
  }
111
125
  if (isNonNullType(oldType)) {
112
- const ofType = isNonNullType(newType) ? newType : newType;
126
+ const ofType = isNonNullType(newType) ? newType.ofType : newType;
113
127
  return safeChangeForInputValue(oldType.ofType, ofType);
114
128
  }
115
129
  return false;
@@ -198,6 +212,75 @@ function removeDirectives(node, directiveNames) {
198
212
  }
199
213
  return node;
200
214
  }
215
+ function getReachableTypes(schema) {
216
+ const reachableTypes = new Set();
217
+ const collect = (type) => {
218
+ const typeName = type.name;
219
+ if (reachableTypes.has(typeName)) {
220
+ return;
221
+ }
222
+ reachableTypes.add(typeName);
223
+ if (isScalarType(type)) {
224
+ return;
225
+ }
226
+ else if (isInterfaceType(type) || isObjectType(type)) {
227
+ if (isInterfaceType(type)) {
228
+ const { objects, interfaces } = schema.getImplementations(type);
229
+ for (const child of objects) {
230
+ collect(child);
231
+ }
232
+ for (const child of interfaces) {
233
+ collect(child);
234
+ }
235
+ }
236
+ const fields = type.getFields();
237
+ for (const fieldName in fields) {
238
+ const field = fields[fieldName];
239
+ collect(resolveOutputType(field.type));
240
+ const args = field.args;
241
+ for (const argName in args) {
242
+ const arg = args[argName];
243
+ collect(resolveInputType(arg.type));
244
+ }
245
+ }
246
+ }
247
+ else if (isUnionType(type)) {
248
+ const types = type.getTypes();
249
+ for (const child of types) {
250
+ collect(child);
251
+ }
252
+ }
253
+ else if (isInputObjectType(type)) {
254
+ const fields = type.getFields();
255
+ for (const fieldName in fields) {
256
+ const field = fields[fieldName];
257
+ collect(resolveInputType(field.type));
258
+ }
259
+ }
260
+ };
261
+ for (const type of [
262
+ schema.getQueryType(),
263
+ schema.getMutationType(),
264
+ schema.getSubscriptionType(),
265
+ ]) {
266
+ if (type) {
267
+ collect(type);
268
+ }
269
+ }
270
+ return reachableTypes;
271
+ }
272
+ function resolveOutputType(output) {
273
+ if (isListType(output) || isNonNullType(output)) {
274
+ return resolveOutputType(output.ofType);
275
+ }
276
+ return output;
277
+ }
278
+ function resolveInputType(input) {
279
+ if (isListType(input) || isNonNullType(input)) {
280
+ return resolveInputType(input.ofType);
281
+ }
282
+ return input;
283
+ }
201
284
 
202
285
  var ChangeType;
203
286
  (function (ChangeType) {
@@ -616,7 +699,7 @@ function inputFieldRemoved(input, field) {
616
699
  reason: 'Removing an input field will cause existing queries that use this input field to error.',
617
700
  },
618
701
  type: ChangeType.InputFieldRemoved,
619
- message: `Input field '${field.name}' was removed from input object type '${input.name}'`,
702
+ message: `Input field '${field.name}' ${isDeprecated(field) ? '(deprecated) ' : ''}was removed from input object type '${input.name}'`,
620
703
  path: [input.name, field.name].join('.'),
621
704
  };
622
705
  }
@@ -1283,6 +1366,18 @@ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
1283
1366
  }
1284
1367
  }
1285
1368
  }
1369
+ if (change.type === ChangeType.InputFieldRemoved &&
1370
+ change.criticality.level === CriticalityLevel.Breaking &&
1371
+ change.path) {
1372
+ const [inputName, inputItem] = parsePath(change.path);
1373
+ const type = oldSchema.getType(inputName);
1374
+ if (isInputObjectType(type)) {
1375
+ const item = type.getFields()[inputItem];
1376
+ if (item && isDeprecated(item)) {
1377
+ return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }) });
1378
+ }
1379
+ }
1380
+ }
1286
1381
  return change;
1287
1382
  });
1288
1383
  };
@@ -1337,12 +1432,26 @@ const considerUsage = ({ changes, config, }) => __awaiter(void 0, void 0, void 0
1337
1432
  });
1338
1433
  });
1339
1434
 
1435
+ const safeUnreachable = ({ changes, oldSchema }) => {
1436
+ const reachable = getReachableTypes(oldSchema);
1437
+ return changes.map((change) => {
1438
+ if (change.criticality.level === CriticalityLevel.Breaking && change.path) {
1439
+ const [typeName] = parsePath(change.path);
1440
+ if (!reachable.has(typeName)) {
1441
+ return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.NonBreaking }), message: 'Unreachable from root' });
1442
+ }
1443
+ }
1444
+ return change;
1445
+ });
1446
+ };
1447
+
1340
1448
  const rules = /*#__PURE__*/Object.freeze({
1341
1449
  __proto__: null,
1342
1450
  dangerousBreaking: dangerousBreaking,
1343
1451
  suppressRemovalOfDeprecatedField: suppressRemovalOfDeprecatedField,
1344
1452
  ignoreDescriptionChanges: ignoreDescriptionChanges,
1345
- considerUsage: considerUsage
1453
+ considerUsage: considerUsage,
1454
+ safeUnreachable: safeUnreachable
1346
1455
  });
1347
1456
 
1348
1457
  const DiffRule = rules;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-inspector/core",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "description": "Tooling for GraphQL. Compare GraphQL Schemas, check documents, find breaking changes, find similar types.",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
@@ -3,7 +3,6 @@ export declare function isEqual<T>(a: T, b: T): boolean;
3
3
  export declare function isNotEqual<T>(a: T, b: T): boolean;
4
4
  export declare function isVoid<T>(a: T): boolean;
5
5
  export declare function diffArrays<T>(a: T[] | readonly T[], b: T[] | readonly T[]): T[];
6
- export declare function unionArrays<T>(a: T[] | readonly T[], b: T[] | readonly T[]): T[];
7
6
  export declare function compareLists<T extends {
8
7
  name: string;
9
8
  }>(oldList: readonly T[], newList: readonly T[], callbacks?: {
@@ -1,2 +1,2 @@
1
- import { GraphQLEnumValue, GraphQLField } from 'graphql';
2
- export declare function isDeprecated(fieldOrEnumValue: GraphQLField<any, any> | GraphQLEnumValue): boolean;
1
+ import { GraphQLInputField, GraphQLEnumValue, GraphQLField } from 'graphql';
2
+ export declare function isDeprecated(fieldOrEnumValue: GraphQLField<any, any> | GraphQLEnumValue | GraphQLInputField): boolean;