@apollo/federation-internals 2.8.4 → 2.9.0-beta.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 (44) hide show
  1. package/dist/argumentCompositionStrategies.d.ts +17 -0
  2. package/dist/argumentCompositionStrategies.d.ts.map +1 -1
  3. package/dist/argumentCompositionStrategies.js +38 -0
  4. package/dist/argumentCompositionStrategies.js.map +1 -1
  5. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  6. package/dist/extractSubgraphsFromSupergraph.js +59 -8
  7. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  8. package/dist/federation.d.ts +5 -2
  9. package/dist/federation.d.ts.map +1 -1
  10. package/dist/federation.js +16 -2
  11. package/dist/federation.js.map +1 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/index.js.map +1 -1
  16. package/dist/operations.d.ts +3 -1
  17. package/dist/operations.d.ts.map +1 -1
  18. package/dist/operations.js +26 -5
  19. package/dist/operations.js.map +1 -1
  20. package/dist/specs/coreSpec.d.ts +1 -0
  21. package/dist/specs/coreSpec.d.ts.map +1 -1
  22. package/dist/specs/coreSpec.js +18 -0
  23. package/dist/specs/coreSpec.js.map +1 -1
  24. package/dist/specs/costSpec.d.ts +17 -0
  25. package/dist/specs/costSpec.d.ts.map +1 -0
  26. package/dist/specs/costSpec.js +49 -0
  27. package/dist/specs/costSpec.js.map +1 -0
  28. package/dist/specs/federationSpec.d.ts +3 -1
  29. package/dist/specs/federationSpec.d.ts.map +1 -1
  30. package/dist/specs/federationSpec.js +8 -1
  31. package/dist/specs/federationSpec.js.map +1 -1
  32. package/dist/supergraphs.d.ts.map +1 -1
  33. package/dist/supergraphs.js +1 -0
  34. package/dist/supergraphs.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/argumentCompositionStrategies.ts +37 -0
  37. package/src/extractSubgraphsFromSupergraph.ts +94 -7
  38. package/src/federation.ts +21 -2
  39. package/src/index.ts +1 -0
  40. package/src/operations.ts +41 -4
  41. package/src/specs/coreSpec.ts +21 -0
  42. package/src/specs/costSpec.ts +60 -0
  43. package/src/specs/federationSpec.ts +9 -1
  44. package/src/supergraphs.ts +1 -0
package/src/federation.ts CHANGED
@@ -100,6 +100,7 @@ import {
100
100
  SourceFieldDirectiveArgs,
101
101
  SourceTypeDirectiveArgs,
102
102
  } from "./specs/sourceSpec";
103
+ import { CostDirectiveArguments, ListSizeDirectiveArguments } from "./specs/costSpec";
103
104
 
104
105
  const linkSpec = LINK_VERSIONS.latest();
105
106
  const tagSpec = TAG_VERSIONS.latest();
@@ -1275,6 +1276,14 @@ export class FederationMetadata {
1275
1276
  return this.getPost20FederationDirective(FederationDirectiveName.CONTEXT);
1276
1277
  }
1277
1278
 
1279
+ costDirective(): Post20FederationDirectiveDefinition<CostDirectiveArguments> {
1280
+ return this.getPost20FederationDirective(FederationDirectiveName.COST);
1281
+ }
1282
+
1283
+ listSizeDirective(): Post20FederationDirectiveDefinition<ListSizeDirectiveArguments> {
1284
+ return this.getPost20FederationDirective(FederationDirectiveName.LIST_SIZE);
1285
+ }
1286
+
1278
1287
  allFederationDirectives(): DirectiveDefinition[] {
1279
1288
  const baseDirectives: DirectiveDefinition[] = [
1280
1289
  this.keyDirective(),
@@ -1338,6 +1347,16 @@ export class FederationMetadata {
1338
1347
  baseDirectives.push(fromContextDirective);
1339
1348
  }
1340
1349
 
1350
+ const costDirective = this.costDirective();
1351
+ if (isFederationDirectiveDefinedInSchema(costDirective)) {
1352
+ baseDirectives.push(costDirective);
1353
+ }
1354
+
1355
+ const listSizeDirective = this.listSizeDirective();
1356
+ if (isFederationDirectiveDefinedInSchema(listSizeDirective)) {
1357
+ baseDirectives.push(listSizeDirective);
1358
+ }
1359
+
1341
1360
  return baseDirectives;
1342
1361
  }
1343
1362
 
@@ -1831,9 +1850,9 @@ export function setSchemaAsFed2Subgraph(schema: Schema, useLatest: boolean = fal
1831
1850
 
1832
1851
  // This is the full @link declaration as added by `asFed2SubgraphDocument`. It's here primarily for uses by tests that print and match
1833
1852
  // subgraph schema to avoid having to update 20+ tests every time we use a new directive or the order of import changes ...
1834
- export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.8", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy", "@sourceAPI", "@sourceType", "@sourceField", "@context", "@fromContext"])';
1853
+ export const FEDERATION2_LINK_WITH_FULL_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject", "@authenticated", "@requiresScopes", "@policy", "@sourceAPI", "@sourceType", "@sourceField", "@context", "@fromContext", "@cost", "@listSize"])';
1835
1854
  // This is the full @link declaration that is added when upgrading fed v1 subgraphs to v2 version. It should only be used by tests.
1836
- export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.8", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
1855
+ export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS = '@link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
1837
1856
 
1838
1857
  // This is the federation @link for tests that go through the SchemaUpgrader.
1839
1858
  export const FEDERATION2_LINK_WITH_AUTO_EXPANDED_IMPORTS_UPGRADED = '@link(url: "https://specs.apollo.dev/federation/v2.4", import: ["@key", "@requires", "@provides", "@external", "@tag", "@extends", "@shareable", "@inaccessible", "@override", "@composeDirective", "@interfaceObject"])';
package/src/index.ts CHANGED
@@ -25,3 +25,4 @@ export * from './specs/authenticatedSpec';
25
25
  export * from './specs/requiresScopesSpec';
26
26
  export * from './specs/policySpec';
27
27
  export * from './specs/sourceSpec';
28
+ export * from './specs/costSpec';
package/src/operations.ts CHANGED
@@ -56,6 +56,8 @@ import { assert, mapKeys, mapValues, MapWithCachedArrays, MultiMap, SetMultiMap
56
56
  import { argumentsEquals, argumentsFromAST, isValidValue, valueToAST, valueToString } from "./values";
57
57
  import { v1 as uuidv1 } from 'uuid';
58
58
 
59
+ export const DEFAULT_MIN_USAGES_TO_OPTIMIZE = 2;
60
+
59
61
  function validate(condition: any, message: () => string, sourceAST?: ASTNode): asserts condition {
60
62
  if (!condition) {
61
63
  throw ERRORS.INVALID_GRAPHQL.err(message(), { nodes: sourceAST });
@@ -934,25 +936,55 @@ export class Operation extends DirectiveTargetElement<Operation> {
934
936
  this.appliedDirectives,
935
937
  );
936
938
  }
939
+
940
+ private collectUndefinedVariablesFromFragments(fragments: NamedFragments): Variable[] {
941
+ const collector = new VariableCollector();
942
+ for (const namedFragment of fragments.definitions()) {
943
+ namedFragment.selectionSet.usedVariables().forEach(v => {
944
+ if (!this.variableDefinitions.definition(v)) {
945
+ collector.add(v);
946
+ }
947
+ });
948
+ }
949
+ return collector.variables();
950
+ }
937
951
 
938
952
  // Returns a copy of this operation with the provided updated selection set and fragments.
939
- private withUpdatedSelectionSetAndFragments(newSelectionSet: SelectionSet, newFragments: NamedFragments | undefined): Operation {
953
+ private withUpdatedSelectionSetAndFragments(
954
+ newSelectionSet: SelectionSet,
955
+ newFragments: NamedFragments | undefined,
956
+ allAvailableVariables?: VariableDefinitions,
957
+ ): Operation {
940
958
  if (this.selectionSet === newSelectionSet && newFragments === this.fragments) {
941
959
  return this;
942
960
  }
961
+
962
+ let newVariableDefinitions = this.variableDefinitions;
963
+ if (allAvailableVariables && newFragments) {
964
+ const undefinedVariables = this.collectUndefinedVariablesFromFragments(newFragments);
965
+ if (undefinedVariables.length > 0) {
966
+ newVariableDefinitions = new VariableDefinitions();
967
+ newVariableDefinitions.addAll(this.variableDefinitions);
968
+ newVariableDefinitions.addAll(allAvailableVariables.filter(undefinedVariables));
969
+ }
970
+ }
943
971
 
944
972
  return new Operation(
945
973
  this.schema(),
946
974
  this.rootKind,
947
975
  newSelectionSet,
948
- this.variableDefinitions,
976
+ newVariableDefinitions,
949
977
  newFragments,
950
978
  this.name,
951
979
  this.appliedDirectives,
952
980
  );
953
981
  }
954
982
 
955
- optimize(fragments?: NamedFragments, minUsagesToOptimize: number = 2): Operation {
983
+ optimize(
984
+ fragments?: NamedFragments,
985
+ minUsagesToOptimize: number = DEFAULT_MIN_USAGES_TO_OPTIMIZE,
986
+ allAvailableVariables?: VariableDefinitions,
987
+ ): Operation {
956
988
  assert(minUsagesToOptimize >= 1, `Expected 'minUsagesToOptimize' to be at least 1, but got ${minUsagesToOptimize}`)
957
989
  if (!fragments || fragments.isEmpty()) {
958
990
  return this;
@@ -1001,11 +1033,16 @@ export class Operation extends DirectiveTargetElement<Operation> {
1001
1033
  }
1002
1034
  }
1003
1035
 
1004
- return this.withUpdatedSelectionSetAndFragments(optimizedSelection, finalFragments ?? undefined);
1036
+ return this.withUpdatedSelectionSetAndFragments(
1037
+ optimizedSelection,
1038
+ finalFragments ?? undefined,
1039
+ allAvailableVariables,
1040
+ );
1005
1041
  }
1006
1042
 
1007
1043
  generateQueryFragments(): Operation {
1008
1044
  const [minimizedSelectionSet, fragments] = this.selectionSet.minimizeSelectionSet();
1045
+
1009
1046
  return new Operation(
1010
1047
  this.schema(),
1011
1048
  this.rootKind,
@@ -552,6 +552,27 @@ export class CoreSpecDefinition extends FeatureDefinition {
552
552
  return feature.addElementsToSchema(schema);
553
553
  }
554
554
 
555
+ applyFeatureAsLink(schema: Schema, feature: FeatureDefinition, purpose?: CorePurpose, imports?: CoreImport[]): GraphQLError[] {
556
+ const existing = schema.schemaDefinition.appliedDirectivesOf(linkDirectiveDefaultName).find((link) => link.arguments().url === feature.toString());
557
+ if (existing) {
558
+ existing.remove();
559
+ }
560
+
561
+ const coreDirective = this.coreDirective(schema);
562
+ const args: LinkDirectiveArgs = {
563
+ url: feature.toString(),
564
+ import: (existing?.arguments().import ?? []).concat(imports?.map((i) => i.as ? { name: `@${i.name}`, as: `@${i.as}` } : `@${i.name}`)),
565
+ feature: undefined,
566
+ };
567
+
568
+ if (this.supportPurposes() && purpose) {
569
+ args.for = purpose;
570
+ }
571
+
572
+ schema.schemaDefinition.applyDirective(coreDirective, args);
573
+ return feature.addElementsToSchema(schema);
574
+ }
575
+
555
576
  extractFeatureUrl(args: CoreOrLinkDirectiveArgs): FeatureUrl {
556
577
  return FeatureUrl.parse(args[this.urlArgName()]!);
557
578
  }
@@ -0,0 +1,60 @@
1
+ import { DirectiveLocation } from 'graphql';
2
+ import { createDirectiveSpecification } from '../directiveAndTypeSpecification';
3
+ import { FeatureDefinition, FeatureDefinitions, FeatureUrl, FeatureVersion } from './coreSpec';
4
+ import { ListType, NonNullType } from '../definitions';
5
+ import { registerKnownFeature } from '../knownCoreFeatures';
6
+ import { ARGUMENT_COMPOSITION_STRATEGIES } from '../argumentCompositionStrategies';
7
+
8
+ export const costIdentity = 'https://specs.apollo.dev/cost';
9
+
10
+ export class CostSpecDefinition extends FeatureDefinition {
11
+ constructor(version: FeatureVersion, readonly minimumFederationVersion: FeatureVersion) {
12
+ super(new FeatureUrl(costIdentity, 'cost', version), minimumFederationVersion);
13
+
14
+ this.registerDirective(createDirectiveSpecification({
15
+ name: 'cost',
16
+ locations: [
17
+ DirectiveLocation.ARGUMENT_DEFINITION,
18
+ DirectiveLocation.ENUM,
19
+ DirectiveLocation.FIELD_DEFINITION,
20
+ DirectiveLocation.INPUT_FIELD_DEFINITION,
21
+ DirectiveLocation.OBJECT,
22
+ DirectiveLocation.SCALAR
23
+ ],
24
+ args: [{ name: 'weight', type: (schema) => new NonNullType(schema.intType()), compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.MAX }],
25
+ composes: true,
26
+ repeatable: false,
27
+ supergraphSpecification: (fedVersion) => COST_VERSIONS.getMinimumRequiredVersion(fedVersion),
28
+ }));
29
+
30
+ this.registerDirective(createDirectiveSpecification({
31
+ name: 'listSize',
32
+ locations: [DirectiveLocation.FIELD_DEFINITION],
33
+ args: [
34
+ { name: 'assumedSize', type: (schema) => schema.intType(), compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.NULLABLE_MAX },
35
+ { name: 'slicingArguments', type: (schema) => new ListType(new NonNullType(schema.stringType())), compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.NULLABLE_UNION },
36
+ { name: 'sizedFields', type: (schema) => new ListType(new NonNullType(schema.stringType())), compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.NULLABLE_UNION },
37
+ { name: 'requireOneSlicingArgument', type: (schema) => schema.booleanType(), defaultValue: true, compositionStrategy: ARGUMENT_COMPOSITION_STRATEGIES.NULLABLE_AND },
38
+ ],
39
+ composes: true,
40
+ repeatable: false,
41
+ supergraphSpecification: (fedVersion) => COST_VERSIONS.getMinimumRequiredVersion(fedVersion)
42
+ }));
43
+ }
44
+ }
45
+
46
+ export const COST_VERSIONS = new FeatureDefinitions<CostSpecDefinition>(costIdentity)
47
+ .add(new CostSpecDefinition(new FeatureVersion(0, 1), new FeatureVersion(2, 9)));
48
+
49
+ registerKnownFeature(COST_VERSIONS);
50
+
51
+ export interface CostDirectiveArguments {
52
+ weight: number;
53
+ }
54
+
55
+ export interface ListSizeDirectiveArguments {
56
+ assumedSize?: number;
57
+ slicingArguments?: string[];
58
+ sizedFields?: string[];
59
+ requireOneSlicingArgument?: boolean;
60
+ }
@@ -20,6 +20,7 @@ import { REQUIRES_SCOPES_VERSIONS } from "./requiresScopesSpec";
20
20
  import { POLICY_VERSIONS } from './policySpec';
21
21
  import { SOURCE_VERSIONS } from './sourceSpec';
22
22
  import { CONTEXT_VERSIONS } from './contextSpec';
23
+ import { COST_VERSIONS } from "./costSpec";
23
24
 
24
25
  export const federationIdentity = 'https://specs.apollo.dev/federation';
25
26
 
@@ -48,6 +49,8 @@ export enum FederationDirectiveName {
48
49
  SOURCE_FIELD = 'sourceField',
49
50
  CONTEXT = 'context',
50
51
  FROM_CONTEXT = 'fromContext',
52
+ COST = 'cost',
53
+ LIST_SIZE = 'listSize',
51
54
  }
52
55
 
53
56
  const fieldSetTypeSpec = createScalarTypeSpecification({ name: FederationTypeName.FIELD_SET });
@@ -182,6 +185,10 @@ export class FederationSpecDefinition extends FeatureDefinition {
182
185
  if (version.gte(new FeatureVersion(2, 8))) {
183
186
  this.registerSubFeature(CONTEXT_VERSIONS.find(new FeatureVersion(0, 1))!);
184
187
  }
188
+
189
+ if (version.gte(new FeatureVersion(2, 9))) {
190
+ this.registerSubFeature(COST_VERSIONS.find(new FeatureVersion(0, 1))!);
191
+ }
185
192
  }
186
193
  }
187
194
 
@@ -194,6 +201,7 @@ export const FEDERATION_VERSIONS = new FeatureDefinitions<FederationSpecDefiniti
194
201
  .add(new FederationSpecDefinition(new FeatureVersion(2, 5)))
195
202
  .add(new FederationSpecDefinition(new FeatureVersion(2, 6)))
196
203
  .add(new FederationSpecDefinition(new FeatureVersion(2, 7)))
197
- .add(new FederationSpecDefinition(new FeatureVersion(2, 8)));
204
+ .add(new FederationSpecDefinition(new FeatureVersion(2, 8)))
205
+ .add(new FederationSpecDefinition(new FeatureVersion(2, 9)));
198
206
 
199
207
  registerKnownFeature(FEDERATION_VERSIONS);
@@ -40,6 +40,7 @@ export const ROUTER_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
40
40
  'https://specs.apollo.dev/policy/v0.1',
41
41
  'https://specs.apollo.dev/source/v0.1',
42
42
  'https://specs.apollo.dev/context/v0.1',
43
+ 'https://specs.apollo.dev/cost/v0.1',
43
44
  ]);
44
45
 
45
46
  const coreVersionZeroDotOneUrl = FeatureUrl.parse('https://specs.apollo.dev/core/v0.1');