@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.
- package/CHANGELOG.md +21 -0
- package/dist/argumentCompositionStrategies.d.ts +34 -0
- package/dist/argumentCompositionStrategies.d.ts.map +1 -0
- package/dist/argumentCompositionStrategies.js +35 -0
- package/dist/argumentCompositionStrategies.js.map +1 -0
- package/dist/coreSpec.d.ts +12 -3
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +68 -17
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +1 -0
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +30 -27
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +26 -7
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +56 -4
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +24 -2
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +2 -13
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +10 -60
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +0 -2
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +3 -6
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/knownCoreFeatures.d.ts +1 -0
- package/dist/knownCoreFeatures.d.ts.map +1 -1
- package/dist/knownCoreFeatures.js +5 -1
- package/dist/knownCoreFeatures.js.map +1 -1
- package/dist/operations.d.ts +18 -6
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +102 -37
- package/dist/operations.js.map +1 -1
- package/dist/print.d.ts +7 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +33 -5
- package/dist/print.js.map +1 -1
- package/dist/tagSpec.d.ts +0 -2
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +4 -10
- package/dist/tagSpec.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/directiveAndTypeSpecifications.test.ts +41 -0
- package/src/__tests__/operations.test.ts +175 -10
- package/src/argumentCompositionStrategies.ts +39 -0
- package/src/coreSpec.ts +94 -34
- package/src/definitions.ts +35 -29
- package/src/directiveAndTypeSpecification.ts +101 -14
- package/src/federation.ts +33 -4
- package/src/federationSpec.ts +13 -73
- package/src/inaccessibleSpec.ts +4 -11
- package/src/index.ts +3 -0
- package/src/knownCoreFeatures.ts +9 -0
- package/src/operations.ts +198 -40
- package/src/print.ts +39 -4
- package/src/tagSpec.ts +4 -12
- package/tsconfig.tsbuildinfo +1 -1
package/src/definitions.ts
CHANGED
|
@@ -1078,16 +1078,7 @@ export class CoreFeatures {
|
|
|
1078
1078
|
isImported: false,
|
|
1079
1079
|
} : undefined;
|
|
1080
1080
|
} else {
|
|
1081
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
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
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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?:
|
|
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
|
-
|
|
74
|
+
args = [],
|
|
75
|
+
composes = false,
|
|
76
|
+
supergraphSpecification = undefined,
|
|
52
77
|
}: {
|
|
53
78
|
name: string,
|
|
54
79
|
locations: DirectiveLocation[],
|
|
55
80
|
repeatable?: boolean,
|
|
56
|
-
|
|
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 {
|
|
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
|
|
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:
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
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({
|
package/src/federationSpec.ts
CHANGED
|
@@ -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
|
|
15
|
-
import { assert
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
package/src/inaccessibleSpec.ts
CHANGED
|
@@ -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';
|
package/src/knownCoreFeatures.ts
CHANGED
|
@@ -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
|
+
}
|