@apollo/federation-internals 2.7.8 → 2.8.0-alpha.1
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.
- package/dist/directiveAndTypeSpecification.d.ts +13 -1
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +2 -2
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +6 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +12 -0
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +62 -7
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +15 -2
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +394 -4
- package/dist/federation.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/operations.d.ts +10 -8
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +37 -14
- package/dist/operations.js.map +1 -1
- package/dist/specs/contextSpec.d.ts +20 -0
- package/dist/specs/contextSpec.d.ts.map +1 -0
- package/dist/specs/contextSpec.js +62 -0
- package/dist/specs/contextSpec.js.map +1 -0
- package/dist/specs/federationSpec.d.ts +5 -2
- package/dist/specs/federationSpec.d.ts.map +1 -1
- package/dist/specs/federationSpec.js +9 -1
- package/dist/specs/federationSpec.js.map +1 -1
- package/dist/specs/joinSpec.d.ts +6 -0
- package/dist/specs/joinSpec.d.ts.map +1 -1
- package/dist/specs/joinSpec.js +11 -1
- package/dist/specs/joinSpec.js.map +1 -1
- package/dist/supergraphs.d.ts +4 -0
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +35 -2
- package/dist/supergraphs.js.map +1 -1
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +39 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/directiveAndTypeSpecification.ts +8 -1
- package/src/error.ts +42 -0
- package/src/extractSubgraphsFromSupergraph.ts +76 -14
- package/src/federation.ts +593 -10
- package/src/index.ts +1 -0
- package/src/operations.ts +48 -21
- package/src/specs/contextSpec.ts +87 -0
- package/src/specs/federationSpec.ts +10 -1
- package/src/specs/joinSpec.ts +27 -3
- package/src/supergraphs.ts +37 -1
- 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);
|
package/src/specs/joinSpec.ts
CHANGED
|
@@ -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);
|
package/src/supergraphs.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|