@apollo/federation-internals 2.1.0-alpha.2 → 2.1.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.
- package/CHANGELOG.md +5 -4
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +14 -4
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +1 -4
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +1 -5
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +66 -34
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +309 -165
- package/dist/definitions.js.map +1 -1
- package/dist/error.d.ts +5 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +47 -1
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +135 -5
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +3 -2
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +12 -7
- package/dist/federation.js.map +1 -1
- package/dist/inaccessibleSpec.js +1 -2
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/operations.d.ts +44 -20
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +287 -27
- package/dist/operations.js.map +1 -1
- package/dist/print.d.ts +1 -1
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +1 -1
- package/dist/print.js.map +1 -1
- package/dist/schemaUpgrader.d.ts.map +1 -1
- package/dist/schemaUpgrader.js +6 -6
- package/dist/schemaUpgrader.js.map +1 -1
- package/dist/supergraphs.d.ts +1 -3
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +9 -22
- package/dist/supergraphs.js.map +1 -1
- package/dist/utils.d.ts +10 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +40 -1
- package/dist/utils.js.map +1 -1
- package/package.json +3 -4
- package/src/__tests__/coreSpec.test.ts +1 -1
- package/src/__tests__/definitions.test.ts +27 -0
- package/src/__tests__/extractSubgraphsFromSupergraph.test.ts +9 -5
- package/src/__tests__/operations.test.ts +36 -0
- package/src/__tests__/removeInaccessibleElements.test.ts +1 -3
- package/src/__tests__/schemaUpgrader.test.ts +0 -1
- package/src/__tests__/subgraphValidation.test.ts +1 -2
- package/src/buildSchema.ts +20 -7
- package/src/coreSpec.ts +2 -7
- package/src/definitions.ts +355 -155
- package/src/error.ts +62 -0
- package/src/extractSubgraphsFromSupergraph.ts +198 -7
- package/src/federation.ts +11 -4
- package/src/inaccessibleSpec.ts +2 -5
- package/src/operations.ts +428 -40
- package/src/print.ts +3 -3
- package/src/schemaUpgrader.ts +7 -6
- package/src/supergraphs.ts +16 -25
- package/src/utils.ts +49 -0
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/error.ts
CHANGED
|
@@ -56,6 +56,62 @@ export function extractGraphQLErrorOptions(e: GraphQLError): GraphQLErrorOptions
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
class AggregateGraphQLError extends GraphQLError {
|
|
60
|
+
constructor(
|
|
61
|
+
code: String,
|
|
62
|
+
message: string,
|
|
63
|
+
readonly causes: GraphQLError[],
|
|
64
|
+
options?: GraphQLErrorOptions,
|
|
65
|
+
) {
|
|
66
|
+
super(
|
|
67
|
+
message + '. Caused by:\n' + causes.map((c) => c.toString()).join('\n\n'),
|
|
68
|
+
{
|
|
69
|
+
...options,
|
|
70
|
+
extensions: { code },
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
toString() {
|
|
76
|
+
let output = `[${this.extensions.code}] ${super.toString()}`
|
|
77
|
+
output += "\ncaused by:";
|
|
78
|
+
for (const cause of this.causes) {
|
|
79
|
+
output += "\n\n - ";
|
|
80
|
+
output += cause.toString().split("\n").join("\n ");
|
|
81
|
+
}
|
|
82
|
+
return output;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function aggregateError(code: String, message: string, causes: GraphQLError[]): GraphQLError {
|
|
87
|
+
return new AggregateGraphQLError(code, message, causes);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Given an error, check if it is a graphQL error and potentially extract its causes if is aggregate.
|
|
92
|
+
* If the error is not a graphQL error, undefined is returned.
|
|
93
|
+
*/
|
|
94
|
+
export function errorCauses(e: Error): GraphQLError[] | undefined {
|
|
95
|
+
if (e instanceof AggregateGraphQLError) {
|
|
96
|
+
return e.causes;
|
|
97
|
+
}
|
|
98
|
+
if (e instanceof GraphQLError) {
|
|
99
|
+
return [e];
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function printGraphQLErrorsOrRethrow(e: Error): string {
|
|
105
|
+
const causes = errorCauses(e);
|
|
106
|
+
if (!causes) {
|
|
107
|
+
throw e;
|
|
108
|
+
}
|
|
109
|
+
return causes.map(e => e.toString()).join('\n\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function printErrors(errors: GraphQLError[]): string {
|
|
113
|
+
return errors.map(e => e.toString()).join('\n\n');
|
|
114
|
+
}
|
|
59
115
|
/*
|
|
60
116
|
* Most codes currently originate from the initial fed 2 release so we use this for convenience.
|
|
61
117
|
* This can be changed later, inline versions everywhere, if that becomes irrelevant.
|
|
@@ -144,6 +200,11 @@ const TYPE_DEFINITION_INVALID = makeCodeDefinition(
|
|
|
144
200
|
'A built-in or federation type has an invalid definition in the schema.',
|
|
145
201
|
);
|
|
146
202
|
|
|
203
|
+
const UNSUPPORTED_LINKED_FEATURE = makeCodeDefinition(
|
|
204
|
+
'UNSUPPORTED_LINKED_FEATURE',
|
|
205
|
+
'Indicates that a feature used in a @link is either unsupported or is used with unsupported options.',
|
|
206
|
+
);
|
|
207
|
+
|
|
147
208
|
const UNKNOWN_FEDERATION_LINK_VERSION = makeCodeDefinition(
|
|
148
209
|
'UNKNOWN_FEDERATION_LINK_VERSION',
|
|
149
210
|
'The version of federation in a @link directive on the schema is unknown.',
|
|
@@ -479,6 +540,7 @@ export const ERRORS = {
|
|
|
479
540
|
INVALID_GRAPHQL,
|
|
480
541
|
DIRECTIVE_DEFINITION_INVALID,
|
|
481
542
|
TYPE_DEFINITION_INVALID,
|
|
543
|
+
UNSUPPORTED_LINKED_FEATURE,
|
|
482
544
|
UNKNOWN_FEDERATION_LINK_VERSION,
|
|
483
545
|
UNKNOWN_LINK_VERSION,
|
|
484
546
|
KEY_FIELDS_HAS_ARGS,
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
baseType,
|
|
3
3
|
CompositeType,
|
|
4
|
+
copyDirectiveDefinitionToSchema,
|
|
4
5
|
Directive,
|
|
5
6
|
FieldDefinition,
|
|
6
7
|
InputFieldDefinition,
|
|
7
8
|
InputObjectType,
|
|
8
9
|
InterfaceType,
|
|
10
|
+
isExecutableDirectiveLocation,
|
|
9
11
|
isEnumType,
|
|
10
12
|
isInterfaceType,
|
|
11
13
|
isObjectType,
|
|
@@ -32,6 +34,7 @@ import { validateSupergraph } from "./supergraphs";
|
|
|
32
34
|
import { builtTypeReference } from "./buildSchema";
|
|
33
35
|
import { isSubtype } from "./types";
|
|
34
36
|
import { printSchema } from "./print";
|
|
37
|
+
import { parseSelectionSet } from "./operations";
|
|
35
38
|
import fs from 'fs';
|
|
36
39
|
import path from 'path';
|
|
37
40
|
import { validateStringContainsBoolean } from "./utils";
|
|
@@ -80,6 +83,114 @@ class SubgraphExtractionError {
|
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
function collectFieldReachableTypesForSubgraph(
|
|
87
|
+
supergraph: Schema,
|
|
88
|
+
subgraphName: string,
|
|
89
|
+
addReachableType: (t: NamedType) => void,
|
|
90
|
+
fieldInfoInSubgraph: (f: FieldDefinition<any> | InputFieldDefinition, subgraphName: string) => { isInSubgraph: boolean, typesInFederationDirectives: NamedType[] },
|
|
91
|
+
typeInfoInSubgraph: (t: NamedType, subgraphName: string) => { isEntityWithKeyInSubgraph: boolean, typesInFederationDirectives: NamedType[] },
|
|
92
|
+
): void {
|
|
93
|
+
const seenTypes = new Set<string>();
|
|
94
|
+
// The types reachable at "top-level" are both the root types, plus any entity type with a key in this subgraph.
|
|
95
|
+
const stack = supergraph.schemaDefinition.roots().map((root) => root.type as NamedType)
|
|
96
|
+
for (const type of supergraph.types()) {
|
|
97
|
+
const { isEntityWithKeyInSubgraph, typesInFederationDirectives } = typeInfoInSubgraph(type, subgraphName);
|
|
98
|
+
if (isEntityWithKeyInSubgraph) {
|
|
99
|
+
stack.push(type);
|
|
100
|
+
}
|
|
101
|
+
typesInFederationDirectives.forEach((t) => stack.push(t));
|
|
102
|
+
}
|
|
103
|
+
while (stack.length > 0) {
|
|
104
|
+
const type = stack.pop()!;
|
|
105
|
+
addReachableType(type);
|
|
106
|
+
if (seenTypes.has(type.name)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
seenTypes.add(type.name);
|
|
110
|
+
switch (type.kind) {
|
|
111
|
+
// @ts-expect-error: we fall-through to ObjectType for fields and implemented interfaces.
|
|
112
|
+
case 'InterfaceType':
|
|
113
|
+
// If an interface if reachable, then all of its implementation are too (a field returning the interface could return any of the
|
|
114
|
+
// implementation at runtime typically).
|
|
115
|
+
type.allImplementations().forEach((t) => stack.push(t));
|
|
116
|
+
case 'ObjectType':
|
|
117
|
+
type.interfaces().forEach((t) => stack.push(t));
|
|
118
|
+
for (const field of type.fields()) {
|
|
119
|
+
const { isInSubgraph, typesInFederationDirectives } = fieldInfoInSubgraph(field, subgraphName);
|
|
120
|
+
if (isInSubgraph) {
|
|
121
|
+
field.arguments().forEach((arg) => stack.push(baseType(arg.type!)));
|
|
122
|
+
stack.push(baseType(field.type!));
|
|
123
|
+
typesInFederationDirectives.forEach((t) => stack.push(t));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
break;
|
|
127
|
+
case 'InputObjectType':
|
|
128
|
+
for (const field of type.fields()) {
|
|
129
|
+
const { isInSubgraph, typesInFederationDirectives } = fieldInfoInSubgraph(field, subgraphName);
|
|
130
|
+
if (isInSubgraph) {
|
|
131
|
+
stack.push(baseType(field.type!));
|
|
132
|
+
typesInFederationDirectives.forEach((t) => stack.push(t));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
case 'UnionType':
|
|
137
|
+
type.members().forEach((m) => stack.push(m.type));
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const directive of supergraph.directives()) {
|
|
143
|
+
// In fed1 supergraphs, which is the only place this is called, only executable directive from subgraph only ever made
|
|
144
|
+
// it to the supergraph. Skipping anything else saves us from worrying about supergraph-specific directives too.
|
|
145
|
+
if (!directive.hasExecutableLocations()) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
directive.arguments().forEach((arg) => stack.push(baseType(arg.type!)));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function collectFieldReachableTypesForAllSubgraphs(
|
|
153
|
+
supergraph: Schema,
|
|
154
|
+
allSubgraphs: readonly string[],
|
|
155
|
+
fieldInfoInSubgraph: (f: FieldDefinition<any> | InputFieldDefinition, subgraphName: string) => { isInSubgraph: boolean, typesInFederationDirectives: NamedType[] },
|
|
156
|
+
typeInfoInSubgraph: (t: NamedType, subgraphName: string) => { isEntityWithKeyInSubgraph: boolean, typesInFederationDirectives: NamedType[] },
|
|
157
|
+
): Map<string, Set<string>> {
|
|
158
|
+
const reachableTypesBySubgraphs = new Map<string, Set<string>>();
|
|
159
|
+
for (const subgraphName of allSubgraphs) {
|
|
160
|
+
const reachableTypes = new Set<string>();
|
|
161
|
+
collectFieldReachableTypesForSubgraph(
|
|
162
|
+
supergraph,
|
|
163
|
+
subgraphName,
|
|
164
|
+
(t) => reachableTypes.add(t.name),
|
|
165
|
+
fieldInfoInSubgraph,
|
|
166
|
+
typeInfoInSubgraph,
|
|
167
|
+
);
|
|
168
|
+
reachableTypesBySubgraphs.set(subgraphName, reachableTypes);
|
|
169
|
+
}
|
|
170
|
+
return reachableTypesBySubgraphs;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function typesUsedInFederationDirective(fieldSet: string | undefined, parentType: CompositeType): NamedType[] {
|
|
174
|
+
if (!fieldSet) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const usedTypes: NamedType[] = [];
|
|
179
|
+
parseSelectionSet({
|
|
180
|
+
parentType,
|
|
181
|
+
source: fieldSet,
|
|
182
|
+
fieldAccessor: (type, fieldName) => {
|
|
183
|
+
const field = type.field(fieldName);
|
|
184
|
+
if (field) {
|
|
185
|
+
usedTypes.push(baseType(field.type!));
|
|
186
|
+
}
|
|
187
|
+
return field;
|
|
188
|
+
},
|
|
189
|
+
validate: false,
|
|
190
|
+
});
|
|
191
|
+
return usedTypes;
|
|
192
|
+
}
|
|
193
|
+
|
|
83
194
|
export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
84
195
|
const [coreFeatures, joinSpec] = validateSupergraph(supergraph);
|
|
85
196
|
const isFed1 = joinSpec.version.equals(new FeatureVersion(0, 1));
|
|
@@ -88,6 +199,61 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
88
199
|
const [subgraphs, graphEnumNameToSubgraphName] = collectEmptySubgraphs(supergraph, joinSpec);
|
|
89
200
|
const typeDirective = joinSpec.typeDirective(supergraph);
|
|
90
201
|
const implementsDirective = joinSpec.implementsDirective(supergraph);
|
|
202
|
+
const ownerDirective = joinSpec.ownerDirective(supergraph);
|
|
203
|
+
const fieldDirective = joinSpec.fieldDirective(supergraph);
|
|
204
|
+
|
|
205
|
+
const getSubgraph = (application: Directive<any, { graph: string }>) => graphEnumNameToSubgraphName.get(application.arguments().graph);
|
|
206
|
+
|
|
207
|
+
/*
|
|
208
|
+
* Fed2 supergraph have "provenance" information for all types and fields, so we can faithfully extract subgraph relatively easily.
|
|
209
|
+
* For fed1 supergraph however, only entity types are marked with `@join__type` and `@join__field`. Which mean that for value types,
|
|
210
|
+
* we cannot directly know in which subgraphs they were initially defined. One strategy consists in "extracting" value types into
|
|
211
|
+
* all subgraphs blindly: functionally, having some unused types in an extracted subgraph schema does not matter much. However, adding
|
|
212
|
+
* those useless types increases memory usage, and we've seen some case with lots of subgraphs and lots of value types where those
|
|
213
|
+
* unused types balloon up memory usage (from 100MB to 1GB in one example; obviously, this is made worst by the fact that javascript
|
|
214
|
+
* is pretty memory heavy in the first place). So to avoid that problem, for fed1 supergraph, we do a first pass where we collect
|
|
215
|
+
* for all the subgraphs the set of types that are actually reachable in that subgraph. As we extract do the actual type extraction,
|
|
216
|
+
* we use this to ignore non-reachable types for any given subgraph.
|
|
217
|
+
*/
|
|
218
|
+
let includeTypeInSubgraph: (t: NamedType, name: string) => boolean = () => true;
|
|
219
|
+
if (isFed1) {
|
|
220
|
+
const reachableTypesBySubgraph = collectFieldReachableTypesForAllSubgraphs(
|
|
221
|
+
supergraph,
|
|
222
|
+
subgraphs.names(),
|
|
223
|
+
(f, name) => {
|
|
224
|
+
const fieldApplications: Directive<any, { graph: string, requires?: string, provides?: string }>[] = f.appliedDirectivesOf(fieldDirective);
|
|
225
|
+
if (fieldApplications.length) {
|
|
226
|
+
const application = fieldApplications.find((application) => getSubgraph(application) === name);
|
|
227
|
+
if (application) {
|
|
228
|
+
const args = application.arguments();
|
|
229
|
+
const typesInFederationDirectives =
|
|
230
|
+
typesUsedInFederationDirective(args.provides, baseType(f.type!) as CompositeType)
|
|
231
|
+
.concat(typesUsedInFederationDirective(args.requires, f.parent));
|
|
232
|
+
return { isInSubgraph: true, typesInFederationDirectives };
|
|
233
|
+
} else {
|
|
234
|
+
return { isInSubgraph: false, typesInFederationDirectives: [] };
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
// No field application depends on the "owner" directive on the type. If we have no owner, then the
|
|
238
|
+
// field is in all subgraph and we return true. Otherwise, the field is only in the owner subgraph.
|
|
239
|
+
// In any case, the field cannot have a requires or provides
|
|
240
|
+
const ownerApplications = ownerDirective ? f.parent.appliedDirectivesOf(ownerDirective) : [];
|
|
241
|
+
return { isInSubgraph: !ownerApplications.length || getSubgraph(ownerApplications[0]) == name, typesInFederationDirectives: [] };
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
(t, name) => {
|
|
245
|
+
const typeApplications: Directive<any, { graph: string, key?: string}>[] = t.appliedDirectivesOf(typeDirective);
|
|
246
|
+
const application = typeApplications.find((application) => (application.arguments().key && (getSubgraph(application) === name)));
|
|
247
|
+
if (application) {
|
|
248
|
+
const typesInFederationDirectives = typesUsedInFederationDirective(application.arguments().key, t as CompositeType);
|
|
249
|
+
return { isEntityWithKeyInSubgraph: true, typesInFederationDirectives };
|
|
250
|
+
} else {
|
|
251
|
+
return { isEntityWithKeyInSubgraph: false, typesInFederationDirectives: [] };
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
includeTypeInSubgraph = (t, name) => reachableTypesBySubgraph.get(name)?.has(t.name) ?? false;
|
|
256
|
+
}
|
|
91
257
|
|
|
92
258
|
// Next, we iterate on all types and add it to the proper subgraphs (along with any @key).
|
|
93
259
|
// Note that we first add all types empty and populate the types next. This avoids having to care about the iteration
|
|
@@ -95,13 +261,15 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
95
261
|
for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) {
|
|
96
262
|
const typeApplications = type.appliedDirectivesOf(typeDirective);
|
|
97
263
|
if (!typeApplications.length) {
|
|
98
|
-
// Imply the type is
|
|
99
|
-
|
|
100
|
-
|
|
264
|
+
// Imply we don't know in which subgraph the type is, so we had it in all subgraph in which the type is reachable.
|
|
265
|
+
subgraphs
|
|
266
|
+
.values()
|
|
267
|
+
.filter((sg) => includeTypeInSubgraph(type, sg.name))
|
|
268
|
+
.map(sg => sg.schema).forEach(schema => schema.addType(newNamedType(type.kind, type.name)));
|
|
101
269
|
} else {
|
|
102
270
|
for (const application of typeApplications) {
|
|
103
271
|
const args = application.arguments();
|
|
104
|
-
const subgraphName =
|
|
272
|
+
const subgraphName = getSubgraph(application)!;
|
|
105
273
|
const schema = subgraphs.get(subgraphName)!.schema;
|
|
106
274
|
// We can have more than one type directive for a given subgraph
|
|
107
275
|
let subgraphType = schema.type(type.name);
|
|
@@ -119,8 +287,6 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
119
287
|
}
|
|
120
288
|
}
|
|
121
289
|
|
|
122
|
-
const ownerDirective = joinSpec.ownerDirective(supergraph);
|
|
123
|
-
const fieldDirective = joinSpec.fieldDirective(supergraph);
|
|
124
290
|
// We can now populate all those types (with relevant @provides and @requires on fields).
|
|
125
291
|
for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) {
|
|
126
292
|
switch (type.kind) {
|
|
@@ -179,7 +345,14 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
179
345
|
const args = application.arguments();
|
|
180
346
|
const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!;
|
|
181
347
|
const subgraphField = addSubgraphField(field, subgraph, args.type);
|
|
182
|
-
|
|
348
|
+
if (!subgraphField) {
|
|
349
|
+
// It's unlikely but possible that a fed1 supergraph has a `@provides` on a field of a value type,
|
|
350
|
+
// and that value type is actually unreachable. Because we trim unreachable types for fed1 supergraph
|
|
351
|
+
// (see comment on `includeTypeInSubgraph` above), it would mean we get `undefined` here. It's fine
|
|
352
|
+
// however: the type is unreachable in this subgraph, so ignoring that field application is fine too.
|
|
353
|
+
assert(!includeTypeInSubgraph(type, subgraph.name), () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
183
356
|
if (args.requires) {
|
|
184
357
|
subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': args.requires});
|
|
185
358
|
}
|
|
@@ -234,6 +407,7 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
234
407
|
}
|
|
235
408
|
}
|
|
236
409
|
|
|
410
|
+
const allExecutableDirectives = supergraph.directives().filter((def) => def.hasExecutableLocations());
|
|
237
411
|
for (const subgraph of subgraphs) {
|
|
238
412
|
if (isFed1) {
|
|
239
413
|
// The join spec in fed1 was not including external fields. Let's make sure we had them or we'll get validation
|
|
@@ -266,6 +440,23 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
266
440
|
break;
|
|
267
441
|
}
|
|
268
442
|
}
|
|
443
|
+
|
|
444
|
+
// Lastly, we add all the "executable" directives from the supergraph to each subgraphs, as those may be part
|
|
445
|
+
// of a query and end up in any subgraph fetches. We do this "last" to make sure that if one of the directive
|
|
446
|
+
// use a type for an argument, that argument exists.
|
|
447
|
+
// Note that we don't bother with non-executable directives at the moment since we've don't extract their
|
|
448
|
+
// applications. It might become something we need later, but we don't so far.
|
|
449
|
+
for (const definition of allExecutableDirectives) {
|
|
450
|
+
// Note that we skip any potentially applied directives in the argument of the copied definition, because as said
|
|
451
|
+
// in the comment above, we haven't copied type-system directives. And so far, we really don't care about those
|
|
452
|
+
// applications.
|
|
453
|
+
copyDirectiveDefinitionToSchema({
|
|
454
|
+
definition,
|
|
455
|
+
schema: subgraph.schema,
|
|
456
|
+
copyDirectiveApplicationsInArguments: false,
|
|
457
|
+
locationFilter: (loc) => isExecutableDirectiveLocation(loc),
|
|
458
|
+
});
|
|
459
|
+
}
|
|
269
460
|
}
|
|
270
461
|
|
|
271
462
|
// TODO: Not sure that code is needed anymore (any field necessary to validate an interface will have been marked
|
package/src/federation.ts
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
Directive,
|
|
8
8
|
DirectiveDefinition,
|
|
9
9
|
ErrGraphQLValidationFailed,
|
|
10
|
-
errorCauses,
|
|
11
10
|
FieldDefinition,
|
|
12
11
|
InputFieldDefinition,
|
|
13
12
|
InterfaceType,
|
|
@@ -22,6 +21,7 @@ import {
|
|
|
22
21
|
ScalarType,
|
|
23
22
|
Schema,
|
|
24
23
|
SchemaBlueprint,
|
|
24
|
+
SchemaConfig,
|
|
25
25
|
SchemaDefinition,
|
|
26
26
|
SchemaElement,
|
|
27
27
|
sourceASTs,
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
ERRORS,
|
|
55
55
|
withModifiedErrorMessage,
|
|
56
56
|
extractGraphQLErrorOptions,
|
|
57
|
+
errorCauses,
|
|
57
58
|
} from "./error";
|
|
58
59
|
import { computeShareables } from "./precompute";
|
|
59
60
|
import {
|
|
@@ -703,7 +704,9 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
703
704
|
}
|
|
704
705
|
|
|
705
706
|
onDirectiveDefinitionAndSchemaParsed(schema: Schema): GraphQLError[] {
|
|
706
|
-
|
|
707
|
+
const errors = completeSubgraphSchema(schema);
|
|
708
|
+
schema.schemaDefinition.processUnappliedDirectives();
|
|
709
|
+
return errors;
|
|
707
710
|
}
|
|
708
711
|
|
|
709
712
|
onInvalidation(schema: Schema) {
|
|
@@ -878,6 +881,10 @@ export class FederationBlueprint extends SchemaBlueprint {
|
|
|
878
881
|
}
|
|
879
882
|
return error;
|
|
880
883
|
}
|
|
884
|
+
|
|
885
|
+
applyDirectivesAfterParsing() {
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
881
888
|
}
|
|
882
889
|
|
|
883
890
|
function findUnusedNamedForLinkDirective(schema: Schema): string | undefined {
|
|
@@ -1018,8 +1025,8 @@ export function buildSubgraph(
|
|
|
1018
1025
|
return subgraph.validate();
|
|
1019
1026
|
}
|
|
1020
1027
|
|
|
1021
|
-
export function newEmptyFederation2Schema(): Schema {
|
|
1022
|
-
const schema = new Schema(new FederationBlueprint(true));
|
|
1028
|
+
export function newEmptyFederation2Schema(config?: SchemaConfig): Schema {
|
|
1029
|
+
const schema = new Schema(new FederationBlueprint(true), config);
|
|
1023
1030
|
setSchemaAsFed2Subgraph(schema);
|
|
1024
1031
|
return schema;
|
|
1025
1032
|
}
|
package/src/inaccessibleSpec.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
EnumType,
|
|
7
7
|
EnumValue,
|
|
8
8
|
ErrGraphQLAPISchemaValidationFailed,
|
|
9
|
-
executableDirectiveLocations,
|
|
10
9
|
FieldDefinition,
|
|
11
10
|
InputFieldDefinition,
|
|
12
11
|
InputObjectType,
|
|
@@ -17,6 +16,7 @@ import {
|
|
|
17
16
|
isListType,
|
|
18
17
|
isNonNullType,
|
|
19
18
|
isScalarType,
|
|
19
|
+
isTypeSystemDirectiveLocation,
|
|
20
20
|
isVariable,
|
|
21
21
|
NamedType,
|
|
22
22
|
ObjectType,
|
|
@@ -682,11 +682,8 @@ function validateInaccessibleElements(
|
|
|
682
682
|
}
|
|
683
683
|
}
|
|
684
684
|
|
|
685
|
-
const executableDirectiveLocationSet = new Set(executableDirectiveLocations);
|
|
686
685
|
for (const directive of schema.allDirectives()) {
|
|
687
|
-
const typeSystemLocations = directive.locations.filter((loc) =>
|
|
688
|
-
!executableDirectiveLocationSet.has(loc)
|
|
689
|
-
);
|
|
686
|
+
const typeSystemLocations = directive.locations.filter((loc) => isTypeSystemDirectiveLocation(loc));
|
|
690
687
|
if (hasBuiltInName(directive)) {
|
|
691
688
|
// Built-in directives (and their descendants) aren't allowed to be
|
|
692
689
|
// @inaccessible, regardless of shadowing.
|