@graphql-inspector/core 2.9.0 → 3.1.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.
@@ -1,6 +1,7 @@
1
+ import { __awaiter } from 'tslib';
1
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';
3
+ import inspect from 'object-inspect';
2
4
  import { DepGraph } from 'dependency-graph';
3
- import 'object-inspect';
4
5
 
5
6
  function keyMap(list, keyFn) {
6
7
  return list.reduce((map, item) => {
@@ -71,6 +72,20 @@ function compareLists(oldList, newList, callbacks) {
71
72
  };
72
73
  }
73
74
 
75
+ function isDeprecated(fieldOrEnumValue) {
76
+ var _a, _b;
77
+ if ('isDeprecated' in fieldOrEnumValue) {
78
+ return fieldOrEnumValue['isDeprecated'];
79
+ }
80
+ if (fieldOrEnumValue.deprecationReason != null) {
81
+ return true;
82
+ }
83
+ 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")) {
84
+ return true;
85
+ }
86
+ return false;
87
+ }
88
+
74
89
  function safeChangeForField(oldType, newType) {
75
90
  if (!isWrappingType(oldType) && !isWrappingType(newType)) {
76
91
  return oldType.toString() === newType.toString();
@@ -113,7 +128,7 @@ function getTypePrefix(type) {
113
128
  [Kind.ENUM_TYPE_DEFINITION]: 'enum',
114
129
  [Kind.INPUT_OBJECT_TYPE_DEFINITION]: 'input',
115
130
  };
116
- return kindsMap[kind];
131
+ return kindsMap[kind.toString()];
117
132
  }
118
133
  function isPrimitive(type) {
119
134
  return (['String', 'Int', 'Float', 'Boolean', 'ID'].indexOf(typeof type === 'string' ? type : type.name) !== -1);
@@ -148,7 +163,7 @@ function findDeprecatedUsages(schema, ast) {
148
163
  },
149
164
  Field(node) {
150
165
  const fieldDef = typeInfo.getFieldDef();
151
- if (fieldDef && fieldDef.isDeprecated) {
166
+ if (fieldDef && isDeprecated(fieldDef)) {
152
167
  const parentType = typeInfo.getParentType();
153
168
  if (parentType) {
154
169
  const reason = fieldDef.deprecationReason;
@@ -158,7 +173,7 @@ function findDeprecatedUsages(schema, ast) {
158
173
  },
159
174
  EnumValue(node) {
160
175
  const enumVal = typeInfo.getEnumValue();
161
- if (enumVal && enumVal.isDeprecated) {
176
+ if (enumVal && isDeprecated(enumVal)) {
162
177
  const type = getNamedType(typeInfo.getInputType());
163
178
  if (type) {
164
179
  const reason = enumVal.deprecationReason;
@@ -471,7 +486,7 @@ function enumValueRemoved(oldEnum, value) {
471
486
  reason: `Removing an enum value will cause existing queries that use this enum value to error.`,
472
487
  },
473
488
  type: ChangeType.EnumValueRemoved,
474
- message: `Enum value '${value.name}' ${value.isDeprecated ? '(deprecated) ' : ''}was removed from enum '${oldEnum.name}'`,
489
+ message: `Enum value '${value.name}' ${isDeprecated(value) ? '(deprecated) ' : ''}was removed from enum '${oldEnum.name}'`,
475
490
  path: [oldEnum.name, value.name].join('.'),
476
491
  };
477
492
  }
@@ -755,7 +770,7 @@ function fieldRemoved(type, field) {
755
770
  : `Removing a field is a breaking change. It is preferable to deprecate the field before removing it.`,
756
771
  },
757
772
  type: ChangeType.FieldRemoved,
758
- message: `Field '${field.name}' ${field.isDeprecated ? '(deprecated) ' : ''}was removed from ${entity} '${type.name}'`,
773
+ message: `Field '${field.name}' ${isDeprecated(field) ? '(deprecated) ' : ''}was removed from ${entity} '${type.name}'`,
759
774
  path: [type.name, field.name].join('.'),
760
775
  };
761
776
  }
@@ -890,6 +905,73 @@ function fieldArgumentRemoved(type, field, arg) {
890
905
  };
891
906
  }
892
907
 
908
+ function compareTwoStrings(str1, str2) {
909
+ if (!str1.length && !str2.length)
910
+ return 1;
911
+ if (!str1.length || !str2.length)
912
+ return 0;
913
+ if (str1.toUpperCase() === str2.toUpperCase())
914
+ return 1;
915
+ if (str1.length === 1 && str2.length === 1)
916
+ return 0;
917
+ const pairs1 = wordLetterPairs(str1);
918
+ const pairs2 = wordLetterPairs(str2);
919
+ const union = pairs1.length + pairs2.length;
920
+ let intersection = 0;
921
+ pairs1.forEach((pair1) => {
922
+ for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
923
+ if (pair1 !== pair2)
924
+ continue;
925
+ intersection++;
926
+ pairs2.splice(i, 1);
927
+ break;
928
+ }
929
+ });
930
+ return (intersection * 2) / union;
931
+ }
932
+ function findBestMatch(mainString, targetStrings) {
933
+ if (!areArgsValid(mainString, targetStrings))
934
+ throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
935
+ const ratings = targetStrings.map((target) => ({
936
+ target,
937
+ rating: compareTwoStrings(mainString, target.value),
938
+ }));
939
+ const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
940
+ return { ratings, bestMatch };
941
+ }
942
+ function flattenDeep(arr) {
943
+ return Array.isArray(arr)
944
+ ? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
945
+ : [arr];
946
+ }
947
+ function areArgsValid(mainString, targetStrings) {
948
+ if (typeof mainString !== 'string')
949
+ return false;
950
+ if (!Array.isArray(targetStrings))
951
+ return false;
952
+ if (!targetStrings.length)
953
+ return false;
954
+ if (targetStrings.find((s) => typeof s.value !== 'string'))
955
+ return false;
956
+ return true;
957
+ }
958
+ function letterPairs(str) {
959
+ const pairs = [];
960
+ for (let i = 0, max = str.length - 1; i < max; i++)
961
+ pairs[i] = str.substring(i, i + 2);
962
+ return pairs;
963
+ }
964
+ function wordLetterPairs(str) {
965
+ const pairs = str.toUpperCase().split(' ').map(letterPairs);
966
+ return flattenDeep(pairs);
967
+ }
968
+ function safeString(obj) {
969
+ if (obj != null && typeof obj.toString === 'function') {
970
+ return `${obj}`;
971
+ }
972
+ return inspect(obj);
973
+ }
974
+
893
975
  function fieldArgumentDescriptionChanged(type, field, oldArg, newArg) {
894
976
  return {
895
977
  criticality: {
@@ -908,8 +990,8 @@ function fieldArgumentDefaultChanged(type, field, oldArg, newArg) {
908
990
  },
909
991
  type: ChangeType.FieldArgumentDefaultChanged,
910
992
  message: typeof oldArg.defaultValue === 'undefined'
911
- ? `Default value '${newArg.defaultValue}' was added to argument '${newArg.name}' on field '${type.name}.${field.name}'`
912
- : `Default value for argument '${newArg.name}' on field '${type.name}.${field.name}' changed from '${oldArg.defaultValue}' to '${newArg.defaultValue}'`,
993
+ ? `Default value '${safeString(newArg.defaultValue)}' was added to argument '${newArg.name}' on field '${type.name}.${field.name}'`
994
+ : `Default value for argument '${newArg.name}' on field '${type.name}.${field.name}' changed from '${safeString(oldArg.defaultValue)}' to '${safeString(newArg.defaultValue)}'`,
913
995
  path: [type.name, field.name, oldArg.name].join('.'),
914
996
  };
915
997
  }
@@ -964,8 +1046,8 @@ function changesInField(type, oldField, newField, addChange) {
964
1046
  addChange(fieldDescriptionChanged(type, oldField, newField));
965
1047
  }
966
1048
  }
967
- if (isNotEqual(oldField.isDeprecated, newField.isDeprecated)) {
968
- if (newField.isDeprecated) {
1049
+ if (isNotEqual(isDeprecated(oldField), isDeprecated(newField))) {
1050
+ if (isDeprecated(newField)) {
969
1051
  addChange(fieldDeprecationAdded(type, newField));
970
1052
  }
971
1053
  else {
@@ -1106,17 +1188,21 @@ function diffSchema(oldSchema, newSchema) {
1106
1188
  return changes;
1107
1189
  }
1108
1190
  function changesInSchema(oldSchema, newSchema, addChange) {
1191
+ var _a, _b, _c, _d, _e, _f;
1192
+ const defaultNames = {
1193
+ query: 'Query',
1194
+ mutation: 'Mutation',
1195
+ subscription: 'Subscription',
1196
+ };
1109
1197
  const oldRoot = {
1110
- query: (oldSchema.getQueryType() || {}).name,
1111
- mutation: (oldSchema.getMutationType() || {}).name,
1112
- subscription: (oldSchema.getSubscriptionType() || {})
1113
- .name,
1198
+ query: (_a = (oldSchema.getQueryType() || {}).name) !== null && _a !== void 0 ? _a : defaultNames.query,
1199
+ mutation: (_b = (oldSchema.getMutationType() || {}).name) !== null && _b !== void 0 ? _b : defaultNames.mutation,
1200
+ subscription: (_c = (oldSchema.getSubscriptionType() || {}).name) !== null && _c !== void 0 ? _c : defaultNames.subscription,
1114
1201
  };
1115
1202
  const newRoot = {
1116
- query: (newSchema.getQueryType() || {}).name,
1117
- mutation: (newSchema.getMutationType() || {}).name,
1118
- subscription: (newSchema.getSubscriptionType() || {})
1119
- .name,
1203
+ query: (_d = (newSchema.getQueryType() || {}).name) !== null && _d !== void 0 ? _d : defaultNames.query,
1204
+ mutation: (_e = (newSchema.getMutationType() || {}).name) !== null && _e !== void 0 ? _e : defaultNames.mutation,
1205
+ subscription: (_f = (newSchema.getSubscriptionType() || {}).name) !== null && _f !== void 0 ? _f : defaultNames.subscription,
1120
1206
  };
1121
1207
  if (isNotEqual(oldRoot.query, newRoot.query)) {
1122
1208
  addChange(schemaQueryTypeChanged(oldSchema, newSchema));
@@ -1183,7 +1269,7 @@ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
1183
1269
  const type = oldSchema.getType(typeName);
1184
1270
  if (isObjectType(type) || isInterfaceType(type)) {
1185
1271
  const field = type.getFields()[fieldName];
1186
- if (field.isDeprecated) {
1272
+ if (isDeprecated(field)) {
1187
1273
  return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }) });
1188
1274
  }
1189
1275
  }
@@ -1195,7 +1281,7 @@ const suppressRemovalOfDeprecatedField = ({ changes, oldSchema, }) => {
1195
1281
  const type = oldSchema.getType(enumName);
1196
1282
  if (isEnumType(type)) {
1197
1283
  const item = type.getValue(enumItem);
1198
- if (item && item.isDeprecated) {
1284
+ if (item && isDeprecated(item)) {
1199
1285
  return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }) });
1200
1286
  }
1201
1287
  }
@@ -1221,21 +1307,59 @@ const ignoreDescriptionChanges = ({ changes }) => {
1221
1307
  return changes.filter((change) => descriptionChangeTypes.indexOf(change.type) === -1);
1222
1308
  };
1223
1309
 
1310
+ const considerUsage = ({ changes, config, }) => __awaiter(void 0, void 0, void 0, function* () {
1311
+ if (!config) {
1312
+ throw new Error(`considerUsage rule is missing config`);
1313
+ }
1314
+ const collectedBreakingField = [];
1315
+ changes.forEach((change) => {
1316
+ if (change.criticality.level === CriticalityLevel.Breaking && change.path) {
1317
+ const [typeName, fieldName, argumentName] = parsePath(change.path);
1318
+ collectedBreakingField.push({
1319
+ type: typeName,
1320
+ field: fieldName,
1321
+ argument: argumentName,
1322
+ });
1323
+ }
1324
+ });
1325
+ // True if safe to break, false otherwise
1326
+ const usageList = yield config.checkUsage(collectedBreakingField);
1327
+ // turns an array of booleans into an array of `Type.Field` strings
1328
+ // includes only those that are safe to break the api
1329
+ const suppressedPaths = collectedBreakingField
1330
+ .filter((_, i) => usageList[i] === true)
1331
+ .map(({ type, field, argument }) => [type, field, argument].filter(Boolean).join('.'));
1332
+ return changes.map((change) => {
1333
+ // Turns those "safe to break" changes into "dangerous"
1334
+ if (change.criticality.level === CriticalityLevel.Breaking &&
1335
+ change.path &&
1336
+ suppressedPaths.some((p) => change.path.startsWith(p))) {
1337
+ return Object.assign(Object.assign({}, change), { criticality: Object.assign(Object.assign({}, change.criticality), { level: CriticalityLevel.Dangerous }), message: `${change.message} (non-breaking based on usage)` });
1338
+ }
1339
+ return change;
1340
+ });
1341
+ });
1342
+
1224
1343
  const rules = /*#__PURE__*/Object.freeze({
1225
1344
  __proto__: null,
1226
1345
  dangerousBreaking: dangerousBreaking,
1227
1346
  suppressRemovalOfDeprecatedField: suppressRemovalOfDeprecatedField,
1228
- ignoreDescriptionChanges: ignoreDescriptionChanges
1347
+ ignoreDescriptionChanges: ignoreDescriptionChanges,
1348
+ considerUsage: considerUsage
1229
1349
  });
1230
1350
 
1231
1351
  const DiffRule = rules;
1232
- function diff(oldSchema, newSchema, rules = []) {
1352
+ function diff(oldSchema, newSchema, rules = [], config) {
1233
1353
  const changes = diffSchema(oldSchema, newSchema);
1234
- return rules.reduce((prev, rule) => rule({
1235
- changes: prev,
1236
- oldSchema,
1237
- newSchema,
1238
- }), changes);
1354
+ return rules.reduce((prev, rule) => __awaiter(this, void 0, void 0, function* () {
1355
+ const prevChanges = yield prev;
1356
+ return rule({
1357
+ changes: prevChanges,
1358
+ oldSchema,
1359
+ newSchema,
1360
+ config,
1361
+ });
1362
+ }), Promise.resolve(changes));
1239
1363
  }
1240
1364
 
1241
1365
  function readDocument(source) {
@@ -1399,7 +1523,7 @@ function validate(schema, sources, options) {
1399
1523
  .filter((doc) => doc.hasOperations)
1400
1524
  .forEach((doc) => {
1401
1525
  const docWithOperations = {
1402
- kind: 'Document',
1526
+ kind: Kind.DOCUMENT,
1403
1527
  definitions: doc.operations.map((d) => d.node),
1404
1528
  };
1405
1529
  const extractedFragments = (extractFragments(print(docWithOperations)) || [])
@@ -1410,7 +1534,7 @@ function validate(schema, sources, options) {
1410
1534
  // remove duplicates
1411
1535
  .filter((def, i, all) => all.findIndex((item) => item.name.value === def.name.value) === i);
1412
1536
  const merged = {
1413
- kind: 'Document',
1537
+ kind: Kind.DOCUMENT,
1414
1538
  definitions: [...docWithOperations.definitions, ...extractedFragments],
1415
1539
  };
1416
1540
  let transformedSchema = config.apollo
@@ -1473,67 +1597,6 @@ function sumLengths(...arrays) {
1473
1597
  return arrays.reduce((sum, { length }) => sum + length, 0);
1474
1598
  }
1475
1599
 
1476
- function compareTwoStrings(str1, str2) {
1477
- if (!str1.length && !str2.length)
1478
- return 1;
1479
- if (!str1.length || !str2.length)
1480
- return 0;
1481
- if (str1.toUpperCase() === str2.toUpperCase())
1482
- return 1;
1483
- if (str1.length === 1 && str2.length === 1)
1484
- return 0;
1485
- const pairs1 = wordLetterPairs(str1);
1486
- const pairs2 = wordLetterPairs(str2);
1487
- const union = pairs1.length + pairs2.length;
1488
- let intersection = 0;
1489
- pairs1.forEach((pair1) => {
1490
- for (let i = 0, pair2; (pair2 = pairs2[i]); i++) {
1491
- if (pair1 !== pair2)
1492
- continue;
1493
- intersection++;
1494
- pairs2.splice(i, 1);
1495
- break;
1496
- }
1497
- });
1498
- return (intersection * 2) / union;
1499
- }
1500
- function findBestMatch(mainString, targetStrings) {
1501
- if (!areArgsValid(mainString, targetStrings))
1502
- throw new Error('Bad arguments: First argument should be a string, second should be an array of strings');
1503
- const ratings = targetStrings.map((target) => ({
1504
- target,
1505
- rating: compareTwoStrings(mainString, target.value),
1506
- }));
1507
- const bestMatch = Array.from(ratings).sort((a, b) => b.rating - a.rating)[0];
1508
- return { ratings, bestMatch };
1509
- }
1510
- function flattenDeep(arr) {
1511
- return Array.isArray(arr)
1512
- ? arr.reduce((a, b) => a.concat(flattenDeep(b)), [])
1513
- : [arr];
1514
- }
1515
- function areArgsValid(mainString, targetStrings) {
1516
- if (typeof mainString !== 'string')
1517
- return false;
1518
- if (!Array.isArray(targetStrings))
1519
- return false;
1520
- if (!targetStrings.length)
1521
- return false;
1522
- if (targetStrings.find((s) => typeof s.value !== 'string'))
1523
- return false;
1524
- return true;
1525
- }
1526
- function letterPairs(str) {
1527
- const pairs = [];
1528
- for (let i = 0, max = str.length - 1; i < max; i++)
1529
- pairs[i] = str.substring(i, i + 2);
1530
- return pairs;
1531
- }
1532
- function wordLetterPairs(str) {
1533
- const pairs = str.toUpperCase().split(' ').map(letterPairs);
1534
- return flattenDeep(pairs);
1535
- }
1536
-
1537
1600
  function similar(schema, typeName, threshold = 0.4) {
1538
1601
  const typeMap = schema.getTypeMap();
1539
1602
  const targets = Object.keys(schema.getTypeMap())
@@ -1673,4 +1736,3 @@ function coverage(schema, sources) {
1673
1736
  }
1674
1737
 
1675
1738
  export { ChangeType, CriticalityLevel, DiffRule, coverage, diff, getTypePrefix, similar, validate };
1676
- //# sourceMappingURL=index.esm.js.map
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@graphql-inspector/core",
3
- "version": "2.9.0",
3
+ "version": "3.1.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": {
7
- "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0"
7
+ "graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
8
8
  },
9
9
  "dependencies": {
10
10
  "dependency-graph": "0.11.0",
@@ -27,10 +27,20 @@
27
27
  "url": "https://github.com/kamilkisiela"
28
28
  },
29
29
  "license": "MIT",
30
- "main": "index.cjs.js",
31
- "module": "index.esm.js",
30
+ "main": "index.js",
31
+ "module": "index.mjs",
32
32
  "typings": "index.d.ts",
33
33
  "typescript": {
34
34
  "definition": "index.d.ts"
35
+ },
36
+ "exports": {
37
+ ".": {
38
+ "require": "./index.js",
39
+ "import": "./index.mjs"
40
+ },
41
+ "./*": {
42
+ "require": "./*.js",
43
+ "import": "./*.mjs"
44
+ }
35
45
  }
36
46
  }
@@ -2,8 +2,8 @@ export declare function keyMap<T>(list: readonly T[], keyFn: (item: T) => string
2
2
  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
- export declare function diffArrays(a: string[], b: string[]): string[];
6
- export declare function unionArrays(a: string[], b: string[]): string[];
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
7
  export declare function compareLists<T extends {
8
8
  name: string;
9
9
  }>(oldList: readonly T[], newList: readonly T[], callbacks?: {
@@ -8,3 +8,4 @@ export declare function isForIntrospection(type: GraphQLNamedType | string): boo
8
8
  export declare function findDeprecatedUsages(schema: GraphQLSchema, ast: DocumentNode): Array<GraphQLError>;
9
9
  export declare function removeFieldIfDirectives(node: FieldNode, directiveNames: string[]): FieldNode | null;
10
10
  export declare function removeDirectives(node: FieldNode, directiveNames: string[]): FieldNode;
11
+ export declare function getReachableTypes(schema: GraphQLSchema): Set<string>;
@@ -0,0 +1,2 @@
1
+ import { GraphQLEnumValue, GraphQLField } from "graphql";
2
+ export declare function isDeprecated(fieldOrEnumValue: GraphQLField<any, any> | GraphQLEnumValue): boolean;