@apollo/federation-internals 2.4.1 → 2.4.3

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 (64) hide show
  1. package/CHANGELOG.md +21 -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 +68 -17
  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 +18 -6
  38. package/dist/operations.d.ts.map +1 -1
  39. package/dist/operations.js +102 -37
  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 +175 -10
  52. package/src/argumentCompositionStrategies.ts +39 -0
  53. package/src/coreSpec.ts +94 -34
  54. package/src/definitions.ts +35 -29
  55. package/src/directiveAndTypeSpecification.ts +101 -14
  56. package/src/federation.ts +33 -4
  57. package/src/federationSpec.ts +13 -73
  58. package/src/inaccessibleSpec.ts +4 -11
  59. package/src/index.ts +3 -0
  60. package/src/knownCoreFeatures.ts +9 -0
  61. package/src/operations.ts +198 -40
  62. package/src/print.ts +39 -4
  63. package/src/tagSpec.ts +4 -12
  64. package/tsconfig.tsbuildinfo +1 -1
@@ -1078,16 +1078,7 @@ export class CoreFeatures {
1078
1078
  isImported: false,
1079
1079
  } : undefined;
1080
1080
  } else {
1081
- const directFeature = this.byAlias.get(element.name);
1082
- if (directFeature && isDirective) {
1083
- return {
1084
- feature: directFeature,
1085
- nameInFeature: directFeature.imports.find(imp => imp.as === `@${element.name}`)?.name.slice(1) ?? element.name,
1086
- isImported: true,
1087
- };
1088
- }
1089
-
1090
- // Let's see if it's an import. If not, it's not associated to a declared feature.
1081
+ // Let's first see if it's an import, as this would take precedence over directive implicitely named like their feature.
1091
1082
  const importName = isDirective ? '@' + element.name : element.name;
1092
1083
  const allFeatures = [this.coreItself, ...this.byIdentity.values()];
1093
1084
  for (const feature of allFeatures) {
@@ -1101,6 +1092,17 @@ export class CoreFeatures {
1101
1092
  }
1102
1093
  }
1103
1094
  }
1095
+
1096
+ // Otherwise, this may be the special directive having the same name as its feature.
1097
+ const directFeature = this.byAlias.get(element.name);
1098
+ if (directFeature && isDirective) {
1099
+ return {
1100
+ feature: directFeature,
1101
+ nameInFeature: directFeature.imports.find(imp => imp.as === `@${element.name}`)?.name.slice(1) ?? element.name,
1102
+ isImported: true,
1103
+ };
1104
+ }
1105
+
1104
1106
  return undefined;
1105
1107
  }
1106
1108
  }
@@ -1113,22 +1115,22 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
1113
1115
  createDirectiveSpecification({
1114
1116
  name: 'include',
1115
1117
  locations: [DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
1116
- argumentFct: (schema) => ({ args: [{ name: 'if', type: new NonNullType(schema.booleanType()) }], errors: [] })
1118
+ args: [{ name: 'if', type: (schema) => new NonNullType(schema.booleanType()) }],
1117
1119
  }),
1118
1120
  createDirectiveSpecification({
1119
1121
  name: 'skip',
1120
1122
  locations: [DirectiveLocation.FIELD, DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
1121
- argumentFct: (schema) => ({ args: [{ name: 'if', type: new NonNullType(schema.booleanType()) }], errors: [] })
1123
+ args: [{ name: 'if', type: (schema) => new NonNullType(schema.booleanType()) }],
1122
1124
  }),
1123
1125
  createDirectiveSpecification({
1124
1126
  name: 'deprecated',
1125
1127
  locations: [DirectiveLocation.FIELD_DEFINITION, DirectiveLocation.ENUM_VALUE, DirectiveLocation.ARGUMENT_DEFINITION, DirectiveLocation.INPUT_FIELD_DEFINITION],
1126
- argumentFct: (schema) => ({ args: [{ name: 'reason', type: schema.stringType(), defaultValue: 'No longer supported' }], errors: [] })
1128
+ args: [{ name: 'reason', type: (schema) => schema.stringType(), defaultValue: 'No longer supported' }],
1127
1129
  }),
1128
1130
  createDirectiveSpecification({
1129
1131
  name: 'specifiedBy',
1130
1132
  locations: [DirectiveLocation.SCALAR],
1131
- argumentFct: (schema) => ({ args: [{ name: 'url', type: new NonNullType(schema.stringType()) }], errors: [] })
1133
+ args: [{ name: 'url', type: (schema) => new NonNullType(schema.stringType()) }],
1132
1134
  }),
1133
1135
  // Note that @defer and @stream are unconditionally added to `Schema` even if they are technically "optional" built-in. _But_,
1134
1136
  // the `Schema#toGraphQLJSSchema` method has an option to decide if @defer/@stream should be included or not in the resulting
@@ -1136,13 +1138,10 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
1136
1138
  createDirectiveSpecification({
1137
1139
  name: 'defer',
1138
1140
  locations: [DirectiveLocation.FRAGMENT_SPREAD, DirectiveLocation.INLINE_FRAGMENT],
1139
- argumentFct: (schema) => ({
1140
- args: [
1141
- { name: 'label', type: schema.stringType() },
1142
- { name: 'if', type: new NonNullType(schema.booleanType()), defaultValue: true },
1143
- ],
1144
- errors: [],
1145
- })
1141
+ args: [
1142
+ { name: 'label', type: (schema) => schema.stringType() },
1143
+ { name: 'if', type: (schema) => new NonNullType(schema.booleanType()), defaultValue: true },
1144
+ ],
1146
1145
  }),
1147
1146
  // Adding @stream too so that it's know and we don't error out if it is queries. It feels like it would be weird to do so for @stream but not
1148
1147
  // @defer when both are defined in the same spec. That said, that does *not* mean we currently _implement_ @stream, we don't, and so putting
@@ -1150,14 +1149,11 @@ const graphQLBuiltInDirectivesSpecifications: readonly DirectiveSpecification[]
1150
1149
  createDirectiveSpecification({
1151
1150
  name: 'stream',
1152
1151
  locations: [DirectiveLocation.FIELD],
1153
- argumentFct: (schema) => ({
1154
- args: [
1155
- { name: 'label', type: schema.stringType() },
1156
- { name: 'initialCount', type: schema.intType(), defaultValue: 0 },
1157
- { name: 'if', type: new NonNullType(schema.booleanType()), defaultValue: true },
1158
- ],
1159
- errors: [],
1160
- })
1152
+ args: [
1153
+ { name: 'label', type: (schema) => schema.stringType() },
1154
+ { name: 'initialCount', type: (schema) => schema.intType(), defaultValue: 0 },
1155
+ { name: 'if', type: (schema) => new NonNullType(schema.booleanType()), defaultValue: true },
1156
+ ],
1161
1157
  }),
1162
1158
  ];
1163
1159
 
@@ -1457,6 +1453,16 @@ export class Schema {
1457
1453
  return this._builtInTypes.get('ID')! as ScalarType;
1458
1454
  }
1459
1455
 
1456
+ builtInScalarTypes(): ScalarType[] {
1457
+ return [
1458
+ this.intType(),
1459
+ this.floatType(),
1460
+ this.stringType(),
1461
+ this.booleanType(),
1462
+ this.idType(),
1463
+ ];
1464
+ }
1465
+
1460
1466
  addType<T extends NamedType>(type: T): T {
1461
1467
  const existing = this.type(type.name);
1462
1468
  if (existing) {
@@ -21,10 +21,23 @@ import { ERRORS } from "./error";
21
21
  import { valueEquals, valueToString } from "./values";
22
22
  import { sameType } from "./types";
23
23
  import { arrayEquals, assert } from "./utils";
24
+ import { ArgumentCompositionStrategy } from "./argumentCompositionStrategies";
25
+ import { FeatureDefinition } from "./coreSpec";
24
26
 
25
27
  export type DirectiveSpecification = {
26
28
  name: string,
27
29
  checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => GraphQLError[],
30
+ composition?: DirectiveCompositionSpecification,
31
+ }
32
+
33
+ export type DirectiveCompositionSpecification = {
34
+ supergraphSpecification: () => FeatureDefinition,
35
+ argumentsMerger?: (schema: Schema) => ArgumentMerger | GraphQLError,
36
+ }
37
+
38
+ export type ArgumentMerger = {
39
+ merge: (argName: string, values: any[]) => any,
40
+ toString: () => string,
28
41
  }
29
42
 
30
43
  export type TypeSpecification = {
@@ -34,43 +47,117 @@ export type TypeSpecification = {
34
47
 
35
48
  export type ArgumentSpecification = {
36
49
  name: string,
37
- type: InputType,
50
+ type: (schema: Schema, nameInSchema?: string) => InputType | GraphQLError[],
38
51
  defaultValue?: any,
39
52
  }
40
53
 
54
+ export type DirectiveArgumentSpecification = ArgumentSpecification & {
55
+ compositionStrategy?: ArgumentCompositionStrategy,
56
+ }
57
+
41
58
  export type FieldSpecification = {
42
59
  name: string,
43
60
  type: OutputType,
44
- args?: ArgumentSpecification[],
45
- };
61
+ args?: ResolvedArgumentSpecification[],
62
+ }
63
+
64
+ type ResolvedArgumentSpecification = {
65
+ name: string,
66
+ type: InputType,
67
+ defaultValue?: any,
68
+ }
46
69
 
47
70
  export function createDirectiveSpecification({
48
71
  name,
49
72
  locations,
50
73
  repeatable = false,
51
- argumentFct = undefined,
74
+ args = [],
75
+ composes = false,
76
+ supergraphSpecification = undefined,
52
77
  }: {
53
78
  name: string,
54
79
  locations: DirectiveLocation[],
55
80
  repeatable?: boolean,
56
- argumentFct?: (schema: Schema, nameInSchema?: string) => { args: ArgumentSpecification[], errors: GraphQLError[] },
81
+ args?: DirectiveArgumentSpecification[],
82
+ composes?: boolean,
83
+ supergraphSpecification?: () => FeatureDefinition,
57
84
  }): DirectiveSpecification {
85
+ let composition: DirectiveCompositionSpecification | undefined = undefined;
86
+ if (composes) {
87
+ assert(supergraphSpecification, `Should provide a @link specification to use in supergraph for directive @${name} if it composes`);
88
+ const argStrategies = new Map(args.filter((arg) => arg.compositionStrategy).map((arg) => [arg.name, arg.compositionStrategy!]));
89
+ let argumentsMerger: ((schema: Schema) => ArgumentMerger | GraphQLError) | undefined = undefined;
90
+ if (argStrategies.size > 0) {
91
+ assert(!repeatable, () => `Invalid directive specification for @${name}: @${name} is repeatable and should not define composition strategy for its arguments`);
92
+ assert(argStrategies.size === args.length, () => `Invalid directive specification for @${name}: not all arguments define a composition strategy`);
93
+ argumentsMerger = (schema) => {
94
+ // Validate that the arguments have compatible types with the declared strategies (a bit unfortunate that we can't do this until
95
+ // we have a schema but well, not a huge deal either).
96
+ for (const { name: argName, type } of args) {
97
+ const strategy = argStrategies.get(argName);
98
+ // Note that we've built `argStrategies` from the declared args and checked that all argument had a strategy, so it would be
99
+ // a bug in the code if we didn't get a strategy (not an issue in the directive declaration).
100
+ assert(strategy, () => `Should have a strategy for ${argName}`);
101
+ const argType = type(schema);
102
+ // By the time we call this, the directive should have been added to the schema and so getting the type should not raise errors.
103
+ assert(!Array.isArray(argType), () => `Should have gotten error getting type for @${name}(${argName}:), but got ${argType}`)
104
+ const strategyTypes = strategy.supportedTypes(schema);
105
+ if (!strategyTypes.some((t) => sameType(t, argType))) {
106
+ return new GraphQLError(
107
+ `Invalid composition strategy ${strategy.name} for argument @${name}(${argName}:) of type ${argType}; `
108
+ + `${strategy.name} only supports type(s) ${strategyTypes.join(', ')}`
109
+ );
110
+ }
111
+ };
112
+ return {
113
+ merge: (argName, values) => {
114
+ const strategy = argStrategies.get(argName);
115
+ assert(strategy, () => `Should have a strategy for ${argName}`);
116
+ return strategy.mergeValues(values);
117
+ },
118
+ toString: () => {
119
+ if (argStrategies.size === 0) {
120
+ return "<none>";
121
+ }
122
+ return '{ ' + [...argStrategies.entries()].map(([arg, strategy]) => `"${arg}": ${strategy.name}`).join(', ') + ' }';
123
+ }
124
+ };
125
+ }
126
+ }
127
+ composition = {
128
+ supergraphSpecification,
129
+ argumentsMerger,
130
+ };
131
+ }
132
+
58
133
  return {
59
134
  name,
135
+ composition,
60
136
  checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
61
137
  const actualName = nameInSchema ?? name;
62
- const {args, errors} = argumentFct ? argumentFct(schema, actualName) : { args: [], errors: []};
138
+ const { resolvedArgs, errors } = args.reduce<{ resolvedArgs: (ResolvedArgumentSpecification & { compositionStrategy?: ArgumentCompositionStrategy })[], errors: GraphQLError[] }>(
139
+ ({ resolvedArgs, errors }, arg) => {
140
+ const typeOrErrors = arg.type(schema, actualName);
141
+ if (Array.isArray(typeOrErrors)) {
142
+ errors.push(...typeOrErrors);
143
+ } else {
144
+ resolvedArgs.push({ ...arg, type: typeOrErrors });
145
+ }
146
+ return { resolvedArgs, errors };
147
+ },
148
+ { resolvedArgs: [], errors: [] }
149
+ );
63
150
  if (errors.length > 0) {
64
151
  return errors;
65
152
  }
66
153
  const existing = schema.directive(actualName);
67
154
  if (existing) {
68
- return ensureSameDirectiveStructure({name: actualName, locations, repeatable, args}, existing);
155
+ return ensureSameDirectiveStructure({ name: actualName, locations, repeatable, args: resolvedArgs }, existing);
69
156
  } else {
70
157
  const directive = schema.addDirectiveDefinition(new DirectiveDefinition(actualName, asBuiltIn));
71
158
  directive.repeatable = repeatable;
72
159
  directive.addLocations(...locations);
73
- for (const { name, type, defaultValue } of args) {
160
+ for (const { name, type, defaultValue } of resolvedArgs) {
74
161
  directive.addArgument(name, type, defaultValue);
75
162
  }
76
163
  return [];
@@ -95,7 +182,7 @@ export function createScalarTypeSpecification({ name }: { name: string }): TypeS
95
182
  }
96
183
  }
97
184
 
98
- export function createObjectTypeSpecification({
185
+ export function createObjectTypeSpecification({
99
186
  name,
100
187
  fieldsFct,
101
188
  }: {
@@ -156,7 +243,7 @@ export function createObjectTypeSpecification({
156
243
  }
157
244
  }
158
245
 
159
- export function createUnionTypeSpecification({
246
+ export function createUnionTypeSpecification({
160
247
  name,
161
248
  membersFct,
162
249
  }: {
@@ -210,7 +297,7 @@ export function createEnumTypeSpecification({
210
297
  values,
211
298
  }: {
212
299
  name: string,
213
- values: { name: string, description?: string}[],
300
+ values: { name: string, description?: string }[],
214
301
  }): TypeSpecification {
215
302
  return {
216
303
  name,
@@ -234,7 +321,7 @@ export function createEnumTypeSpecification({
234
321
  return errors;
235
322
  } else {
236
323
  const type = schema.addType(new EnumType(actualName, asBuiltIn));
237
- for (const {name, description} of values) {
324
+ for (const { name, description } of values) {
238
325
  type.addValue(name).description = description;
239
326
  }
240
327
  return [];
@@ -259,7 +346,7 @@ function ensureSameDirectiveStructure(
259
346
  name: string,
260
347
  locations: DirectiveLocation[],
261
348
  repeatable: boolean,
262
- args: ArgumentSpecification[]
349
+ args: ResolvedArgumentSpecification[]
263
350
  },
264
351
  actual: DirectiveDefinition<any>,
265
352
  ): GraphQLError[] {
@@ -285,7 +372,7 @@ function ensureSameDirectiveStructure(
285
372
  function ensureSameArguments(
286
373
  expected: {
287
374
  name: string,
288
- args?: ArgumentSpecification[]
375
+ args?: ResolvedArgumentSpecification[]
289
376
  },
290
377
  actual: { argument(name: string): ArgumentDefinition<any> | undefined, arguments(): readonly ArgumentDefinition<any>[] },
291
378
  what: string,
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
+ }