@apollo/federation-internals 2.0.0-preview.9 → 2.0.2-alpha.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/coreSpec.d.ts +2 -1
  3. package/dist/coreSpec.d.ts.map +1 -1
  4. package/dist/coreSpec.js +37 -11
  5. package/dist/coreSpec.js.map +1 -1
  6. package/dist/definitions.d.ts +20 -8
  7. package/dist/definitions.d.ts.map +1 -1
  8. package/dist/definitions.js +102 -62
  9. package/dist/definitions.js.map +1 -1
  10. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  11. package/dist/directiveAndTypeSpecification.js +10 -1
  12. package/dist/directiveAndTypeSpecification.js.map +1 -1
  13. package/dist/error.d.ts +10 -0
  14. package/dist/error.d.ts.map +1 -1
  15. package/dist/error.js +29 -9
  16. package/dist/error.js.map +1 -1
  17. package/dist/extractSubgraphsFromSupergraph.js +1 -1
  18. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  19. package/dist/federation.d.ts +4 -1
  20. package/dist/federation.d.ts.map +1 -1
  21. package/dist/federation.js +48 -26
  22. package/dist/federation.js.map +1 -1
  23. package/dist/federationSpec.js +1 -1
  24. package/dist/federationSpec.js.map +1 -1
  25. package/dist/genErrorCodeDoc.js +12 -6
  26. package/dist/genErrorCodeDoc.js.map +1 -1
  27. package/dist/inaccessibleSpec.d.ts +9 -4
  28. package/dist/inaccessibleSpec.d.ts.map +1 -1
  29. package/dist/inaccessibleSpec.js +625 -32
  30. package/dist/inaccessibleSpec.js.map +1 -1
  31. package/dist/joinSpec.d.ts +2 -1
  32. package/dist/joinSpec.d.ts.map +1 -1
  33. package/dist/joinSpec.js +3 -0
  34. package/dist/joinSpec.js.map +1 -1
  35. package/dist/operations.d.ts +20 -1
  36. package/dist/operations.d.ts.map +1 -1
  37. package/dist/operations.js +52 -1
  38. package/dist/operations.js.map +1 -1
  39. package/dist/precompute.d.ts.map +1 -1
  40. package/dist/precompute.js +2 -2
  41. package/dist/precompute.js.map +1 -1
  42. package/dist/schemaUpgrader.d.ts +8 -1
  43. package/dist/schemaUpgrader.d.ts.map +1 -1
  44. package/dist/schemaUpgrader.js +56 -6
  45. package/dist/schemaUpgrader.js.map +1 -1
  46. package/dist/supergraphs.d.ts.map +1 -1
  47. package/dist/supergraphs.js +1 -0
  48. package/dist/supergraphs.js.map +1 -1
  49. package/dist/validate.js +13 -7
  50. package/dist/validate.js.map +1 -1
  51. package/dist/values.d.ts +2 -2
  52. package/dist/values.d.ts.map +1 -1
  53. package/dist/values.js +13 -11
  54. package/dist/values.js.map +1 -1
  55. package/package.json +3 -3
  56. package/src/__tests__/coreSpec.test.ts +112 -0
  57. package/src/__tests__/removeInaccessibleElements.test.ts +2252 -177
  58. package/src/__tests__/schemaUpgrader.test.ts +38 -0
  59. package/src/__tests__/subgraphValidation.test.ts +96 -5
  60. package/src/__tests__/values.test.ts +315 -3
  61. package/src/coreSpec.ts +74 -16
  62. package/src/definitions.ts +207 -87
  63. package/src/directiveAndTypeSpecification.ts +18 -1
  64. package/src/error.ts +69 -9
  65. package/src/extractSubgraphsFromSupergraph.ts +1 -1
  66. package/src/federation.ts +86 -26
  67. package/src/federationSpec.ts +2 -2
  68. package/src/genErrorCodeDoc.ts +13 -7
  69. package/src/inaccessibleSpec.ts +978 -56
  70. package/src/joinSpec.ts +5 -1
  71. package/src/operations.ts +68 -2
  72. package/src/precompute.ts +2 -4
  73. package/src/schemaUpgrader.ts +70 -6
  74. package/src/supergraphs.ts +1 -0
  75. package/src/validate.ts +20 -9
  76. package/src/values.ts +39 -12
  77. package/tsconfig.test.tsbuildinfo +1 -1
  78. package/tsconfig.tsbuildinfo +1 -1
@@ -24,7 +24,7 @@ import {
24
24
  FeatureUrl,
25
25
  findCoreSpecVersion,
26
26
  isCoreSpecDirectiveApplication,
27
- removeFeatureElements,
27
+ removeAllCoreFeatures,
28
28
  } from "./coreSpec";
29
29
  import { assert, mapValues, MapWithCachedArrays, setValues } from "./utils";
30
30
  import { withDefaultValues, valueEquals, valueToString, valueToAST, variablesInValue, valueFromAST, valueNodeToConstValueNode } from "./values";
@@ -47,7 +47,15 @@ const DEFAULT_VALIDATION_ERROR_MESSAGE = 'The schema is not a valid GraphQL sche
47
47
 
48
48
  export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: string = DEFAULT_VALIDATION_ERROR_MESSAGE) =>
49
49
  err(validationErrorCode, {
50
- message,
50
+ message: message + '. Caused by:\n' + causes.map((c) => c.toString()).join('\n\n'),
51
+ causes
52
+ });
53
+
54
+ const apiSchemaValidationErrorCode = 'GraphQLAPISchemaValidationFailed';
55
+
56
+ export const ErrGraphQLAPISchemaValidationFailed = (causes: GraphQLError[]) =>
57
+ err(apiSchemaValidationErrorCode, {
58
+ message: 'The supergraph schema failed to produce a valid API schema',
51
59
  causes
52
60
  });
53
61
 
@@ -57,7 +65,7 @@ export const ErrGraphQLValidationFailed = (causes: GraphQLError[], message: stri
57
65
  */
58
66
  export function errorCauses(e: Error): GraphQLError[] | undefined {
59
67
  if (e instanceof GraphQLErrorExt) {
60
- if (e.code === validationErrorCode) {
68
+ if (e.code === validationErrorCode || e.code === apiSchemaValidationErrorCode) {
61
69
  return ((e as any).causes) as GraphQLError[];
62
70
  }
63
71
  return [e];
@@ -214,6 +222,22 @@ export function isInputType(type: Type): type is InputType {
214
222
  }
215
223
  }
216
224
 
225
+ export function isTypeOfKind<T extends Type>(type: Type, kind: T['kind']): type is T {
226
+ return type.kind === kind;
227
+ }
228
+
229
+ export function filterTypesOfKind<T extends Type>(types: readonly Type[], kind: T['kind']): T[] {
230
+ return types.reduce(
231
+ (acc: T[], type: Type) => {
232
+ if (isTypeOfKind(type, kind)) {
233
+ acc.push(type);
234
+ }
235
+ return acc;
236
+ },
237
+ [],
238
+ );
239
+ }
240
+
217
241
  export function baseType(type: Type): NamedType {
218
242
  return isWrapperType(type) ? type.baseType() : type;
219
243
  }
@@ -383,8 +407,8 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
383
407
  }
384
408
  }
385
409
 
386
- export function sourceASTs(...elts: ({ sourceAST?: ASTNode } | undefined)[]): ASTNode[] {
387
- return elts.map(elt => elt?.sourceAST).filter((elt): elt is ASTNode => elt !== undefined);
410
+ export function sourceASTs<TNode extends ASTNode = ASTNode>(...elts: ({ sourceAST?: TNode } | undefined)[]): TNode[] {
411
+ return elts.map(elt => elt?.sourceAST).filter((elt): elt is TNode => elt !== undefined);
388
412
  }
389
413
 
390
414
  // Not exposed: mostly about avoid code duplication between SchemaElement and Directive (which is not a SchemaElement as it can't
@@ -492,39 +516,30 @@ export abstract class SchemaElement<TOwnType extends SchemaElement<any, TParent>
492
516
  }
493
517
 
494
518
  applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
495
- nameOrDefOrDirective: Directive<TOwnType, TApplicationArgs> | DirectiveDefinition<TApplicationArgs> | string,
519
+ nameOrDef: DirectiveDefinition<TApplicationArgs> | string,
496
520
  args?: TApplicationArgs,
497
521
  asFirstDirective: boolean = false,
498
522
  ): Directive<TOwnType, TApplicationArgs> {
499
- let toAdd: Directive<TOwnType, TApplicationArgs>;
500
- if (nameOrDefOrDirective instanceof Directive) {
501
- this.checkUpdate(nameOrDefOrDirective);
502
- toAdd = nameOrDefOrDirective;
503
- if (args) {
504
- toAdd.setArguments(args);
523
+ let name: string;
524
+ if (typeof nameOrDef === 'string') {
525
+ this.checkUpdate();
526
+ const def = this.schema().directive(nameOrDef) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDef, args);
527
+ if (!def) {
528
+ throw this.schema().blueprint.onGraphQLJSValidationError(
529
+ this.schema(),
530
+ new GraphQLError(`Unknown directive "@${nameOrDef}".`)
531
+ );
505
532
  }
506
- } else {
507
- let name: string;
508
- if (typeof nameOrDefOrDirective === 'string') {
509
- this.checkUpdate();
510
- const def = this.schema().directive(nameOrDefOrDirective) ?? this.schema().blueprint.onMissingDirectiveDefinition(this.schema(), nameOrDefOrDirective, args);
511
- if (!def) {
512
- throw this.schema().blueprint.onGraphQLJSValidationError(
513
- this.schema(),
514
- new GraphQLError(`Unknown directive "@${nameOrDefOrDirective}".`)
515
- );
516
- }
517
- if (Array.isArray(def)) {
518
- throw ErrGraphQLValidationFailed(def);
519
- }
520
- name = nameOrDefOrDirective;
521
- } else {
522
- this.checkUpdate(nameOrDefOrDirective);
523
- name = nameOrDefOrDirective.name;
533
+ if (Array.isArray(def)) {
534
+ throw ErrGraphQLValidationFailed(def);
524
535
  }
525
- toAdd = new Directive<TOwnType, TApplicationArgs>(name, args ?? Object.create(null));
526
- Element.prototype['setParent'].call(toAdd, this);
536
+ name = nameOrDef;
537
+ } else {
538
+ this.checkUpdate(nameOrDef);
539
+ name = nameOrDef.name;
527
540
  }
541
+ const toAdd = new Directive<TOwnType, TApplicationArgs>(name, args ?? Object.create(null));
542
+ Element.prototype['setParent'].call(toAdd, this);
528
543
  // TODO: we should typecheck arguments or our TApplicationArgs business is just a lie.
529
544
  if (asFirstDirective) {
530
545
  this._appliedDirectives.unshift(toAdd);
@@ -708,7 +723,7 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
708
723
  /**
709
724
  * Removes this type definition from its parent schema.
710
725
  *
711
- * After calling this method, this type will be "detached": it wil have no parent, schema, fields,
726
+ * After calling this method, this type will be "detached": it will have no parent, schema, fields,
712
727
  * values, directives, etc...
713
728
  *
714
729
  * Note that it is always allowed to remove a type, but this may make a valid schema
@@ -724,15 +739,18 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
724
739
  }
725
740
  this.checkRemoval();
726
741
  this.onModification();
727
- this.removeInnerElements();
728
- Schema.prototype['removeTypeInternal'].call(this._parent, this);
729
- this.removeAppliedDirectives();
742
+ // Remove this type's children.
730
743
  this.sourceAST = undefined;
744
+ this.removeAppliedDirectives();
745
+ this.removeInnerElements();
746
+ // Remove this type's references.
731
747
  const toReturn = setValues(this._referencers).map(r => {
732
748
  SchemaElement.prototype['removeTypeReferenceInternal'].call(r, this);
733
749
  return r;
734
750
  });
735
751
  this._referencers.clear();
752
+ // Remove this type from its parent schema.
753
+ Schema.prototype['removeTypeInternal'].call(this._parent, this);
736
754
  this._parent = undefined;
737
755
  return toReturn;
738
756
  }
@@ -919,17 +937,22 @@ export class CoreFeature {
919
937
  }
920
938
 
921
939
  isFeatureDefinition(element: NamedType | DirectiveDefinition): boolean {
940
+ const importName = element.kind === 'DirectiveDefinition'
941
+ ? '@' + element.name
942
+ : element.name;
922
943
  return element.name.startsWith(this.nameInSchema + '__')
923
944
  || (element.kind === 'DirectiveDefinition' && element.name === this.nameInSchema)
924
- || !!this.imports.find((i) => element.name === (i.as ?? i.name));
945
+ || !!this.imports.find((i) => importName === (i.as ?? i.name));
925
946
  }
926
947
 
927
948
  directiveNameInSchema(name: string): string {
928
- if (name === this.url.name) {
929
- return this.nameInSchema;
930
- }
931
949
  const elementImport = this.imports.find((i) => i.name.charAt(0) === '@' && i.name.slice(1) === name);
932
- return elementImport ? (elementImport.as?.slice(1) ?? name) : this.nameInSchema + '__' + name;
950
+ return elementImport
951
+ ? (elementImport.as?.slice(1) ?? name)
952
+ : (name === this.url.name
953
+ ? this.nameInSchema
954
+ : this.nameInSchema + '__' + name
955
+ );
933
956
  }
934
957
 
935
958
  typeNameInSchema(name: string): string {
@@ -1136,13 +1159,7 @@ export class Schema {
1136
1159
 
1137
1160
  const apiSchema = this.clone();
1138
1161
  removeInaccessibleElements(apiSchema);
1139
- const coreFeatures = apiSchema.coreFeatures;
1140
- if (coreFeatures) {
1141
- // Note that core being a feature itself, this will remove core itself and mark apiSchema as 'not core'
1142
- for (const coreFeature of coreFeatures.allFeatures()) {
1143
- removeFeatureElements(apiSchema, coreFeature);
1144
- }
1145
- }
1162
+ removeAllCoreFeatures(apiSchema);
1146
1163
  assert(!apiSchema.isCoreSchema(), "The API schema shouldn't be a core schema")
1147
1164
  apiSchema.validate();
1148
1165
  this.apiSchema = apiSchema;
@@ -1170,20 +1187,42 @@ export class Schema {
1170
1187
  /**
1171
1188
  * All the types defined on this schema, excluding the built-in types.
1172
1189
  */
1173
- types<T extends NamedType>(kind?: T['kind']): readonly T[] {
1174
- const allKinds = this._types.values();
1175
- return (kind ? allKinds.filter(t => t.kind === kind) : allKinds) as readonly T[];
1190
+ types(): readonly NamedType[] {
1191
+ return this._types.values();
1192
+ }
1193
+
1194
+ interfaceTypes(): readonly InterfaceType[] {
1195
+ return filterTypesOfKind<InterfaceType>(this.types(), 'InterfaceType');
1196
+ }
1197
+
1198
+ objectTypes(): readonly ObjectType[] {
1199
+ return filterTypesOfKind<ObjectType>(this.types(), 'ObjectType');
1200
+ }
1201
+
1202
+ unionTypes(): readonly UnionType[] {
1203
+ return filterTypesOfKind<UnionType>(this.types(), 'UnionType');
1204
+ }
1205
+
1206
+ scalarTypes(): readonly ScalarType[] {
1207
+ return filterTypesOfKind<ScalarType>(this.types(), 'ScalarType');
1208
+ }
1209
+
1210
+ inputTypes(): readonly InputObjectType[] {
1211
+ return filterTypesOfKind<InputObjectType>(this.types(), 'InputObjectType');
1212
+ }
1213
+
1214
+ enumTypes(): readonly EnumType[] {
1215
+ return filterTypesOfKind<EnumType>(this.types(), 'EnumType');
1176
1216
  }
1177
1217
 
1178
1218
  /**
1179
1219
  * All the built-in types for this schema (those that are not displayed when printing the schema).
1180
1220
  */
1181
- builtInTypes<T extends NamedType>(kind?: T['kind'], includeShadowed: boolean = false): readonly T[] {
1221
+ builtInTypes(includeShadowed: boolean = false): readonly NamedType[] {
1182
1222
  const allBuiltIns = this._builtInTypes.values();
1183
- const forKind = (kind ? allBuiltIns.filter(t => t.kind === kind) : allBuiltIns) as readonly T[];
1184
1223
  return includeShadowed
1185
- ? forKind
1186
- : forKind.filter(t => !this.isShadowedBuiltInType(t));
1224
+ ? allBuiltIns
1225
+ : allBuiltIns.filter(t => !this.isShadowedBuiltInType(t));
1187
1226
  }
1188
1227
 
1189
1228
  private isShadowedBuiltInType(type: NamedType) {
@@ -1193,8 +1232,8 @@ export class Schema {
1193
1232
  /**
1194
1233
  * All the types, including the built-in ones.
1195
1234
  */
1196
- allTypes<T extends NamedType>(kind?: T['kind']): readonly T[] {
1197
- return this.builtInTypes(kind).concat(this.types(kind));
1235
+ allTypes(): readonly NamedType[] {
1236
+ return this.builtInTypes().concat(this.types());
1198
1237
  }
1199
1238
 
1200
1239
  /**
@@ -1492,11 +1531,11 @@ export class SchemaDefinition extends SchemaElement<SchemaDefinition, Schema> {
1492
1531
  }
1493
1532
 
1494
1533
  applyDirective<TApplicationArgs extends {[key: string]: any} = {[key: string]: any}>(
1495
- nameOrDefOrDirective: Directive<SchemaDefinition, TApplicationArgs> | DirectiveDefinition<TApplicationArgs> | string,
1534
+ nameOrDef: DirectiveDefinition<TApplicationArgs> | string,
1496
1535
  args?: TApplicationArgs,
1497
1536
  asFirstDirective: boolean = false,
1498
1537
  ): Directive<SchemaDefinition, TApplicationArgs> {
1499
- const applied = super.applyDirective(nameOrDefOrDirective, args, asFirstDirective) as Directive<SchemaDefinition, TApplicationArgs>;
1538
+ const applied = super.applyDirective(nameOrDef, args, asFirstDirective) as Directive<SchemaDefinition, TApplicationArgs>;
1500
1539
  const schema = this.schema();
1501
1540
  const coreFeatures = schema.coreFeatures;
1502
1541
  if (isCoreSpecDirectiveApplication(applied)) {
@@ -2021,7 +2060,12 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
2021
2060
  protected readonly _values: EnumValue[] = [];
2022
2061
 
2023
2062
  get values(): readonly EnumValue[] {
2024
- return this._values;
2063
+ // Because our abstractions are mutable, and removal is done by calling
2064
+ // `remove()` on the element to remove, it's not unlikely someone mauy
2065
+ // try to iterate on the result of this method and call `remove()` on
2066
+ // some of the return value based on some condition. But this will break
2067
+ // in an error-prone way if we don't copy, so we do.
2068
+ return Array.from(this._values);
2025
2069
  }
2026
2070
 
2027
2071
  value(name: string): EnumValue | undefined {
@@ -2320,23 +2364,36 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
2320
2364
  /**
2321
2365
  * Removes this field definition from its parent type.
2322
2366
  *
2323
- * After calling this method, this field definition will be "detached": it wil have no parent, schema, type,
2324
- * arguments or directives.
2367
+ * After calling this method, this field definition will be "detached": it will have no parent, schema, type,
2368
+ * arguments, or directives.
2325
2369
  */
2326
2370
  remove(): never[] {
2327
2371
  if (!this._parent) {
2328
2372
  return [];
2329
2373
  }
2374
+ this.checkRemoval();
2330
2375
  this.onModification();
2331
- this.removeAppliedDirectives();
2376
+ // Remove this field's children.
2377
+ this.sourceAST = undefined;
2332
2378
  this.type = undefined;
2333
- this._extension = undefined;
2379
+ this.removeAppliedDirectives();
2334
2380
  for (const arg of this.arguments()) {
2335
2381
  arg.remove();
2336
2382
  }
2383
+ // Note that we don't track field references outside of parents, so no
2384
+ // removal needed there.
2385
+ //
2386
+ // TODO: One could consider interface fields as references to implementing
2387
+ // object/interface fields, in the sense that removing an implementing
2388
+ // object/interface field breaks the validity of the implementing
2389
+ // interface field. Being aware that an object/interface field is being
2390
+ // referenced in such a way would be useful for understanding breakages
2391
+ // that need to be resolved as a consequence of removal.
2392
+ //
2393
+ // Remove this field from its parent object/interface type.
2337
2394
  FieldBasedType.prototype['removeFieldInternal'].call(this._parent, this);
2338
2395
  this._parent = undefined;
2339
- // Fields have nothing that can reference them outside of their parents
2396
+ this._extension = undefined;
2340
2397
  return [];
2341
2398
  }
2342
2399
 
@@ -2399,20 +2456,38 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
2399
2456
  }
2400
2457
 
2401
2458
  /**
2402
- * Removes this field definition from its parent type.
2459
+ * Removes this input field definition from its parent type.
2403
2460
  *
2404
- * After calling this method, this field definition will be "detached": it wil have no parent, schema, type,
2405
- * arguments or directives.
2461
+ * After calling this method, this input field definition will be "detached": it will have no parent, schema,
2462
+ * type, default value, or directives.
2406
2463
  */
2407
2464
  remove(): never[] {
2408
2465
  if (!this._parent) {
2409
2466
  return [];
2410
2467
  }
2468
+ this.checkRemoval();
2411
2469
  this.onModification();
2470
+ // Remove this input field's children.
2471
+ this.sourceAST = undefined;
2472
+ this.type = undefined;
2473
+ this.defaultValue = undefined;
2474
+ this.removeAppliedDirectives();
2475
+ // Note that we don't track input field references outside of parents, so no
2476
+ // removal needed there.
2477
+ //
2478
+ // TODO: One could consider default values (in field arguments, input
2479
+ // fields, or directive definitions) as references to input fields they
2480
+ // use, in the sense that removing the input field breaks the validity of
2481
+ // the default value. Being aware that an input field is being referenced
2482
+ // in such a way would be useful for understanding breakages that need to
2483
+ // be resolved as a consequence of removal. (The reference is indirect
2484
+ // though, as input field usages are currently represented as strings
2485
+ // within GraphQL values).
2486
+ //
2487
+ // Remove this input field from its parent input object type.
2412
2488
  InputObjectType.prototype['removeFieldInternal'].call(this._parent, this);
2413
2489
  this._parent = undefined;
2414
- this.type = undefined;
2415
- // Fields have nothing that can reference them outside of their parents
2490
+ this._extension = undefined;
2416
2491
  return [];
2417
2492
  }
2418
2493
 
@@ -2457,22 +2532,39 @@ export class ArgumentDefinition<TParent extends FieldDefinition<any> | Directive
2457
2532
  /**
2458
2533
  * Removes this argument definition from its parent element (field or directive).
2459
2534
  *
2460
- * After calling this method, this argument definition will be "detached": it wil have no parent, schema, type,
2461
- * default value or directives.
2535
+ * After calling this method, this argument definition will be "detached": it will have no parent, schema, type,
2536
+ * default value, or directives.
2462
2537
  */
2463
2538
  remove(): never[] {
2464
2539
  if (!this._parent) {
2465
2540
  return [];
2466
2541
  }
2542
+ this.checkRemoval();
2467
2543
  this.onModification();
2544
+ // Remove this argument's children.
2545
+ this.sourceAST = undefined;
2546
+ this.type = undefined;
2547
+ this.defaultValue = undefined;
2548
+ this.removeAppliedDirectives();
2549
+ // Note that we don't track argument references outside of parents, so no
2550
+ // removal needed there.
2551
+ //
2552
+ // TODO: One could consider the arguments of directive applications as
2553
+ // references to the arguments of directive definitions, in the sense that
2554
+ // removing a directive definition argument can break the validity of the
2555
+ // directive application. Being aware that a directive definition argument
2556
+ // is being referenced in such a way would be useful for understanding
2557
+ // breakages that need to be resolved as a consequence of removal. (You
2558
+ // could make a similar claim about interface field arguments being
2559
+ // references to object field arguments.)
2560
+ //
2561
+ // Remove this argument from its parent field or directive definition.
2468
2562
  if (this._parent instanceof FieldDefinition) {
2469
2563
  FieldDefinition.prototype['removeArgumentInternal'].call(this._parent, this.name);
2470
2564
  } else {
2471
2565
  DirectiveDefinition.prototype['removeArgumentInternal'].call(this._parent, this.name);
2472
2566
  }
2473
2567
  this._parent = undefined;
2474
- this.type = undefined;
2475
- this.defaultValue = undefined;
2476
2568
  return [];
2477
2569
  }
2478
2570
 
@@ -2513,23 +2605,36 @@ export class EnumValue extends NamedSchemaElement<EnumValue, EnumType, never> {
2513
2605
  }
2514
2606
 
2515
2607
  /**
2516
- * Removes this field definition from its parent type.
2608
+ * Removes this enum value definition from its parent type.
2517
2609
  *
2518
- * After calling this method, this field definition will be "detached": it wil have no parent, schema, type,
2519
- * arguments or directives.
2610
+ * After calling this method, this enum value definition will be "detached": it will have no parent, schema, type,
2611
+ * arguments, or directives.
2520
2612
  */
2521
2613
  remove(): never[] {
2522
2614
  if (!this._parent) {
2523
2615
  return [];
2524
2616
  }
2617
+ this.checkRemoval();
2525
2618
  this.onModification();
2619
+ // Remove this enum value's children.
2620
+ this.sourceAST = undefined;
2621
+ this.removeAppliedDirectives();
2622
+ // Note that we don't track enum value references outside of parents, so no
2623
+ // removal needed there.
2624
+ //
2625
+ // TODO: One could consider default values (in field arguments, input
2626
+ // fields, or directive definitions) as references to enum values they
2627
+ // use, in the sense that removing the enum value breaks the validity of
2628
+ // the default value. Being aware that an enum value is being referenced
2629
+ // in such a way would be useful for understanding breakages that need to
2630
+ // be resolved as a consequence of removal. (The reference is indirect
2631
+ // though, as enum value usages are currently represented as strings
2632
+ // within GraphQL values).
2633
+ //
2634
+ // Remove this enum value from its parent enum type.
2526
2635
  EnumType.prototype['removeValueInternal'].call(this._parent, this);
2527
2636
  this._parent = undefined;
2528
- // Enum values have nothing that can reference them outside of their parents
2529
- // TODO: that's actually only semi-true if you include arguments, because default values in args and concrete directive applications can
2530
- // indirectly refer to enum value. It's indirect though as we currently keep enum value as string in values. That said, it would
2531
- // probably be really nice to be able to known if an enum value is used or not, rather then removing it and not knowing if we broke
2532
- // something).
2637
+ this._extension = undefined;
2533
2638
  return [];
2534
2639
  }
2535
2640
 
@@ -2659,22 +2764,35 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
2659
2764
  assert(false, `Directive definition ${this} can't reference other types (it's arguments can); shouldn't be asked to remove reference to ${type}`);
2660
2765
  }
2661
2766
 
2767
+ /**
2768
+ * Removes this directive definition from its parent schema.
2769
+ *
2770
+ * After calling this method, this directive definition will be "detached": it will have no parent, schema, or
2771
+ * arguments.
2772
+ */
2662
2773
  remove(): Directive[] {
2663
2774
  if (!this._parent) {
2664
2775
  return [];
2665
2776
  }
2777
+ this.checkRemoval();
2666
2778
  this.onModification();
2667
- Schema.prototype['removeDirectiveInternal'].call(this._parent, this);
2668
- this._parent = undefined;
2779
+ // Remove this directive definition's children.
2780
+ this.sourceAST = undefined;
2669
2781
  assert(this._appliedDirectives.length === 0, "Directive definition should not have directive applied to it");
2670
2782
  for (const arg of this.arguments()) {
2671
2783
  arg.remove();
2672
2784
  }
2673
- // Note that directive applications don't link directly to their definitions. Instead, we fetch
2674
- // their definition from the schema when requested. So we don't have to do anything on the referencers
2675
- // other than return them.
2785
+ // Remove this directive definition's references.
2786
+ //
2787
+ // Note that while a directive application references its definition, it
2788
+ // doesn't store a link to that definition. Instead, we fetch the definition
2789
+ // from the schema when requested. So we don't have to do anything on the
2790
+ // referencers other than clear them (and return the pre-cleared set).
2676
2791
  const toReturn = setValues(this._referencers);
2677
2792
  this._referencers.clear();
2793
+ // Remove this directive definition from its parent schema.
2794
+ Schema.prototype['removeDirectiveInternal'].call(this._parent, this);
2795
+ this._parent = undefined;
2678
2796
  return toReturn;
2679
2797
  }
2680
2798
 
@@ -2840,10 +2958,12 @@ export class Directive<
2840
2958
  if (!this._parent) {
2841
2959
  return false;
2842
2960
  }
2961
+ // Remove this directive application's reference to its definition.
2843
2962
  const definition = this.definition;
2844
2963
  if (definition && this.isAttachedToSchemaElement()) {
2845
2964
  DirectiveDefinition.prototype['removeReferencer'].call(definition, this as Directive<SchemaElement<any, any>>);
2846
2965
  }
2966
+ // Remove this directive application from its parent schema element.
2847
2967
  const parentDirectives = this._parent.appliedDirectives as Directive<TParent>[];
2848
2968
  const index = parentDirectives.indexOf(this);
2849
2969
  assert(index >= 0, () => `Directive ${this} lists ${this._parent} as parent, but that parent doesn't list it as applied directive`);
@@ -4,7 +4,9 @@ import {
4
4
  DirectiveDefinition,
5
5
  EnumType,
6
6
  InputType,
7
+ isCustomScalarType,
7
8
  isEnumType,
9
+ isListType,
8
10
  isNonNullType,
9
11
  isObjectType,
10
12
  isUnionType,
@@ -310,7 +312,7 @@ function ensureSameArguments(
310
312
  // is optional if you so wish.
311
313
  actualType = actualType.ofType;
312
314
  }
313
- if (!sameType(type, actualType)) {
315
+ if (!sameType(type, actualType) && !isValidInputTypeRedefinition(type, actualType)) {
314
316
  errors.push(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
315
317
  message: `Invalid definition for ${what}: argument "${name}" should have type "${type}" but found type "${actualArgument.type!}"`,
316
318
  nodes: actualArgument.sourceAST
@@ -334,3 +336,18 @@ function ensureSameArguments(
334
336
  return errors;
335
337
  }
336
338
 
339
+ function isValidInputTypeRedefinition(expectedType: InputType, actualType: InputType): boolean {
340
+ // If the expected type is a custom scalar, then we allow the redefinition to be another type (unless it's a custom scalar, in which
341
+ // case it has to be the same scalar). The rational being that since graphQL does no validation of values passed to a custom scalar,
342
+ // any code that gets some value as input for a custom scalar has to do validation manually, and so there is little harm in allowing
343
+ // a redefinition with another type since any truly invalid value would failed that "manual validation". In practice, this leeway
344
+ // make sense because many scalar will tend to accept only one kind of values (say, strings) and exists only to inform that said string
345
+ // needs to follow a specific format, and in such case, letting user redefine the type as String adds flexibility while doing little harm.
346
+ if (isListType(expectedType)) {
347
+ return isListType(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType);
348
+ }
349
+ if (isNonNullType(expectedType)) {
350
+ return isNonNullType(actualType) && isValidInputTypeRedefinition(expectedType.ofType, actualType.ofType);
351
+ }
352
+ return isCustomScalarType(expectedType) && !isCustomScalarType(actualType);
353
+ }