@apollo/federation-internals 2.7.8 → 2.8.0-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 (56) hide show
  1. package/dist/directiveAndTypeSpecification.d.ts +13 -1
  2. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  3. package/dist/directiveAndTypeSpecification.js +2 -2
  4. package/dist/directiveAndTypeSpecification.js.map +1 -1
  5. package/dist/error.d.ts +6 -0
  6. package/dist/error.d.ts.map +1 -1
  7. package/dist/error.js +12 -0
  8. package/dist/error.js.map +1 -1
  9. package/dist/extractSubgraphsFromSupergraph.d.ts +1 -1
  10. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  11. package/dist/extractSubgraphsFromSupergraph.js +62 -7
  12. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  13. package/dist/federation.d.ts +15 -2
  14. package/dist/federation.d.ts.map +1 -1
  15. package/dist/federation.js +394 -4
  16. package/dist/federation.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/operations.d.ts +10 -8
  22. package/dist/operations.d.ts.map +1 -1
  23. package/dist/operations.js +37 -14
  24. package/dist/operations.js.map +1 -1
  25. package/dist/specs/contextSpec.d.ts +20 -0
  26. package/dist/specs/contextSpec.d.ts.map +1 -0
  27. package/dist/specs/contextSpec.js +62 -0
  28. package/dist/specs/contextSpec.js.map +1 -0
  29. package/dist/specs/federationSpec.d.ts +5 -2
  30. package/dist/specs/federationSpec.d.ts.map +1 -1
  31. package/dist/specs/federationSpec.js +9 -1
  32. package/dist/specs/federationSpec.js.map +1 -1
  33. package/dist/specs/joinSpec.d.ts +6 -0
  34. package/dist/specs/joinSpec.d.ts.map +1 -1
  35. package/dist/specs/joinSpec.js +11 -1
  36. package/dist/specs/joinSpec.js.map +1 -1
  37. package/dist/supergraphs.d.ts +4 -0
  38. package/dist/supergraphs.d.ts.map +1 -1
  39. package/dist/supergraphs.js +35 -2
  40. package/dist/supergraphs.js.map +1 -1
  41. package/dist/utils.d.ts +3 -0
  42. package/dist/utils.d.ts.map +1 -1
  43. package/dist/utils.js +39 -1
  44. package/dist/utils.js.map +1 -1
  45. package/package.json +1 -1
  46. package/src/directiveAndTypeSpecification.ts +8 -1
  47. package/src/error.ts +42 -0
  48. package/src/extractSubgraphsFromSupergraph.ts +76 -14
  49. package/src/federation.ts +593 -10
  50. package/src/index.ts +1 -0
  51. package/src/operations.ts +48 -21
  52. package/src/specs/contextSpec.ts +87 -0
  53. package/src/specs/federationSpec.ts +10 -1
  54. package/src/specs/joinSpec.ts +27 -3
  55. package/src/supergraphs.ts +37 -1
  56. package/src/utils.ts +38 -0
package/src/operations.ts CHANGED
@@ -21,11 +21,9 @@ import {
21
21
  Directive,
22
22
  DirectiveTargetElement,
23
23
  FieldDefinition,
24
- InterfaceType,
25
24
  isCompositeType,
26
25
  isInterfaceType,
27
26
  isNullableType,
28
- ObjectType,
29
27
  runtimeTypesIntersects,
30
28
  Schema,
31
29
  SchemaRootKind,
@@ -51,7 +49,7 @@ import {
51
49
  directivesToString,
52
50
  directivesToDirectiveNodes,
53
51
  } from "./definitions";
54
- import { isInterfaceObjectType } from "./federation";
52
+ import { federationMetadata, isFederationDirectiveDefinedInSchema, isInterfaceObjectType } from "./federation";
55
53
  import { ERRORS } from "./error";
56
54
  import { isSubtype, sameType, typesCanBeMerged } from "./types";
57
55
  import { assert, mapKeys, mapValues, MapWithCachedArrays, MultiMap, SetMultiMap } from "./utils";
@@ -170,6 +168,17 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
170
168
  baseType(): NamedType {
171
169
  return baseType(this.definition.type!);
172
170
  }
171
+
172
+ withUpdatedArguments(newArgs: TArgs): Field<TArgs> {
173
+ const newField = new Field<TArgs>(
174
+ this.definition,
175
+ { ...this.args, ...newArgs },
176
+ this.appliedDirectives,
177
+ this.alias,
178
+ );
179
+ this.copyAttachementsTo(newField);
180
+ return newField;
181
+ }
173
182
 
174
183
  withUpdatedDefinition(newDefinition: FieldDefinition<any>): Field<TArgs> {
175
184
  const newField = new Field<TArgs>(
@@ -222,17 +231,12 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
222
231
  };
223
232
  });
224
233
  }
225
-
226
-
227
- appliesTo(type: ObjectType | InterfaceType): boolean {
228
- const definition = type.field(this.name);
229
- return !!definition && this.selects(definition);
230
- }
231
-
234
+
232
235
  selects(
233
236
  definition: FieldDefinition<any>,
234
237
  assumeValid: boolean = false,
235
238
  variableDefinitions?: VariableDefinitions,
239
+ contextualArguments?: string[],
236
240
  ): boolean {
237
241
  assert(assumeValid || variableDefinitions, 'Must provide variable definitions if validation is needed');
238
242
 
@@ -252,7 +256,7 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
252
256
  for (const argDef of definition.arguments()) {
253
257
  const appliedValue = this.argumentValue(argDef.name);
254
258
  if (appliedValue === undefined) {
255
- if (argDef.defaultValue === undefined && !isNullableType(argDef.type!)) {
259
+ if (argDef.defaultValue === undefined && !isNullableType(argDef.type!) && (!contextualArguments || !contextualArguments?.includes(argDef.name))) {
256
260
  return false;
257
261
  }
258
262
  } else {
@@ -273,19 +277,28 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
273
277
  return true;
274
278
  }
275
279
 
276
- validate(variableDefinitions: VariableDefinitions) {
280
+ validate(variableDefinitions: VariableDefinitions, validateContextualArgs: boolean) {
277
281
  validate(this.name === this.definition.name, () => `Field name "${this.name}" cannot select field "${this.definition.coordinate}: name mismatch"`);
278
-
282
+
283
+
279
284
  // We need to make sure the field has valid values for every non-optional argument.
280
285
  for (const argDef of this.definition.arguments()) {
281
286
  const appliedValue = this.argumentValue(argDef.name);
287
+
288
+ let isContextualArg = false;
289
+ const schema = this.definition.schema();
290
+ const fromContextDirective = federationMetadata(schema)?.fromContextDirective();
291
+ if (fromContextDirective && isFederationDirectiveDefinedInSchema(fromContextDirective)) {
292
+ isContextualArg = argDef.appliedDirectivesOf(fromContextDirective).length > 0;
293
+ }
294
+
282
295
  if (appliedValue === undefined) {
283
296
  validate(
284
- argDef.defaultValue !== undefined || isNullableType(argDef.type!),
297
+ (isContextualArg && !validateContextualArgs) || argDef.defaultValue !== undefined || isNullableType(argDef.type!),
285
298
  () => `Missing mandatory value for argument "${argDef.name}" of field "${this.definition.coordinate}" in selection "${this}"`);
286
299
  } else {
287
300
  validate(
288
- isValidValue(appliedValue, argDef, variableDefinitions),
301
+ (isContextualArg && !validateContextualArgs) || isValidValue(appliedValue, argDef, variableDefinitions),
289
302
  () => `Invalid value ${valueToString(appliedValue)} for argument "${argDef.coordinate}" of type ${argDef.type}`)
290
303
  }
291
304
  }
@@ -1998,10 +2011,10 @@ export class SelectionSet {
1998
2011
  return this.selections().every((selection) => selection.canAddTo(parentTypeToTest));
1999
2012
  }
2000
2013
 
2001
- validate(variableDefinitions: VariableDefinitions) {
2014
+ validate(variableDefinitions: VariableDefinitions, validateContextualArgs: boolean = false) {
2002
2015
  validate(!this.isEmpty(), () => `Invalid empty selection set`);
2003
2016
  for (const selection of this.selections()) {
2004
- selection.validate(variableDefinitions);
2017
+ selection.validate(variableDefinitions, validateContextualArgs);
2005
2018
  }
2006
2019
  }
2007
2020
 
@@ -2520,7 +2533,7 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
2520
2533
 
2521
2534
  abstract toSelectionNode(): SelectionNode;
2522
2535
 
2523
- abstract validate(variableDefinitions: VariableDefinitions): void;
2536
+ abstract validate(variableDefinitions: VariableDefinitions, validateContextualArgs: boolean): void;
2524
2537
 
2525
2538
  abstract rebaseOn(args: { parentType: CompositeType, fragments: NamedFragments | undefined, errorIfCannotRebase: boolean}): TOwnType | undefined;
2526
2539
 
@@ -3035,8 +3048,8 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
3035
3048
  return predicate(thisWithFilteredSelectionSet) ? thisWithFilteredSelectionSet : undefined;
3036
3049
  }
3037
3050
 
3038
- validate(variableDefinitions: VariableDefinitions) {
3039
- this.element.validate(variableDefinitions);
3051
+ validate(variableDefinitions: VariableDefinitions, validateContextualArgs: boolean) {
3052
+ this.element.validate(variableDefinitions, validateContextualArgs);
3040
3053
  // Note that validation is kind of redundant since `this.selectionSet.validate()` will check that it isn't empty. But doing it
3041
3054
  // allow to provide much better error messages.
3042
3055
  validate(
@@ -3955,7 +3968,7 @@ export function parseSelectionSet({
3955
3968
  return selectionSet;
3956
3969
  }
3957
3970
 
3958
- function parseOperationAST(source: string): OperationDefinitionNode {
3971
+ export function parseOperationAST(source: string): OperationDefinitionNode {
3959
3972
  const parsed = parse(source);
3960
3973
  validate(parsed.definitions.length === 1, () => 'Selections should contain a single definitions, found ' + parsed.definitions.length);
3961
3974
  const def = parsed.definitions[0];
@@ -3980,3 +3993,17 @@ export function operationToDocument(operation: Operation): DocumentNode {
3980
3993
  definitions: [operationAST as DefinitionNode].concat(fragmentASTs),
3981
3994
  };
3982
3995
  }
3996
+
3997
+ export function hasSelectionWithPredicate(selectionSet: SelectionSet, predicate: (s: Selection) => boolean): boolean {
3998
+ for (const selection of selectionSet.selections()) {
3999
+ if (predicate(selection)) {
4000
+ return true;
4001
+ }
4002
+ if (selection.selectionSet) {
4003
+ if (hasSelectionWithPredicate(selection.selectionSet, predicate)) {
4004
+ return true;
4005
+ }
4006
+ }
4007
+ }
4008
+ return false;
4009
+ }
@@ -0,0 +1,87 @@
1
+ import { DirectiveLocation } from "graphql";
2
+ import {
3
+ CorePurpose,
4
+ FeatureDefinition,
5
+ FeatureDefinitions,
6
+ FeatureUrl,
7
+ FeatureVersion,
8
+ } from "./coreSpec";
9
+ import { DirectiveDefinition, NonNullType, Schema, isInputType } from "../definitions";
10
+ import { DirectiveSpecification, createDirectiveSpecification, createScalarTypeSpecification } from "../directiveAndTypeSpecification";
11
+ import { registerKnownFeature } from "../knownCoreFeatures";
12
+ import { Subgraph } from '../federation';
13
+ import { assert } from '../utils';
14
+
15
+ export enum ContextDirectiveName {
16
+ CONTEXT = 'context',
17
+ FROM_CONTEXT = 'fromContext',
18
+ }
19
+
20
+ const fieldValueScalar = 'ContextFieldValue';
21
+
22
+ export class ContextSpecDefinition extends FeatureDefinition {
23
+ public static readonly directiveName = 'context';
24
+ public static readonly identity =
25
+ `https://specs.apollo.dev/${ContextSpecDefinition.directiveName}`;
26
+ public readonly contextDirectiveSpec: DirectiveSpecification;
27
+ public readonly fromContextDirectiveSpec: DirectiveSpecification;
28
+
29
+ constructor(version: FeatureVersion) {
30
+ super(
31
+ new FeatureUrl(
32
+ ContextSpecDefinition.identity,
33
+ ContextSpecDefinition.directiveName,
34
+ version,
35
+ )
36
+ );
37
+
38
+ this.registerType(createScalarTypeSpecification({ name: fieldValueScalar }));
39
+
40
+ this.contextDirectiveSpec = createDirectiveSpecification({
41
+ name: ContextDirectiveName.CONTEXT,
42
+ locations: [DirectiveLocation.INTERFACE, DirectiveLocation.OBJECT, DirectiveLocation.UNION],
43
+ args: [{ name: 'name', type: (schema) => new NonNullType(schema.stringType())}],
44
+ composes: true,
45
+ repeatable: true,
46
+ supergraphSpecification: (fedVersion) => CONTEXT_VERSIONS.getMinimumRequiredVersion(fedVersion),
47
+ staticArgumentTransform: (subgraph: Subgraph, args: {[key: string]: any}) => {
48
+ const subgraphName = subgraph.name;
49
+ return {
50
+ name: `${subgraphName}__${args.name}`,
51
+ };
52
+ },
53
+ });
54
+
55
+ this.fromContextDirectiveSpec = createDirectiveSpecification({
56
+ name: ContextDirectiveName.FROM_CONTEXT,
57
+ locations: [DirectiveLocation.ARGUMENT_DEFINITION],
58
+ args: [{ name: 'field', type: (schema, feature) => {
59
+ assert(feature, "Shouldn't be added without being attached to a @link spec");
60
+ const fieldValue = feature.typeNameInSchema(fieldValueScalar);
61
+ const fieldValueType = schema.type(fieldValue);
62
+ assert(fieldValueType, () => `Expected "${fieldValue}" to be defined`);
63
+ assert(isInputType(fieldValueType), `Expected "${fieldValue}" to be an input type`);
64
+ return fieldValueType;
65
+ }}],
66
+ composes: false,
67
+ });
68
+
69
+ this.registerDirective(this.contextDirectiveSpec);
70
+ this.registerDirective(this.fromContextDirectiveSpec);
71
+ }
72
+
73
+ get defaultCorePurpose(): CorePurpose {
74
+ return 'SECURITY';
75
+ }
76
+
77
+ contextDirective(schema: Schema): DirectiveDefinition<{ name: string }> | undefined {
78
+ return this.directive(schema, ContextSpecDefinition.directiveName);
79
+ }
80
+ }
81
+
82
+ export const CONTEXT_VERSIONS =
83
+ new FeatureDefinitions<ContextSpecDefinition>(
84
+ ContextSpecDefinition.identity
85
+ ).add(new ContextSpecDefinition(new FeatureVersion(0, 1)));
86
+
87
+ registerKnownFeature(CONTEXT_VERSIONS);
@@ -19,11 +19,13 @@ import { AUTHENTICATED_VERSIONS } from "./authenticatedSpec";
19
19
  import { REQUIRES_SCOPES_VERSIONS } from "./requiresScopesSpec";
20
20
  import { POLICY_VERSIONS } from './policySpec';
21
21
  import { SOURCE_VERSIONS } from './sourceSpec';
22
+ import { CONTEXT_VERSIONS } from './contextSpec';
22
23
 
23
24
  export const federationIdentity = 'https://specs.apollo.dev/federation';
24
25
 
25
26
  export enum FederationTypeName {
26
27
  FIELD_SET = 'FieldSet',
28
+ CONTEXT_FIELD_VALUE = 'ContextFieldValue',
27
29
  }
28
30
 
29
31
  export enum FederationDirectiveName {
@@ -44,6 +46,8 @@ export enum FederationDirectiveName {
44
46
  SOURCE_API = 'sourceAPI',
45
47
  SOURCE_TYPE = 'sourceType',
46
48
  SOURCE_FIELD = 'sourceField',
49
+ CONTEXT = 'context',
50
+ FROM_CONTEXT = 'fromContext',
47
51
  }
48
52
 
49
53
  const fieldSetTypeSpec = createScalarTypeSpecification({ name: FederationTypeName.FIELD_SET });
@@ -174,6 +178,10 @@ export class FederationSpecDefinition extends FeatureDefinition {
174
178
  if (version.gte(new FeatureVersion(2, 7))) {
175
179
  this.registerSubFeature(SOURCE_VERSIONS.find(new FeatureVersion(0, 1))!);
176
180
  }
181
+
182
+ if (version.gte(new FeatureVersion(2, 8))) {
183
+ this.registerSubFeature(CONTEXT_VERSIONS.find(new FeatureVersion(0, 1))!);
184
+ }
177
185
  }
178
186
  }
179
187
 
@@ -185,6 +193,7 @@ export const FEDERATION_VERSIONS = new FeatureDefinitions<FederationSpecDefiniti
185
193
  .add(new FederationSpecDefinition(new FeatureVersion(2, 4)))
186
194
  .add(new FederationSpecDefinition(new FeatureVersion(2, 5)))
187
195
  .add(new FederationSpecDefinition(new FeatureVersion(2, 6)))
188
- .add(new FederationSpecDefinition(new FeatureVersion(2, 7)));
196
+ .add(new FederationSpecDefinition(new FeatureVersion(2, 7)))
197
+ .add(new FederationSpecDefinition(new FeatureVersion(2, 8)));
189
198
 
190
199
  registerKnownFeature(FEDERATION_VERSIONS);
@@ -7,6 +7,7 @@ import {
7
7
  Schema,
8
8
  NonNullType,
9
9
  ListType,
10
+ InputObjectType,
10
11
  } from "../definitions";
11
12
  import { Subgraph, Subgraphs } from "../federation";
12
13
  import { registerKnownFeature } from '../knownCoreFeatures';
@@ -36,7 +37,7 @@ export type JoinTypeDirectiveArguments = {
36
37
  key?: string,
37
38
  extension?: boolean,
38
39
  resolvable?: boolean,
39
- isInterfaceObject?: boolean
40
+ isInterfaceObject?: boolean,
40
41
  };
41
42
 
42
43
  export type JoinFieldDirectiveArguments = {
@@ -48,6 +49,12 @@ export type JoinFieldDirectiveArguments = {
48
49
  external?: boolean,
49
50
  usedOverridden?: boolean,
50
51
  overrideLabel?: string,
52
+ contextArguments?: {
53
+ name: string,
54
+ type: string,
55
+ context: string,
56
+ selection: string,
57
+ }[],
51
58
  }
52
59
 
53
60
  export type JoinDirectiveArguments = {
@@ -151,9 +158,24 @@ export class JoinSpecDefinition extends FeatureDefinition {
151
158
  joinDirective.addArgument('name', new NonNullType(schema.stringType()));
152
159
  joinDirective.addArgument('args', this.addScalarType(schema, 'DirectiveArguments'));
153
160
 
154
- //progressive override
161
+ // progressive override
155
162
  joinField.addArgument('overrideLabel', schema.stringType());
156
163
  }
164
+
165
+ if (this.version.gte(new FeatureVersion(0, 5))) {
166
+ const fieldValue = this.addScalarType(schema, 'FieldValue');
167
+
168
+ // set context
169
+ // there are no renames that happen within the join spec, so this is fine
170
+ // note that join spec will only used in supergraph schema
171
+ const contextArgumentsType = schema.addType(new InputObjectType('join__ContextArgument'));
172
+ contextArgumentsType.addField('name', new NonNullType(schema.stringType()));
173
+ contextArgumentsType.addField('type', new NonNullType(schema.stringType()));
174
+ contextArgumentsType.addField('context', new NonNullType(schema.stringType()));
175
+ contextArgumentsType.addField('selection', new NonNullType(fieldValue));
176
+
177
+ joinField.addArgument('contextArguments', new ListType(new NonNullType(contextArgumentsType)));
178
+ }
157
179
 
158
180
  if (this.isV01()) {
159
181
  const joinOwner = this.addDirective(schema, 'owner').addLocations(DirectiveLocation.OBJECT);
@@ -261,10 +283,12 @@ export class JoinSpecDefinition extends FeatureDefinition {
261
283
  // - 0.2: this is the original version released with federation 2.
262
284
  // - 0.3: adds the `isInterfaceObject` argument to `@join__type`, and make the `graph` in `@join__field` skippable.
263
285
  // - 0.4: adds the optional `overrideLabel` argument to `@join_field` for progressive override.
286
+ // - 0.5: adds the `contextArguments` argument to `@join_field` for setting context.
264
287
  export const JOIN_VERSIONS = new FeatureDefinitions<JoinSpecDefinition>(joinIdentity)
265
288
  .add(new JoinSpecDefinition(new FeatureVersion(0, 1)))
266
289
  .add(new JoinSpecDefinition(new FeatureVersion(0, 2)))
267
290
  .add(new JoinSpecDefinition(new FeatureVersion(0, 3), new FeatureVersion(2, 0)))
268
- .add(new JoinSpecDefinition(new FeatureVersion(0, 4), new FeatureVersion(2, 7)));
291
+ .add(new JoinSpecDefinition(new FeatureVersion(0, 4), new FeatureVersion(2, 7)))
292
+ .add(new JoinSpecDefinition(new FeatureVersion(0, 5), new FeatureVersion(2, 8)));
269
293
 
270
294
  registerKnownFeature(JOIN_VERSIONS);
@@ -14,6 +14,7 @@ export const DEFAULT_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
14
14
  'https://specs.apollo.dev/join/v0.2',
15
15
  'https://specs.apollo.dev/join/v0.3',
16
16
  'https://specs.apollo.dev/join/v0.4',
17
+ 'https://specs.apollo.dev/join/v0.5',
17
18
  'https://specs.apollo.dev/tag/v0.1',
18
19
  'https://specs.apollo.dev/tag/v0.2',
19
20
  'https://specs.apollo.dev/tag/v0.3',
@@ -21,6 +22,26 @@ export const DEFAULT_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
21
22
  'https://specs.apollo.dev/inaccessible/v0.2',
22
23
  ]);
23
24
 
25
+ export const ROUTER_SUPPORTED_SUPERGRAPH_FEATURES = new Set([
26
+ 'https://specs.apollo.dev/core/v0.1',
27
+ 'https://specs.apollo.dev/core/v0.2',
28
+ 'https://specs.apollo.dev/join/v0.1',
29
+ 'https://specs.apollo.dev/join/v0.2',
30
+ 'https://specs.apollo.dev/join/v0.3',
31
+ 'https://specs.apollo.dev/join/v0.4',
32
+ 'https://specs.apollo.dev/join/v0.5',
33
+ 'https://specs.apollo.dev/tag/v0.1',
34
+ 'https://specs.apollo.dev/tag/v0.2',
35
+ 'https://specs.apollo.dev/tag/v0.3',
36
+ 'https://specs.apollo.dev/inaccessible/v0.1',
37
+ 'https://specs.apollo.dev/inaccessible/v0.2',
38
+ 'https://specs.apollo.dev/authenticated/v0.1',
39
+ 'https://specs.apollo.dev/requiresScopes/v0.1',
40
+ 'https://specs.apollo.dev/policy/v0.1',
41
+ 'https://specs.apollo.dev/source/v0.1',
42
+ 'https://specs.apollo.dev/context/v0.1',
43
+ ]);
44
+
24
45
  const coreVersionZeroDotOneUrl = FeatureUrl.parse('https://specs.apollo.dev/core/v0.1');
25
46
 
26
47
  /**
@@ -84,6 +105,7 @@ export class Supergraph {
84
105
  private readonly containedSubgraphs: readonly {name: string, url: string}[];
85
106
  // Lazily computed as that requires a bit of work.
86
107
  private _subgraphs?: Subgraphs;
108
+ private _subgraphNameToGraphEnumValue?: Map<string, string>;
87
109
 
88
110
  constructor(
89
111
  readonly schema: Schema,
@@ -114,6 +136,9 @@ export class Supergraph {
114
136
  return new Supergraph(schema, options?.supportedFeatures, options?.validateSupergraph);
115
137
  }
116
138
 
139
+ static buildForTests(supergraphSdl: string | DocumentNode, validateSupergraph?: boolean) {
140
+ return Supergraph.build(supergraphSdl, { supportedFeatures: ROUTER_SUPPORTED_SUPERGRAPH_FEATURES, validateSupergraph });
141
+ }
117
142
  /**
118
143
  * The list of names/urls of the subgraphs contained in this subgraph.
119
144
  *
@@ -129,11 +154,22 @@ export class Supergraph {
129
154
  // Note that `extractSubgraphsFromSupergraph` redo a little bit of work we're already one, like validating
130
155
  // the supergraph. We could refactor things to avoid it, but it's completely negligible in practice so we
131
156
  // can leave that to "some day, maybe".
132
- this._subgraphs = extractSubgraphsFromSupergraph(this.schema, this.shouldValidate);
157
+ const extractionResults = extractSubgraphsFromSupergraph(this.schema, this.shouldValidate);
158
+ this._subgraphs = extractionResults[0];
159
+ this._subgraphNameToGraphEnumValue = extractionResults[1];
133
160
  }
134
161
  return this._subgraphs;
135
162
  }
136
163
 
164
+ subgraphNameToGraphEnumValue(): Map<string, string> {
165
+ if (!this._subgraphNameToGraphEnumValue) {
166
+ const extractionResults = extractSubgraphsFromSupergraph(this.schema, this.shouldValidate);
167
+ this._subgraphs = extractionResults[0];
168
+ this._subgraphNameToGraphEnumValue = extractionResults[1];
169
+ }
170
+ return new Map([...this._subgraphNameToGraphEnumValue]);
171
+ }
172
+
137
173
  apiSchema(): Schema {
138
174
  return this.schema.toAPISchema();
139
175
  }
package/src/utils.ts CHANGED
@@ -441,3 +441,41 @@ export function findLast<T>(array: T[], predicate: (t: T) => boolean): T | undef
441
441
  }
442
442
  return undefined;
443
443
  }
444
+
445
+ export function mergeMapOrNull<K,V>(m1: Map<K, V> | null, m2: Map<K, V> | null): Map<K, V> | null {
446
+ if (!m1) {
447
+ return m2;
448
+ }
449
+ if (!m2) {
450
+ return m1;
451
+ }
452
+ return new Map<K, V>([...m1, ...m2]);
453
+ }
454
+
455
+ export function composeSets<T>(s1: Set<T> | null, s2: Set<T> | null): Set<T> | null {
456
+ if (!s1 && !s2) {
457
+ return null;
458
+ }
459
+ const result = new Set<T>();
460
+ s1?.forEach(v => result.add(v));
461
+ s2?.forEach(v => result.add(v));
462
+ return result;
463
+ }
464
+
465
+ export function setsEqual<T>(s1: Set<T> | null, s2: Set<T> | null): boolean {
466
+ if (s1 === s2) {
467
+ return true;
468
+ }
469
+ if (!s1 && !s2) {
470
+ return true;
471
+ }
472
+ if (!s1 || !s2 || s1.size !== s2.size) {
473
+ return false;
474
+ }
475
+ for (const key of s1) {
476
+ if (!s2.has(key)) {
477
+ return false;
478
+ }
479
+ }
480
+ return true;
481
+ }