@apollo/federation-internals 2.0.0-alpha.2 → 2.0.0-alpha.6

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 (66) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/buildSchema.d.ts.map +1 -1
  3. package/dist/buildSchema.js +3 -3
  4. package/dist/buildSchema.js.map +1 -1
  5. package/dist/debug.d.ts.map +1 -1
  6. package/dist/debug.js +2 -18
  7. package/dist/debug.js.map +1 -1
  8. package/dist/definitions.d.ts +18 -7
  9. package/dist/definitions.d.ts.map +1 -1
  10. package/dist/definitions.js +80 -26
  11. package/dist/definitions.js.map +1 -1
  12. package/dist/error.d.ts +88 -3
  13. package/dist/error.d.ts.map +1 -1
  14. package/dist/error.js +145 -5
  15. package/dist/error.js.map +1 -1
  16. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  17. package/dist/extractSubgraphsFromSupergraph.js +41 -4
  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 +231 -58
  22. package/dist/federation.js.map +1 -1
  23. package/dist/genErrorCodeDoc.d.ts +2 -0
  24. package/dist/genErrorCodeDoc.d.ts.map +1 -0
  25. package/dist/genErrorCodeDoc.js +55 -0
  26. package/dist/genErrorCodeDoc.js.map +1 -0
  27. package/dist/inaccessibleSpec.d.ts.map +1 -1
  28. package/dist/inaccessibleSpec.js +1 -1
  29. package/dist/inaccessibleSpec.js.map +1 -1
  30. package/dist/joinSpec.d.ts.map +1 -1
  31. package/dist/joinSpec.js +6 -5
  32. package/dist/joinSpec.js.map +1 -1
  33. package/dist/operations.d.ts.map +1 -1
  34. package/dist/operations.js +15 -15
  35. package/dist/operations.js.map +1 -1
  36. package/dist/tagSpec.d.ts +2 -2
  37. package/dist/tagSpec.d.ts.map +1 -1
  38. package/dist/tagSpec.js +10 -2
  39. package/dist/tagSpec.js.map +1 -1
  40. package/dist/utils.d.ts +2 -0
  41. package/dist/utils.d.ts.map +1 -1
  42. package/dist/utils.js +34 -1
  43. package/dist/utils.js.map +1 -1
  44. package/dist/values.d.ts +2 -1
  45. package/dist/values.d.ts.map +1 -1
  46. package/dist/values.js +27 -1
  47. package/dist/values.js.map +1 -1
  48. package/jest.config.js +5 -1
  49. package/package.json +3 -6
  50. package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +535 -0
  51. package/src/__tests__/subgraphValidation.test.ts +480 -0
  52. package/src/buildSchema.ts +7 -6
  53. package/src/debug.ts +2 -19
  54. package/src/definitions.ts +151 -40
  55. package/src/error.ts +340 -7
  56. package/src/extractSubgraphsFromSupergraph.ts +50 -5
  57. package/src/federation.ts +297 -92
  58. package/src/genErrorCodeDoc.ts +69 -0
  59. package/src/inaccessibleSpec.ts +7 -2
  60. package/src/joinSpec.ts +11 -5
  61. package/src/operations.ts +20 -18
  62. package/src/tagSpec.ts +11 -6
  63. package/src/utils.ts +49 -0
  64. package/src/values.ts +47 -5
  65. package/tsconfig.test.tsbuildinfo +1 -1
  66. package/tsconfig.tsbuildinfo +1 -1
@@ -1,10 +1,10 @@
1
1
  import {
2
- ArgumentNode,
2
+ ConstArgumentNode,
3
3
  ASTNode,
4
4
  buildASTSchema as buildGraphqlSchemaFromAST,
5
5
  DirectiveLocation,
6
- DirectiveLocationEnum,
7
- DirectiveNode,
6
+ ConstDirectiveNode,
7
+ ConstValueNode,
8
8
  DocumentNode,
9
9
  GraphQLError,
10
10
  GraphQLSchema,
@@ -19,7 +19,7 @@ import {
19
19
  } from "graphql";
20
20
  import { CoreDirectiveArgs, CoreSpecDefinition, CORE_VERSIONS, FeatureUrl, isCoreSpecDirectiveApplication, removeFeatureElements } from "./coreSpec";
21
21
  import { arrayEquals, assert, mapValues, MapWithCachedArrays, setValues } from "./utils";
22
- import { withDefaultValues, valueEquals, valueToString, valueToAST, variablesInValue, valueFromAST } from "./values";
22
+ import { withDefaultValues, valueEquals, valueToString, valueToAST, variablesInValue, valueFromAST, valueNodeToConstValueNode } from "./values";
23
23
  import { removeInaccessibleElements } from "./inaccessibleSpec";
24
24
  import { printSchema, Options, defaultPrintOptions } from './print';
25
25
  import { sameType } from './types';
@@ -233,15 +233,15 @@ export function runtimeTypesIntersects(t1: CompositeType, t2: CompositeType): bo
233
233
  return false;
234
234
  }
235
235
 
236
- export const executableDirectiveLocations: DirectiveLocationEnum[] = [
237
- 'QUERY',
238
- 'MUTATION',
239
- 'SUBSCRIPTION',
240
- 'FIELD',
241
- 'FRAGMENT_DEFINITION',
242
- 'FRAGMENT_SPREAD',
243
- 'INLINE_FRAGMENT',
244
- 'VARIABLE_DEFINITION',
236
+ export const executableDirectiveLocations: DirectiveLocation[] = [
237
+ DirectiveLocation.QUERY,
238
+ DirectiveLocation.MUTATION,
239
+ DirectiveLocation.SUBSCRIPTION,
240
+ DirectiveLocation.FIELD,
241
+ DirectiveLocation.FRAGMENT_DEFINITION,
242
+ DirectiveLocation.FRAGMENT_SPREAD,
243
+ DirectiveLocation.INLINE_FRAGMENT,
244
+ DirectiveLocation.VARIABLE_DEFINITION,
245
245
  ];
246
246
 
247
247
  /**
@@ -253,27 +253,27 @@ export function typeToAST(type: Type): TypeNode {
253
253
  switch (type.kind) {
254
254
  case 'ListType':
255
255
  return {
256
- kind: 'ListType',
256
+ kind: Kind.LIST_TYPE,
257
257
  type: typeToAST(type.ofType)
258
258
  };
259
259
  case 'NonNullType':
260
260
  return {
261
- kind: 'NonNullType',
261
+ kind: Kind.NON_NULL_TYPE,
262
262
  type: typeToAST(type.ofType) as NamedTypeNode | ListTypeNode
263
263
  };
264
264
  default:
265
265
  return {
266
- kind: 'NamedType',
267
- name: { kind: 'Name', value: type.name }
266
+ kind: Kind.NAMED_TYPE,
267
+ name: { kind: Kind.NAME, value: type.name }
268
268
  };
269
269
  }
270
270
  }
271
271
 
272
272
  export function typeFromAST(schema: Schema, node: TypeNode): Type {
273
273
  switch (node.kind) {
274
- case 'ListType':
274
+ case Kind.LIST_TYPE:
275
275
  return new ListType(typeFromAST(schema, node.type));
276
- case 'NonNullType':
276
+ case Kind.NON_NULL_TYPE:
277
277
  return new NonNullType(typeFromAST(schema, node.type) as NullableType);
278
278
  default:
279
279
  const type = schema.type(node.name.value);
@@ -339,14 +339,14 @@ export class DirectiveTargetElement<T extends DirectiveTargetElement<T>> {
339
339
  return toAdd;
340
340
  }
341
341
 
342
- appliedDirectivesToDirectiveNodes() : DirectiveNode[] | undefined {
342
+ appliedDirectivesToDirectiveNodes() : ConstDirectiveNode[] | undefined {
343
343
  if (this.appliedDirectives.length == 0) {
344
344
  return undefined;
345
345
  }
346
346
 
347
347
  return this.appliedDirectives.map(directive => {
348
348
  return {
349
- kind: 'Directive',
349
+ kind: Kind.DIRECTIVE,
350
350
  name: {
351
351
  kind: Kind.NAME,
352
352
  value: directive.name,
@@ -697,6 +697,26 @@ abstract class BaseNamedType<TReferencer, TOwnType extends NamedType & NamedSche
697
697
  return toReturn;
698
698
  }
699
699
 
700
+ /**
701
+ * Removes this this definition _and_, recursively, any other elements that references this type and would be invalid
702
+ * after the removal.
703
+ *
704
+ * Note that contrarily to `remove()` (which this method essentially call recursively), this method leaves the schema
705
+ * valid (assuming it was valid beforehand) _unless_ all the schema ends up being removed through recursion (in which
706
+ * case this leaves an empty schema, and that is not technically valid).
707
+ *
708
+ * Also note that this method does _not_ necessarily remove all the elements that reference this type: for instance,
709
+ * if this type is an interface, objects implementing it will _not_ be removed, they will simply stop implementing
710
+ * the interface. In practice, this method mainly remove fields that were using the removed type (in either argument or
711
+ * return type), but it can also remove object/input object/interface if through such field removal some type ends up
712
+ * empty, and it can remove unions if through that removal process and union becomes empty.
713
+ */
714
+ removeRecursive(): void {
715
+ this.remove().forEach(ref => this.removeReferenceRecursive(ref));
716
+ }
717
+
718
+ protected abstract removeReferenceRecursive(ref: TReferencer): void;
719
+
700
720
  referencers(): readonly TReferencer[] {
701
721
  return setValues(this._referencers);
702
722
  }
@@ -787,14 +807,18 @@ export class BuiltIns {
787
807
  addBuiltInDirectives(schema: Schema) {
788
808
  for (const name of ['include', 'skip']) {
789
809
  this.addBuiltInDirective(schema, name)
790
- .addLocations('FIELD', 'FRAGMENT_SPREAD', 'INLINE_FRAGMENT')
810
+ .addLocations(DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT)
791
811
  .addArgument('if', new NonNullType(schema.booleanType()));
792
812
  }
793
813
  this.addBuiltInDirective(schema, 'deprecated')
794
- .addLocations('FIELD_DEFINITION', 'ENUM_VALUE', 'ARGUMENT_DEFINITION', 'INPUT_FIELD_DEFINITION')
795
- .addArgument('reason', schema.stringType(), 'No longer supported');
814
+ .addLocations(
815
+ DirectiveLocation.FIELD_DEFINITION,
816
+ DirectiveLocation.ENUM_VALUE,
817
+ DirectiveLocation.ARGUMENT_DEFINITION,
818
+ DirectiveLocation.INPUT_FIELD_DEFINITION,
819
+ ).addArgument('reason', schema.stringType(), 'No longer supported');
796
820
  this.addBuiltInDirective(schema, 'specifiedBy')
797
- .addLocations('SCALAR')
821
+ .addLocations(DirectiveLocation.SCALAR)
798
822
  .addArgument('url', new NonNullType(schema.stringType()));
799
823
  }
800
824
 
@@ -1365,7 +1389,7 @@ export class Schema {
1365
1389
 
1366
1390
  // TODO: we should ensure first that there is no undefined types (or maybe throw properly when printing the AST
1367
1391
  // and catching that properly).
1368
- let errors: GraphQLError[] = validateSDL(this.toAST(), undefined, this.builtIns.validationRules());
1392
+ let errors = validateSDL(this.toAST(), undefined, this.builtIns.validationRules());
1369
1393
  errors = errors.concat(validateSchema(this));
1370
1394
 
1371
1395
  // We avoid adding federation-specific validations if the base schema is not proper graphQL as the later can easily trigger
@@ -1377,7 +1401,7 @@ export class Schema {
1377
1401
  }
1378
1402
 
1379
1403
  if (errors.length > 0) {
1380
- throw ErrGraphQLValidationFailed(errors);
1404
+ throw ErrGraphQLValidationFailed(errors as GraphQLError[]);
1381
1405
  }
1382
1406
 
1383
1407
  this.isValidated = true;
@@ -1530,6 +1554,10 @@ export class ScalarType extends BaseNamedType<OutputTypeReferencer | InputTypeRe
1530
1554
  protected removeInnerElements(): void {
1531
1555
  // No inner elements
1532
1556
  }
1557
+
1558
+ protected removeReferenceRecursive(ref: OutputTypeReferencer | InputTypeReferencer): void {
1559
+ ref.remove();
1560
+ }
1533
1561
  }
1534
1562
 
1535
1563
  export class InterfaceImplementation<T extends ObjectType | InterfaceType> extends BaseExtensionMember<T> {
@@ -1753,6 +1781,20 @@ export class ObjectType extends FieldBasedType<ObjectType, ObjectTypeReferencer>
1753
1781
  const schema = this.schema();
1754
1782
  return schema.schemaDefinition.root('query')?.type === this;
1755
1783
  }
1784
+
1785
+ protected removeReferenceRecursive(ref: ObjectTypeReferencer): void {
1786
+ // Note that the ref can also be a`SchemaDefinition`, but don't have anything to do then.
1787
+ switch (ref.kind) {
1788
+ case 'FieldDefinition':
1789
+ ref.removeRecursive();
1790
+ break;
1791
+ case 'UnionType':
1792
+ if (ref.membersCount() === 0) {
1793
+ ref.removeRecursive();
1794
+ }
1795
+ break;
1796
+ }
1797
+ }
1756
1798
  }
1757
1799
 
1758
1800
  export class InterfaceType extends FieldBasedType<InterfaceType, InterfaceTypeReferencer> {
@@ -1771,6 +1813,14 @@ export class InterfaceType extends FieldBasedType<InterfaceType, InterfaceTypeRe
1771
1813
  const typeName = typeof type === 'string' ? type : type.name;
1772
1814
  return this.possibleRuntimeTypes().some(t => t.name == typeName);
1773
1815
  }
1816
+
1817
+ protected removeReferenceRecursive(ref: InterfaceTypeReferencer): void {
1818
+ // Note that an interface can be referenced by an object/interface that implements it, but after remove(), said object/interface
1819
+ // will simply not implement "this" anymore and we have nothing more to do.
1820
+ if (ref.kind === 'FieldDefinition') {
1821
+ ref.removeRecursive();
1822
+ }
1823
+ }
1774
1824
  }
1775
1825
 
1776
1826
  export class UnionMember extends BaseExtensionMember<UnionType> {
@@ -1895,6 +1945,10 @@ export class UnionType extends BaseNamedType<OutputTypeReferencer, UnionType> {
1895
1945
  protected hasNonExtensionInnerElements(): boolean {
1896
1946
  return this.members().some(m => m.ofExtension() === undefined);
1897
1947
  }
1948
+
1949
+ protected removeReferenceRecursive(ref: OutputTypeReferencer): void {
1950
+ ref.removeRecursive();
1951
+ }
1898
1952
  }
1899
1953
 
1900
1954
  export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
@@ -1949,6 +2003,10 @@ export class EnumType extends BaseNamedType<OutputTypeReferencer, EnumType> {
1949
2003
  protected hasNonExtensionInnerElements(): boolean {
1950
2004
  return this._values.some(v => v.ofExtension() === undefined);
1951
2005
  }
2006
+
2007
+ protected removeReferenceRecursive(ref: OutputTypeReferencer): void {
2008
+ ref.removeRecursive();
2009
+ }
1952
2010
  }
1953
2011
 
1954
2012
  export class InputObjectType extends BaseNamedType<InputTypeReferencer, InputObjectType> {
@@ -2019,6 +2077,19 @@ export class InputObjectType extends BaseNamedType<InputTypeReferencer, InputObj
2019
2077
  protected hasNonExtensionInnerElements(): boolean {
2020
2078
  return this.fields().some(f => f.ofExtension() === undefined);
2021
2079
  }
2080
+
2081
+ protected removeReferenceRecursive(ref: InputTypeReferencer): void {
2082
+ if (ref.kind === 'ArgumentDefinition') {
2083
+ // Not only do we want to remove the argument, but we want to remove its parent. Technically, only removing the argument would
2084
+ // leave the schema in a valid state so it would be an option, but this feel a bit too weird of a behaviour in practice for a
2085
+ // method calling `removeRecursive`. And in particular, it would mean that if the argument is a directive definition one,
2086
+ // we'd also have to update each of the directive application to remove the correspond argument. Removing the full directive
2087
+ // definition (and all its applications) feels a bit more predictable.
2088
+ ref.parent().removeRecursive();
2089
+ } else {
2090
+ ref.removeRecursive();
2091
+ }
2092
+ }
2022
2093
  }
2023
2094
 
2024
2095
  class BaseWrapperType<T extends Type> {
@@ -2192,6 +2263,19 @@ export class FieldDefinition<TParent extends CompositeType> extends NamedSchemaE
2192
2263
  return [];
2193
2264
  }
2194
2265
 
2266
+ /**
2267
+ * Like `remove()`, but if this field was the last field of its parent type, the parent type is removed through its `removeRecursive` method.
2268
+ */
2269
+ removeRecursive(): void {
2270
+ const parent = this._parent;
2271
+ this.remove();
2272
+ // Note that we exclude the union type here because it doesn't have the `fields()` method, but the only field unions can have is the __typename
2273
+ // one and it cannot be removed, so remove() above will actually throw in practice before reaching this.
2274
+ if (parent && !isUnionType(parent) && parent.fields().length === 0) {
2275
+ parent.removeRecursive();
2276
+ }
2277
+ }
2278
+
2195
2279
  toString(): string {
2196
2280
  const args = this._args.size == 0
2197
2281
  ? ""
@@ -2251,6 +2335,17 @@ export class InputFieldDefinition extends NamedSchemaElementWithType<InputType,
2251
2335
  return [];
2252
2336
  }
2253
2337
 
2338
+ /**
2339
+ * Like `remove()`, but if this field was the last field of its parent type, the parent type is removed through its `removeRecursive` method.
2340
+ */
2341
+ removeRecursive(): void {
2342
+ const parent = this._parent;
2343
+ this.remove();
2344
+ if (parent && parent.fields().length === 0) {
2345
+ parent.removeRecursive();
2346
+ }
2347
+ }
2348
+
2254
2349
  toString(): string {
2255
2350
  const defaultStr = this.defaultValue === undefined ? "" : ` = ${valueToString(this.defaultValue, this.type)}`;
2256
2351
  return `${this.name}: ${this.type}${defaultStr}`;
@@ -2367,7 +2462,7 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
2367
2462
 
2368
2463
  private readonly _args: MapWithCachedArrays<string, ArgumentDefinition<DirectiveDefinition>> = new MapWithCachedArrays();
2369
2464
  repeatable: boolean = false;
2370
- private readonly _locations: DirectiveLocationEnum[] = [];
2465
+ private readonly _locations: DirectiveLocation[] = [];
2371
2466
  private readonly _referencers: Set<Directive<SchemaElement<any, any>, TApplicationArgs>> = new Set();
2372
2467
 
2373
2468
  constructor(name: string, readonly isBuiltIn: boolean = false) {
@@ -2414,11 +2509,11 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
2414
2509
  this._args.delete(name);
2415
2510
  }
2416
2511
 
2417
- get locations(): readonly DirectiveLocationEnum[] {
2512
+ get locations(): readonly DirectiveLocation[] {
2418
2513
  return this._locations;
2419
2514
  }
2420
2515
 
2421
- addLocations(...locations: DirectiveLocationEnum[]): DirectiveDefinition {
2516
+ addLocations(...locations: DirectiveLocation[]): DirectiveDefinition {
2422
2517
  let modified = false;
2423
2518
  for (const location of locations) {
2424
2519
  if (!this._locations.includes(location)) {
@@ -2437,10 +2532,17 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
2437
2532
  }
2438
2533
 
2439
2534
  addAllTypeLocations(): DirectiveDefinition {
2440
- return this.addLocations('SCALAR', 'OBJECT', 'INTERFACE', 'UNION', 'ENUM', 'INPUT_OBJECT');
2535
+ return this.addLocations(
2536
+ DirectiveLocation.SCALAR,
2537
+ DirectiveLocation.OBJECT,
2538
+ DirectiveLocation.INTERFACE,
2539
+ DirectiveLocation.UNION,
2540
+ DirectiveLocation.ENUM,
2541
+ DirectiveLocation.INPUT_OBJECT,
2542
+ );
2441
2543
  }
2442
2544
 
2443
- removeLocations(...locations: DirectiveLocationEnum[]): DirectiveDefinition {
2545
+ removeLocations(...locations: DirectiveLocation[]): DirectiveDefinition {
2444
2546
  let modified = false;
2445
2547
  for (const location of locations) {
2446
2548
  const index = this._locations.indexOf(location);
@@ -2491,6 +2593,13 @@ export class DirectiveDefinition<TApplicationArgs extends {[key: string]: any} =
2491
2593
  return toReturn;
2492
2594
  }
2493
2595
 
2596
+ /**
2597
+ * Removes this this directive definition _and_ all its applications.
2598
+ */
2599
+ removeRecursive(): void {
2600
+ this.remove().forEach(ref => ref.remove());
2601
+ }
2602
+
2494
2603
  toString(): string {
2495
2604
  return `@${this.name}`;
2496
2605
  }
@@ -2588,7 +2697,7 @@ export class Directive<
2588
2697
  this.onModification();
2589
2698
  }
2590
2699
 
2591
- argumentsToAST(): ArgumentNode[] | undefined {
2700
+ argumentsToAST(): ConstArgumentNode[] | undefined {
2592
2701
  const entries = Object.entries(this._args);
2593
2702
  if (entries.length === 0) {
2594
2703
  return undefined;
@@ -2598,9 +2707,9 @@ export class Directive<
2598
2707
  assert(definition, () => `Cannot convert arguments of detached directive ${this}`);
2599
2708
  return entries.map(([n, v]) => {
2600
2709
  return {
2601
- kind: 'Argument',
2710
+ kind: Kind.ARGUMENT,
2602
2711
  name: { kind: Kind.NAME, value: n },
2603
- value: valueToAST(v, definition.argument(n)!.type!)!,
2712
+ value: valueToAST(v, definition.argument(n)!.type!)! as ConstValueNode,
2604
2713
  };
2605
2714
  });
2606
2715
  }
@@ -2664,8 +2773,8 @@ export class Variable {
2664
2773
 
2665
2774
  toVariableNode(): VariableNode {
2666
2775
  return {
2667
- kind: 'Variable',
2668
- name: { kind: 'Name', value: this.name },
2776
+ kind: Kind.VARIABLE,
2777
+ name: { kind: Kind.NAME, value: this.name },
2669
2778
  }
2670
2779
  }
2671
2780
 
@@ -2719,12 +2828,14 @@ export class VariableDefinition extends DirectiveTargetElement<VariableDefinitio
2719
2828
  }
2720
2829
 
2721
2830
  toVariableDefinitionNode(): VariableDefinitionNode {
2831
+ const ast = valueToAST(this.defaultValue, this.type);
2832
+
2722
2833
  return {
2723
- kind: 'VariableDefinition',
2834
+ kind: Kind.VARIABLE_DEFINITION,
2724
2835
  variable: this.variable.toVariableNode(),
2725
2836
  type: typeToAST(this.type),
2726
- defaultValue: valueToAST(this.defaultValue, this.type),
2727
- directives: this.appliedDirectivesToDirectiveNodes()
2837
+ defaultValue: (ast !== undefined) ? valueNodeToConstValueNode(ast) : undefined,
2838
+ directives: this.appliedDirectivesToDirectiveNodes(),
2728
2839
  }
2729
2840
  }
2730
2841