@apollo/federation-internals 2.4.0 → 2.4.2

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 (65) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/argumentCompositionStrategies.d.ts +34 -0
  3. package/dist/argumentCompositionStrategies.d.ts.map +1 -0
  4. package/dist/argumentCompositionStrategies.js +35 -0
  5. package/dist/argumentCompositionStrategies.js.map +1 -0
  6. package/dist/coreSpec.d.ts +12 -3
  7. package/dist/coreSpec.d.ts.map +1 -1
  8. package/dist/coreSpec.js +69 -18
  9. package/dist/coreSpec.js.map +1 -1
  10. package/dist/definitions.d.ts +1 -0
  11. package/dist/definitions.d.ts.map +1 -1
  12. package/dist/definitions.js +30 -27
  13. package/dist/definitions.js.map +1 -1
  14. package/dist/directiveAndTypeSpecification.d.ts +26 -7
  15. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  16. package/dist/directiveAndTypeSpecification.js +56 -4
  17. package/dist/directiveAndTypeSpecification.js.map +1 -1
  18. package/dist/federation.d.ts.map +1 -1
  19. package/dist/federation.js +24 -2
  20. package/dist/federation.js.map +1 -1
  21. package/dist/federationSpec.d.ts +2 -13
  22. package/dist/federationSpec.d.ts.map +1 -1
  23. package/dist/federationSpec.js +10 -60
  24. package/dist/federationSpec.js.map +1 -1
  25. package/dist/inaccessibleSpec.d.ts +0 -2
  26. package/dist/inaccessibleSpec.d.ts.map +1 -1
  27. package/dist/inaccessibleSpec.js +3 -6
  28. package/dist/inaccessibleSpec.js.map +1 -1
  29. package/dist/index.d.ts +3 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +5 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/knownCoreFeatures.d.ts +1 -0
  34. package/dist/knownCoreFeatures.d.ts.map +1 -1
  35. package/dist/knownCoreFeatures.js +5 -1
  36. package/dist/knownCoreFeatures.js.map +1 -1
  37. package/dist/operations.d.ts +92 -1
  38. package/dist/operations.d.ts.map +1 -1
  39. package/dist/operations.js +192 -48
  40. package/dist/operations.js.map +1 -1
  41. package/dist/print.d.ts +7 -1
  42. package/dist/print.d.ts.map +1 -1
  43. package/dist/print.js +33 -5
  44. package/dist/print.js.map +1 -1
  45. package/dist/tagSpec.d.ts +0 -2
  46. package/dist/tagSpec.d.ts.map +1 -1
  47. package/dist/tagSpec.js +4 -10
  48. package/dist/tagSpec.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/__tests__/directiveAndTypeSpecifications.test.ts +41 -0
  51. package/src/__tests__/operations.test.ts +598 -45
  52. package/src/__tests__/schemaUpgrader.test.ts +1 -1
  53. package/src/argumentCompositionStrategies.ts +39 -0
  54. package/src/coreSpec.ts +94 -34
  55. package/src/definitions.ts +35 -29
  56. package/src/directiveAndTypeSpecification.ts +101 -14
  57. package/src/federation.ts +33 -4
  58. package/src/federationSpec.ts +13 -73
  59. package/src/inaccessibleSpec.ts +4 -11
  60. package/src/index.ts +3 -0
  61. package/src/knownCoreFeatures.ts +9 -0
  62. package/src/operations.ts +318 -102
  63. package/src/print.ts +39 -4
  64. package/src/tagSpec.ts +4 -12
  65. package/tsconfig.tsbuildinfo +1 -1
package/src/federation.ts CHANGED
@@ -80,6 +80,8 @@ import {
80
80
  import { defaultPrintOptions, PrintOptions as PrintOptions, printSchema } from "./print";
81
81
  import { createObjectTypeSpecification, createScalarTypeSpecification, createUnionTypeSpecification } from "./directiveAndTypeSpecification";
82
82
  import { didYouMean, suggestionList } from "./suggestions";
83
+ import { coreFeatureDefinitionIfKnown } from "./knownCoreFeatures";
84
+ import { joinIdentity } from "./joinSpec";
83
85
 
84
86
  const linkSpec = LINK_VERSIONS.latest();
85
87
  const tagSpec = TAG_VERSIONS.latest();
@@ -110,7 +112,6 @@ const FEDERATION_SPECIFIC_VALIDATION_RULES = [
110
112
  const FEDERATION_VALIDATION_RULES = specifiedSDLRules.filter(rule => !FEDERATION_OMITTED_VALIDATION_RULES.includes(rule)).concat(FEDERATION_SPECIFIC_VALIDATION_RULES);
111
113
 
112
114
  const ALL_DEFAULT_FEDERATION_DIRECTIVE_NAMES: string[] = Object.values(FederationDirectiveName);
113
-
114
115
  function validateFieldSetSelections({
115
116
  directiveName,
116
117
  selectionSet,
@@ -1342,12 +1343,14 @@ function completeFed1SubgraphSchema(schema: Schema): GraphQLError[] {
1342
1343
  }
1343
1344
  }
1344
1345
 
1345
- return FEDERATION1_TYPES.map((spec) => spec.checkOrAdd(schema, '_' + spec.name))
1346
+ const errors = FEDERATION1_TYPES.map((spec) => spec.checkOrAdd(schema, '_' + spec.name))
1346
1347
  .concat(FEDERATION1_DIRECTIVES.map((spec) => spec.checkOrAdd(schema)))
1347
1348
  .flat();
1349
+
1350
+ return errors.length === 0 ? expandKnownFeatures(schema) : errors;
1348
1351
  }
1349
1352
 
1350
- function completeFed2SubgraphSchema(schema: Schema) {
1353
+ function completeFed2SubgraphSchema(schema: Schema): GraphQLError[] {
1351
1354
  const coreFeatures = schema.coreFeatures;
1352
1355
  assert(coreFeatures, 'This method should not have been called on a non-core schema');
1353
1356
 
@@ -1362,7 +1365,33 @@ function completeFed2SubgraphSchema(schema: Schema) {
1362
1365
  )];
1363
1366
  }
1364
1367
 
1365
- return spec.addElementsToSchema(schema);
1368
+ const errors = spec.addElementsToSchema(schema);
1369
+ return errors.length === 0 ? expandKnownFeatures(schema) : errors;
1370
+ }
1371
+
1372
+ function expandKnownFeatures(schema: Schema): GraphQLError[] {
1373
+ const coreFeatures = schema.coreFeatures;
1374
+ if (!coreFeatures) {
1375
+ return [];
1376
+ }
1377
+
1378
+ let errors: GraphQLError[] = [];
1379
+ for (const feature of coreFeatures.allFeatures()) {
1380
+ // We should already have dealt with the core/link spec and federation at this point. Also, we shouldn't have the `join` spec in subgraphs,
1381
+ // but some tests play with the idea and currently the join spec is implemented in a way that is not idempotent (it doesn't use
1382
+ // `DirectiveSpecification.checkAndAdd`; we should clean it up at some point, but not exactly urgent).
1383
+ if (feature === coreFeatures.coreItself || feature.url.identity === federationIdentity || feature.url.identity === joinIdentity) {
1384
+ continue;
1385
+ }
1386
+
1387
+ const spec = coreFeatureDefinitionIfKnown(feature.url);
1388
+ if (!spec) {
1389
+ continue;
1390
+ }
1391
+
1392
+ errors = errors.concat(spec.addElementsToSchema(schema));
1393
+ }
1394
+ return errors;
1366
1395
  }
1367
1396
 
1368
1397
  export function parseFieldSetArgument({
@@ -8,11 +8,9 @@ import {
8
8
  ArgumentSpecification,
9
9
  createDirectiveSpecification,
10
10
  createScalarTypeSpecification,
11
- DirectiveSpecification,
12
- TypeSpecification,
13
11
  } from "./directiveAndTypeSpecification";
14
- import { DirectiveLocation, GraphQLError } from "graphql";
15
- import { assert, MapWithCachedArrays } from "./utils";
12
+ import { DirectiveLocation } from "graphql";
13
+ import { assert } from "./utils";
16
14
  import { TAG_VERSIONS } from "./tagSpec";
17
15
  import { federationMetadata } from "./federation";
18
16
  import { registerKnownFeature } from "./knownCoreFeatures";
@@ -40,17 +38,16 @@ export enum FederationDirectiveName {
40
38
 
41
39
  const fieldSetTypeSpec = createScalarTypeSpecification({ name: FederationTypeName.FIELD_SET });
42
40
 
41
+ const fieldsArgument: ArgumentSpecification = { name: 'fields', type: (schema) => fieldSetType(schema) };
42
+
43
43
  const keyDirectiveSpec = createDirectiveSpecification({
44
44
  name: FederationDirectiveName.KEY,
45
45
  locations: [DirectiveLocation.OBJECT, DirectiveLocation.INTERFACE],
46
46
  repeatable: true,
47
- argumentFct: (schema) => ({
48
- args: [
49
- fieldsArgument(schema),
50
- { name: 'resolvable', type: schema.booleanType(), defaultValue: true },
51
- ],
52
- errors: [],
53
- }),
47
+ args: [
48
+ fieldsArgument,
49
+ { name: 'resolvable', type: (schema) => schema.booleanType(), defaultValue: true },
50
+ ]
54
51
  });
55
52
 
56
53
  const extendsDirectiveSpec = createDirectiveSpecification({
@@ -61,28 +58,19 @@ const extendsDirectiveSpec = createDirectiveSpecification({
61
58
  const externalDirectiveSpec = createDirectiveSpecification({
62
59
  name: FederationDirectiveName.EXTERNAL,
63
60
  locations: [DirectiveLocation.OBJECT, DirectiveLocation.FIELD_DEFINITION],
64
- argumentFct: (schema) => ({
65
- args: [{ name: 'reason', type: schema.stringType() }],
66
- errors: [],
67
- }),
61
+ args: [{ name: 'reason', type: (schema) => schema.stringType() }],
68
62
  });
69
63
 
70
64
  const requiresDirectiveSpec = createDirectiveSpecification({
71
65
  name: FederationDirectiveName.REQUIRES,
72
66
  locations: [DirectiveLocation.FIELD_DEFINITION],
73
- argumentFct: (schema) => ({
74
- args: [fieldsArgument(schema)],
75
- errors: [],
76
- }),
67
+ args: [fieldsArgument],
77
68
  });
78
69
 
79
70
  const providesDirectiveSpec = createDirectiveSpecification({
80
71
  name: FederationDirectiveName.PROVIDES,
81
72
  locations: [DirectiveLocation.FIELD_DEFINITION],
82
- argumentFct: (schema) => ({
83
- args: [fieldsArgument(schema)],
84
- errors: [],
85
- }),
73
+ args: [fieldsArgument],
86
74
  });
87
75
 
88
76
  const legacyFederationTypes = [
@@ -103,9 +91,6 @@ const legacyFederationDirectives = [
103
91
  export const FEDERATION1_TYPES = legacyFederationTypes;
104
92
  export const FEDERATION1_DIRECTIVES = legacyFederationDirectives;
105
93
 
106
- function fieldsArgument(schema: Schema): ArgumentSpecification {
107
- return { name: 'fields', type: fieldSetType(schema) };
108
- }
109
94
 
110
95
  function fieldSetType(schema: Schema): InputType {
111
96
  const metadata = federationMetadata(schema);
@@ -114,9 +99,6 @@ function fieldSetType(schema: Schema): InputType {
114
99
  }
115
100
 
116
101
  export class FederationSpecDefinition extends FeatureDefinition {
117
- private readonly _directiveSpecs = new MapWithCachedArrays<string, DirectiveSpecification>();
118
- private readonly _typeSpecs = new MapWithCachedArrays<string, TypeSpecification>();
119
-
120
102
  constructor(version: FeatureVersion) {
121
103
  super(new FeatureUrl(federationIdentity, 'federation', version));
122
104
 
@@ -139,10 +121,7 @@ export class FederationSpecDefinition extends FeatureDefinition {
139
121
  this.registerDirective(createDirectiveSpecification({
140
122
  name: FederationDirectiveName.OVERRIDE,
141
123
  locations: [DirectiveLocation.FIELD_DEFINITION],
142
- argumentFct: (schema) => ({
143
- args: [{ name: 'from', type: new NonNullType(schema.stringType()) }],
144
- errors: [],
145
- }),
124
+ args: [{ name: 'from', type: (schema) => new NonNullType(schema.stringType()) }],
146
125
  }));
147
126
 
148
127
  if (version >= (new FeatureVersion(2, 1))) {
@@ -150,10 +129,7 @@ export class FederationSpecDefinition extends FeatureDefinition {
150
129
  name: FederationDirectiveName.COMPOSE_DIRECTIVE,
151
130
  locations: [DirectiveLocation.SCHEMA],
152
131
  repeatable: true,
153
- argumentFct: (schema) => ({
154
- args: [{ name: 'name', type: schema.stringType() }],
155
- errors: [],
156
- }),
132
+ args: [{ name: 'name', type: (schema) => schema.stringType() }],
157
133
  }));
158
134
  }
159
135
 
@@ -167,42 +143,6 @@ export class FederationSpecDefinition extends FeatureDefinition {
167
143
  );
168
144
  }
169
145
  }
170
-
171
- private registerDirective(spec: DirectiveSpecification) {
172
- this._directiveSpecs.set(spec.name, spec);
173
- }
174
-
175
- private registerType(spec: TypeSpecification) {
176
- this._typeSpecs.set(spec.name, spec);
177
- }
178
-
179
- directiveSpecs(): readonly DirectiveSpecification[] {
180
- return this._directiveSpecs.values();
181
- }
182
-
183
- typeSpecs(): readonly TypeSpecification[] {
184
- return this._typeSpecs.values();
185
- }
186
-
187
- addElementsToSchema(schema: Schema): GraphQLError[] {
188
- const feature = this.featureInSchema(schema);
189
- assert(feature, 'The federation specification should have been added to the schema before this is called');
190
-
191
- let errors: GraphQLError[] = [];
192
- for (const type of this.typeSpecs()) {
193
- errors = errors.concat(this.addTypeSpec(schema, type));
194
- }
195
-
196
- for (const directive of this.directiveSpecs()) {
197
- errors = errors.concat(this.addDirectiveSpec(schema, directive));
198
- }
199
- return errors;
200
- }
201
-
202
- allElementNames(): string[] {
203
- return this.directiveSpecs().map((spec) => `@${spec.name}`)
204
- .concat(this.typeSpecs().map((spec) => spec.name));
205
- }
206
146
  }
207
147
 
208
148
  export const FEDERATION_VERSIONS = new FeatureDefinitions<FederationSpecDefinition>(federationIdentity)
@@ -62,17 +62,16 @@ export class InaccessibleSpecDefinition extends FeatureDefinition {
62
62
  this.inaccessibleDirectiveSpec = createDirectiveSpecification({
63
63
  name: 'inaccessible',
64
64
  locations: this.inaccessibleLocations,
65
+ composes: true,
66
+ supergraphSpecification: () => INACCESSIBLE_VERSIONS.latest(),
65
67
  });
68
+ this.registerDirective(this.inaccessibleDirectiveSpec);
66
69
  }
67
70
 
68
71
  isV01() {
69
72
  return this.version.equals(new FeatureVersion(0, 1));
70
73
  }
71
74
 
72
- addElementsToSchema(schema: Schema): GraphQLError[] {
73
- return this.addDirectiveSpec(schema, this.inaccessibleDirectiveSpec);
74
- }
75
-
76
75
  inaccessibleDirective(schema: Schema): DirectiveDefinition<Record<string, never>> | undefined {
77
76
  return this.directive(schema, 'inaccessible');
78
77
  }
@@ -89,10 +88,6 @@ export class InaccessibleSpecDefinition extends FeatureDefinition {
89
88
  return undefined;
90
89
  }
91
90
 
92
- allElementNames(): string[] {
93
- return ['@inaccessible'];
94
- }
95
-
96
91
  get defaultCorePurpose(): CorePurpose | undefined {
97
92
  return 'SECURITY';
98
93
  }
@@ -891,9 +886,7 @@ function getInputType(element: SchemaElementWithDefaultValue): InputType {
891
886
  // similar to the "Values of Correct Type" validation in the GraphQL spec.
892
887
  // However, there are two noteable differences:
893
888
  // 1. Variable references are not allowed.
894
- // 2. Scalar values are not required to be coercible (due to machine-specific
895
- // differences in input coercion rules).
896
- //
889
+ // 2. Scalar values are not required to be coercible (due to machine-specific differences in input coercion rules).
897
890
  // As it turns out, building a Schema object validates this (and a bit more)
898
891
  // already, so in the interests of not duplicating validations/keeping the logic
899
892
  // centralized, this code assumes the input values it receives satisfy the above
package/src/index.ts CHANGED
@@ -18,3 +18,6 @@ export * from './error';
18
18
  export * from './schemaUpgrader';
19
19
  export * from './suggestions';
20
20
  export * from './graphQLJSSchemaToAST';
21
+ export * from './directiveAndTypeSpecification';
22
+ export { coreFeatureDefinitionIfKnown } from './knownCoreFeatures';
23
+ export * from './argumentCompositionStrategies';
@@ -11,3 +11,12 @@ export function registerKnownFeature(definitions: FeatureDefinitions) {
11
11
  export function coreFeatureDefinitionIfKnown(url: FeatureUrl): FeatureDefinition | undefined {
12
12
  return registeredFeatures.get(url.identity)?.find(url.version);
13
13
  }
14
+
15
+ /**
16
+ * Removes a feature from the set of known features.
17
+ *
18
+ * This exists purely for testing purposes. There is no reason to unregistered features otherwise.
19
+ */
20
+ export function unregisterKnownFeatures(definitions: FeatureDefinitions) {
21
+ registeredFeatures.delete(definitions.identity);
22
+ }