@apollo/federation-internals 2.0.0-alpha.6 → 2.0.0-preview.2
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 +12 -0
- package/dist/buildSchema.d.ts +7 -3
- package/dist/buildSchema.d.ts.map +1 -1
- package/dist/buildSchema.js +41 -22
- package/dist/buildSchema.js.map +1 -1
- package/dist/coreSpec.d.ts +26 -4
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +86 -25
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +50 -43
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +201 -217
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +38 -0
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -0
- package/dist/directiveAndTypeSpecification.js +196 -0
- package/dist/directiveAndTypeSpecification.js.map +1 -0
- package/dist/error.d.ts +9 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +20 -2
- package/dist/error.js.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +28 -93
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +88 -46
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +706 -228
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +19 -0
- package/dist/federationSpec.d.ts.map +1 -0
- package/dist/federationSpec.js +91 -0
- package/dist/federationSpec.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/joinSpec.d.ts +1 -0
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +1 -0
- package/dist/joinSpec.js.map +1 -1
- package/dist/operations.d.ts +8 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +11 -4
- package/dist/operations.js.map +1 -1
- package/dist/print.d.ts +11 -9
- package/dist/print.d.ts.map +1 -1
- package/dist/print.js +21 -11
- package/dist/print.js.map +1 -1
- package/dist/schemaUpgrader.d.ts +108 -0
- package/dist/schemaUpgrader.d.ts.map +1 -0
- package/dist/schemaUpgrader.js +498 -0
- package/dist/schemaUpgrader.js.map +1 -0
- package/dist/sharing.d.ts +3 -0
- package/dist/sharing.d.ts.map +1 -0
- package/dist/sharing.js +51 -0
- package/dist/sharing.js.map +1 -0
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +2 -3
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +1 -3
- package/dist/tagSpec.js.map +1 -1
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +34 -1
- package/dist/utils.js.map +1 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +9 -4
- package/dist/validate.js.map +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +1 -1
- package/dist/validation/KnownTypeNamesInFederationRule.js +1 -2
- package/dist/validation/KnownTypeNamesInFederationRule.js.map +1 -1
- package/dist/values.d.ts +1 -0
- package/dist/values.d.ts.map +1 -1
- package/dist/values.js +3 -2
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/definitions.test.ts +19 -17
- package/src/__tests__/federation.test.ts +31 -0
- package/src/__tests__/operations.test.ts +2 -3
- package/src/__tests__/schemaUpgrader.test.ts +168 -0
- package/src/__tests__/subgraphValidation.test.ts +5 -19
- package/src/__tests__/values.test.ts +2 -4
- package/src/buildSchema.ts +55 -36
- package/src/coreSpec.ts +112 -31
- package/src/definitions.ts +247 -260
- package/src/directiveAndTypeSpecification.ts +276 -0
- package/src/error.ts +55 -5
- package/src/extractSubgraphsFromSupergraph.ts +34 -118
- package/src/federation.ts +912 -295
- package/src/federationSpec.ts +113 -0
- package/src/index.ts +2 -0
- package/src/joinSpec.ts +2 -1
- package/src/operations.ts +22 -7
- package/src/print.ts +51 -38
- package/src/schemaUpgrader.ts +657 -0
- package/src/sharing.ts +68 -0
- package/src/supergraphs.ts +3 -3
- package/src/tagSpec.ts +1 -3
- package/src/utils.ts +63 -0
- package/src/validate.ts +13 -7
- package/src/validation/KnownTypeNamesInFederationRule.ts +1 -7
- package/src/values.ts +7 -3
- package/tsconfig.test.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { DirectiveLocation, GraphQLError } from "graphql";
|
|
2
|
+
import {
|
|
3
|
+
ArgumentDefinition,
|
|
4
|
+
DirectiveDefinition,
|
|
5
|
+
InputType,
|
|
6
|
+
isNonNullType,
|
|
7
|
+
isObjectType,
|
|
8
|
+
isUnionType,
|
|
9
|
+
NamedType,
|
|
10
|
+
ObjectType,
|
|
11
|
+
OutputType,
|
|
12
|
+
ScalarType,
|
|
13
|
+
Schema,
|
|
14
|
+
UnionType,
|
|
15
|
+
} from "./definitions";
|
|
16
|
+
import { ERRORS } from "./error";
|
|
17
|
+
import { valueEquals, valueToString } from "./values";
|
|
18
|
+
import { sameType } from "./types";
|
|
19
|
+
import { arrayEquals, assert } from "./utils";
|
|
20
|
+
|
|
21
|
+
export type DirectiveSpecification = {
|
|
22
|
+
name: string,
|
|
23
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => GraphQLError[],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type TypeSpecification = {
|
|
27
|
+
name: string,
|
|
28
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => GraphQLError[],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ArgumentSpecification = {
|
|
32
|
+
name: string,
|
|
33
|
+
type: InputType,
|
|
34
|
+
defaultValue?: any,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type FieldSpecification = {
|
|
38
|
+
name: string,
|
|
39
|
+
type: OutputType,
|
|
40
|
+
args?: ArgumentSpecification[],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function createDirectiveSpecification({
|
|
44
|
+
name,
|
|
45
|
+
locations,
|
|
46
|
+
repeatable = false,
|
|
47
|
+
argumentFct = undefined,
|
|
48
|
+
}: {
|
|
49
|
+
name: string,
|
|
50
|
+
locations: DirectiveLocation[],
|
|
51
|
+
repeatable?: boolean,
|
|
52
|
+
argumentFct?: (schema: Schema) => ArgumentSpecification[],
|
|
53
|
+
}): DirectiveSpecification {
|
|
54
|
+
return {
|
|
55
|
+
name,
|
|
56
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
57
|
+
const args = argumentFct ? argumentFct(schema) : [];
|
|
58
|
+
const actualName = nameInSchema ?? name;
|
|
59
|
+
const existing = schema.directive(actualName);
|
|
60
|
+
if (existing) {
|
|
61
|
+
return ensureSameDirectiveStructure({name: actualName, locations, repeatable, args}, existing);
|
|
62
|
+
} else {
|
|
63
|
+
const directive = schema.addDirectiveDefinition(new DirectiveDefinition(actualName, asBuiltIn));
|
|
64
|
+
directive.repeatable = repeatable;
|
|
65
|
+
directive.addLocations(...locations);
|
|
66
|
+
for (const { name, type, defaultValue } of args) {
|
|
67
|
+
directive.addArgument(name, type, defaultValue);
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createScalarTypeSpecification({ name }: { name: string }): TypeSpecification {
|
|
76
|
+
return {
|
|
77
|
+
name,
|
|
78
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
79
|
+
const actualName = nameInSchema ?? name;
|
|
80
|
+
const existing = schema.type(actualName);
|
|
81
|
+
if (existing) {
|
|
82
|
+
return ensureSameTypeKind('ScalarType', existing);
|
|
83
|
+
} else {
|
|
84
|
+
schema.addType(new ScalarType(actualName, asBuiltIn));
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createObjectTypeSpecification({
|
|
92
|
+
name,
|
|
93
|
+
fieldsFct,
|
|
94
|
+
}: {
|
|
95
|
+
name: string,
|
|
96
|
+
fieldsFct: (schema: Schema) => FieldSpecification[],
|
|
97
|
+
}): TypeSpecification {
|
|
98
|
+
return {
|
|
99
|
+
name,
|
|
100
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
101
|
+
const actualName = nameInSchema ?? name;
|
|
102
|
+
const expectedFields = fieldsFct(schema);
|
|
103
|
+
const existing = schema.type(actualName);
|
|
104
|
+
if (existing) {
|
|
105
|
+
let errors = ensureSameTypeKind('ObjectType', existing);
|
|
106
|
+
if (errors.length > 0) {
|
|
107
|
+
return errors;
|
|
108
|
+
}
|
|
109
|
+
assert(isObjectType(existing), 'Should be an object type');
|
|
110
|
+
for (const { name, type, args } of expectedFields) {
|
|
111
|
+
const existingField = existing.field(name);
|
|
112
|
+
if (!existingField) {
|
|
113
|
+
errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
114
|
+
message: `Invalid definition of type ${name}: missing field ${name}`,
|
|
115
|
+
nodes: existing.sourceAST
|
|
116
|
+
}));
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// We allow adding non-nullability because we've seen redefinition of the federation _Service type with type String! for the `sdl` field
|
|
120
|
+
// and we don't want to break backward compatibility as this doesn't feel too harmful.
|
|
121
|
+
let existingType = existingField.type!;
|
|
122
|
+
if (!isNonNullType(type) && isNonNullType(existingType)) {
|
|
123
|
+
existingType = existingType.ofType;
|
|
124
|
+
}
|
|
125
|
+
if (!sameType(type, existingType)) {
|
|
126
|
+
errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
127
|
+
message: `Invalid definition for field ${name} of type ${name}: should have type ${type} but found type ${existingField.type}`,
|
|
128
|
+
nodes: existingField.sourceAST
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
errors = errors.concat(ensureSameArguments(
|
|
132
|
+
{ name, args },
|
|
133
|
+
existingField,
|
|
134
|
+
`field ${existingField.coordinate}`,
|
|
135
|
+
));
|
|
136
|
+
}
|
|
137
|
+
return errors;
|
|
138
|
+
} else {
|
|
139
|
+
const createdType = schema.addType(new ObjectType(actualName, asBuiltIn));
|
|
140
|
+
for (const { name, type, args } of expectedFields) {
|
|
141
|
+
const field = createdType.addField(name, type);
|
|
142
|
+
for (const { name: argName, type: argType, defaultValue } of args ?? []) {
|
|
143
|
+
field.addArgument(argName, argType, defaultValue);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function createUnionTypeSpecification({
|
|
153
|
+
name,
|
|
154
|
+
membersFct,
|
|
155
|
+
}: {
|
|
156
|
+
name: string,
|
|
157
|
+
membersFct: (schema: Schema) => string[],
|
|
158
|
+
}): TypeSpecification {
|
|
159
|
+
return {
|
|
160
|
+
name,
|
|
161
|
+
checkOrAdd: (schema: Schema, nameInSchema?: string, asBuiltIn?: boolean) => {
|
|
162
|
+
const actualName = nameInSchema ?? name;
|
|
163
|
+
const existing = schema.type(actualName);
|
|
164
|
+
const expectedMembers = membersFct(schema).sort((n1, n2) => n1.localeCompare(n2));
|
|
165
|
+
if (expectedMembers.length === 0) {
|
|
166
|
+
if (existing) {
|
|
167
|
+
return [ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
168
|
+
message: `Invalid definition of type ${name}: expected the union type to not exist/have no members but it is defined.`,
|
|
169
|
+
nodes: existing.sourceAST
|
|
170
|
+
})];
|
|
171
|
+
}
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
if (existing) {
|
|
175
|
+
let errors = ensureSameTypeKind('UnionType', existing);
|
|
176
|
+
if (errors.length > 0) {
|
|
177
|
+
return errors;
|
|
178
|
+
}
|
|
179
|
+
assert(isUnionType(existing), 'Should be an union type');
|
|
180
|
+
const actualMembers = existing.members().map(m => m.type.name).sort((n1, n2) => n1.localeCompare(n2));
|
|
181
|
+
// This is kind of fragile in a core schema world where members may have been renamed, but we currently
|
|
182
|
+
// only use this one for the _Entity type where that shouldn't be an issue.
|
|
183
|
+
if (!arrayEquals(expectedMembers, actualMembers)) {
|
|
184
|
+
errors = errors.concat(ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
185
|
+
message: `Invalid definition of type ${name}: expected members [${expectedMembers}] but found [${actualMembers}].`,
|
|
186
|
+
nodes: existing.sourceAST
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
return errors;
|
|
190
|
+
} else {
|
|
191
|
+
const type = schema.addType(new UnionType(actualName, asBuiltIn));
|
|
192
|
+
for (const member of expectedMembers) {
|
|
193
|
+
type.addType(member);
|
|
194
|
+
}
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function ensureSameTypeKind(expected: NamedType['kind'], actual: NamedType): GraphQLError[] {
|
|
202
|
+
return expected === actual.kind
|
|
203
|
+
? []
|
|
204
|
+
: [ERRORS.TYPE_DEFINITION_INVALID.err({
|
|
205
|
+
message: `Invalid definition for type ${actual.name}: ${actual.name} should be a ${expected} but is defined as a ${actual.kind}`,
|
|
206
|
+
nodes: actual.sourceAST
|
|
207
|
+
})];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function ensureSameDirectiveStructure(
|
|
211
|
+
expected: {
|
|
212
|
+
name: string,
|
|
213
|
+
locations: DirectiveLocation[],
|
|
214
|
+
repeatable: boolean,
|
|
215
|
+
args: ArgumentSpecification[]
|
|
216
|
+
},
|
|
217
|
+
actual: DirectiveDefinition<any>,
|
|
218
|
+
): GraphQLError[] {
|
|
219
|
+
let errors = ensureSameArguments(expected, actual, `directive ${expected}`);
|
|
220
|
+
// It's ok to say you'll never repeat a repeatable directive. It's not ok to repeat one that isn't.
|
|
221
|
+
if (!expected.repeatable && actual.repeatable) {
|
|
222
|
+
errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
223
|
+
message: `Invalid definition for directive ${expected}: ${expected} should${expected.repeatable ? "" : " not"} be repeatable`,
|
|
224
|
+
nodes: actual.sourceAST
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
// Similarly, it's ok to say that you will never use a directive in some locations, but not that you will use it in places not allowed by what is expected.
|
|
228
|
+
if (!actual.locations.every(loc => expected.locations.includes(loc))) {
|
|
229
|
+
errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
230
|
+
message: `Invalid efinition for directive ${expected}: ${expected} should have locations ${expected.locations.join(', ')}, but found (non-subset) ${actual.locations.join(', ')}`,
|
|
231
|
+
nodes: actual.sourceAST
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
return errors;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function ensureSameArguments(
|
|
238
|
+
expected: {
|
|
239
|
+
name: string,
|
|
240
|
+
args?: ArgumentSpecification[]
|
|
241
|
+
},
|
|
242
|
+
actual: { argument(name: string): ArgumentDefinition<any> | undefined, arguments(): readonly ArgumentDefinition<any>[] },
|
|
243
|
+
what: string,
|
|
244
|
+
): GraphQLError[] {
|
|
245
|
+
const expectedArguments = expected.args ?? [];
|
|
246
|
+
const foundArguments = actual.arguments();
|
|
247
|
+
if (expectedArguments.length !== foundArguments.length) {
|
|
248
|
+
return [ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
249
|
+
message: `Invalid definition for ${what}: should have ${expectedArguments.length} arguments but ${foundArguments.length} found`,
|
|
250
|
+
})];
|
|
251
|
+
}
|
|
252
|
+
let errors: GraphQLError[] = [];
|
|
253
|
+
for (const { name, type, defaultValue } of expectedArguments) {
|
|
254
|
+
const actualArgument = actual.argument(name)!;
|
|
255
|
+
let actualType = actualArgument.type!;
|
|
256
|
+
if (isNonNullType(actualType) && !isNonNullType(type)) {
|
|
257
|
+
// It's ok to redefine an optional argument as mandatory. For instance, if you want to force people on your team to provide a "deprecation reason", you can
|
|
258
|
+
// redefine @deprecated as `directive @deprecated(reason: String!)...` to get validation. In other words, you are allowed to always pass an argument that
|
|
259
|
+
// is optional if you so wish.
|
|
260
|
+
actualType = actualType.ofType;
|
|
261
|
+
}
|
|
262
|
+
if (!sameType(type, actualType)) {
|
|
263
|
+
errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
264
|
+
message: `Invalid definition of ${what}: ${name} should have type ${type} but found type ${actualArgument.type!}`,
|
|
265
|
+
nodes: actualArgument.sourceAST
|
|
266
|
+
}));
|
|
267
|
+
} else if (!isNonNullType(actualType) && !valueEquals(defaultValue, actualArgument.defaultValue)) {
|
|
268
|
+
errors = errors.concat(ERRORS.DIRECTIVE_DEFINITION_INVALID.err({
|
|
269
|
+
message: `Invalid definition of ${what}: ${name} should have default value ${valueToString(defaultValue)} but found default value ${valueToString(actualArgument.defaultValue)}`,
|
|
270
|
+
nodes: actualArgument.sourceAST
|
|
271
|
+
}));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return errors;
|
|
275
|
+
}
|
|
276
|
+
|
package/src/error.ts
CHANGED
|
@@ -110,10 +110,20 @@ const INVALID_GRAPHQL = makeCodeDefinition(
|
|
|
110
110
|
'A schema is invalid GraphQL: it violates one of the rule of the specification.'
|
|
111
111
|
);
|
|
112
112
|
|
|
113
|
-
const
|
|
114
|
-
'
|
|
115
|
-
'
|
|
116
|
-
{
|
|
113
|
+
const DIRECTIVE_DEFINITION_INVALID = makeCodeDefinition(
|
|
114
|
+
'DIRECTIVE_DEFINITION_INVALID',
|
|
115
|
+
'A built-in or federation directive has an invalid definition in the schema.',
|
|
116
|
+
{ ...DEFAULT_METADATA, replaces: ['TAG_DEFINITION_INVALID'] },
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const TYPE_DEFINITION_INVALID = makeCodeDefinition(
|
|
120
|
+
'TYPE_DEFINITION_INVALID',
|
|
121
|
+
'A built-in or federation type has an invalid definition in the schema.',
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const UNKNOWN_FEDERATION_LINK_VERSION = makeCodeDefinition(
|
|
125
|
+
'UNKNOWN_FEDERATION_LINK_VERSION',
|
|
126
|
+
'The version of federation in a @link directive on the schema is unknown.',
|
|
117
127
|
);
|
|
118
128
|
|
|
119
129
|
const FIELDS_HAS_ARGS = makeFederationDirectiveErrorCodeCategory(
|
|
@@ -149,6 +159,13 @@ const EXTERNAL_UNUSED = makeCodeDefinition(
|
|
|
149
159
|
{ addedIn: FED1_CODE },
|
|
150
160
|
);
|
|
151
161
|
|
|
162
|
+
const TYPE_WITH_ONLY_UNUSED_EXTERNAL = makeCodeDefinition(
|
|
163
|
+
'TYPE_WITH_ONLY_UNUSED_EXTERNAL',
|
|
164
|
+
'A federation 1 schema has a composite type comprised only of unused external fields.'
|
|
165
|
+
+ ` Note that this error can _only_ be raised for federation 1 schema as federation 2 schema do not allow unused external fields (and errors with code ${EXTERNAL_UNUSED.code} will be raised in that case).`
|
|
166
|
+
+ ' But when federation 1 schema are automatically migrated to federation 2 ones, unused external fields are automaticaly removed, and in rare case this can leave a type empty. If that happens, an error with this code will be raised',
|
|
167
|
+
);
|
|
168
|
+
|
|
152
169
|
const PROVIDES_ON_NON_OBJECT_FIELD = makeCodeDefinition(
|
|
153
170
|
'PROVIDES_ON_NON_OBJECT_FIELD',
|
|
154
171
|
'A `@provides` directive is used to mark a field whose base type is not an object type.'
|
|
@@ -230,6 +247,11 @@ const EXTERNAL_ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
|
|
|
230
247
|
'An `@external` field declares an argument with a default that is incompatible with the corresponding argument in the declaration(s) of that field in other subgtaphs.',
|
|
231
248
|
);
|
|
232
249
|
|
|
250
|
+
const EXTERNAL_ON_INTERFACE = makeCodeDefinition(
|
|
251
|
+
'EXTERNAL_ON_INTERFACE',
|
|
252
|
+
'The field of an interface type is marked with `@external`: as external is about marking field not resolved by the subgraph and as interface field are not resolved (only implementations of those fields are), an "external" interface field is nonsensical',
|
|
253
|
+
);
|
|
254
|
+
|
|
233
255
|
const FIELD_TYPE_MISMATCH = makeCodeDefinition(
|
|
234
256
|
'FIELD_TYPE_MISMATCH',
|
|
235
257
|
'A field has a type that is incompatible with other declarations of that field in other subgraphs.',
|
|
@@ -252,6 +274,11 @@ const ARGUMENT_DEFAULT_MISMATCH = makeCodeDefinition(
|
|
|
252
274
|
'An argument (of a field/directive) has a default value that is incompatible with that of other declarations of that same argument in other subgraphs.',
|
|
253
275
|
);
|
|
254
276
|
|
|
277
|
+
const NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH = makeCodeDefinition(
|
|
278
|
+
'NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH',
|
|
279
|
+
'A non-repeatable directive is applied to a schema element in different subgraphs but with arguments that are different.',
|
|
280
|
+
);
|
|
281
|
+
|
|
255
282
|
const EXTENSION_WITH_NO_BASE = makeCodeDefinition(
|
|
256
283
|
'EXTENSION_WITH_NO_BASE',
|
|
257
284
|
'A subgraph is attempting to `extend` a type that is not originally defined in any known subgraph.',
|
|
@@ -269,6 +296,21 @@ const INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH = makeCodeDefinition(
|
|
|
269
296
|
'For an interface field, some of its concrete implementations have @external or @requires and there is difference in those implementations return type (which is currently not supported; see https://github.com/apollographql/federation/issues/1257)'
|
|
270
297
|
);
|
|
271
298
|
|
|
299
|
+
const INVALID_FIELD_SHARING = makeCodeDefinition(
|
|
300
|
+
'INVALID_FIELD_SHARING',
|
|
301
|
+
'A field that is non-shareable in at least one subgraph is resolved by multiple subgraphs.'
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const INVALID_LINK_DIRECTIVE_USAGE = makeCodeDefinition(
|
|
305
|
+
'INVALID_LINK_DIRECTIVE_USAGE',
|
|
306
|
+
'An application of the @link directive is invalid/does not respect the specification.'
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
const REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH = makeCodeDefinition(
|
|
310
|
+
'REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH',
|
|
311
|
+
'An argument of a field or directive definition is mandatory in some subgraphs, but the argument is not defined in all subgraphs that define the field or directive definition.'
|
|
312
|
+
);
|
|
313
|
+
|
|
272
314
|
const SATISFIABILITY_ERROR = makeCodeDefinition(
|
|
273
315
|
'SATISFIABILITY_ERROR',
|
|
274
316
|
'Subgraphs can be merged, but the resulting supergraph API would have queries that cannot be satisfied by those subgraphs.',
|
|
@@ -285,7 +327,9 @@ export const ERROR_CATEGORIES = {
|
|
|
285
327
|
|
|
286
328
|
export const ERRORS = {
|
|
287
329
|
INVALID_GRAPHQL,
|
|
288
|
-
|
|
330
|
+
DIRECTIVE_DEFINITION_INVALID,
|
|
331
|
+
TYPE_DEFINITION_INVALID,
|
|
332
|
+
UNKNOWN_FEDERATION_LINK_VERSION,
|
|
289
333
|
KEY_FIELDS_HAS_ARGS,
|
|
290
334
|
PROVIDES_FIELDS_HAS_ARGS,
|
|
291
335
|
REQUIRES_FIELDS_HAS_ARGS,
|
|
@@ -295,6 +339,7 @@ export const ERRORS = {
|
|
|
295
339
|
PROVIDES_UNSUPPORTED_ON_INTERFACE,
|
|
296
340
|
REQUIRES_UNSUPPORTED_ON_INTERFACE,
|
|
297
341
|
EXTERNAL_UNUSED,
|
|
342
|
+
TYPE_WITH_ONLY_UNUSED_EXTERNAL,
|
|
298
343
|
PROVIDES_ON_NON_OBJECT_FIELD,
|
|
299
344
|
KEY_INVALID_FIELDS_TYPE,
|
|
300
345
|
PROVIDES_INVALID_FIELDS_TYPE,
|
|
@@ -314,13 +359,18 @@ export const ERRORS = {
|
|
|
314
359
|
EXTERNAL_ARGUMENT_MISSING,
|
|
315
360
|
EXTERNAL_ARGUMENT_TYPE_MISMATCH,
|
|
316
361
|
EXTERNAL_ARGUMENT_DEFAULT_MISMATCH,
|
|
362
|
+
EXTERNAL_ON_INTERFACE,
|
|
317
363
|
FIELD_TYPE_MISMATCH,
|
|
318
364
|
ARGUMENT_TYPE_MISMATCH,
|
|
319
365
|
INPUT_FIELD_DEFAULT_MISMATCH,
|
|
320
366
|
ARGUMENT_DEFAULT_MISMATCH,
|
|
367
|
+
NON_REPEATABLE_DIRECTIVE_ARGUMENTS_MISMATCH,
|
|
321
368
|
EXTENSION_WITH_NO_BASE,
|
|
322
369
|
EXTERNAL_MISSING_ON_BASE,
|
|
323
370
|
INTERFACE_FIELD_IMPLEM_TYPE_MISMATCH,
|
|
371
|
+
INVALID_FIELD_SHARING,
|
|
372
|
+
INVALID_LINK_DIRECTIVE_USAGE,
|
|
373
|
+
REQUIRED_ARGUMENT_MISSING_IN_SOME_SUBGRAPH,
|
|
324
374
|
SATISFIABILITY_ERROR,
|
|
325
375
|
};
|
|
326
376
|
|
|
@@ -19,20 +19,23 @@ import {
|
|
|
19
19
|
Schema,
|
|
20
20
|
Type,
|
|
21
21
|
} from "./definitions";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
newEmptyFederation2Schema,
|
|
24
|
+
parseFieldSetArgument,
|
|
25
|
+
removeInactiveProvidesAndRequires,
|
|
26
|
+
} from "./federation";
|
|
23
27
|
import { CoreSpecDefinition, FeatureVersion } from "./coreSpec";
|
|
24
28
|
import { JoinSpecDefinition } from "./joinSpec";
|
|
25
|
-
import { Subgraph, Subgraphs } from "./federation";
|
|
29
|
+
import { FederationMetadata, Subgraph, Subgraphs } from "./federation";
|
|
26
30
|
import { assert } from "./utils";
|
|
27
31
|
import { validateSupergraph } from "./supergraphs";
|
|
28
32
|
import { builtTypeReference } from "./buildSchema";
|
|
29
|
-
import { GraphQLError } from "graphql";
|
|
30
|
-
import { selectionOfElement, SelectionSet } from "./operations";
|
|
31
33
|
import { isSubtype } from "./types";
|
|
32
34
|
import { printSchema } from "./print";
|
|
33
35
|
import fs from 'fs';
|
|
34
36
|
import path from 'path';
|
|
35
37
|
import { validateStringContainsBoolean } from "./utils";
|
|
38
|
+
import { errorCauses, printErrors } from ".";
|
|
36
39
|
|
|
37
40
|
function filteredTypes(
|
|
38
41
|
supergraph: Schema,
|
|
@@ -62,7 +65,7 @@ function collectEmptySubgraphs(supergraph: Schema, joinSpec: JoinSpecDefinition)
|
|
|
62
65
|
throw new Error(`Value ${value} of join__Graph enum has no @join__graph directive`);
|
|
63
66
|
}
|
|
64
67
|
const info = graphApplications[0].arguments();
|
|
65
|
-
const subgraph = new Subgraph(info.name, info.url,
|
|
68
|
+
const subgraph = new Subgraph(info.name, info.url, newEmptyFederation2Schema());
|
|
66
69
|
subgraphs.add(subgraph);
|
|
67
70
|
graphEnumNameToSubgraphName.set(value.name, info.name);
|
|
68
71
|
}
|
|
@@ -98,7 +101,8 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
98
101
|
subgraphType = schema.addType(newNamedType(type.kind, type.name));
|
|
99
102
|
}
|
|
100
103
|
if (args.key) {
|
|
101
|
-
const
|
|
104
|
+
const { resolvable } = args;
|
|
105
|
+
const directive = subgraphType.applyDirective('key', {'fields': args.key, resolvable});
|
|
102
106
|
if (args.extension) {
|
|
103
107
|
directive.setOfExtension(subgraphType.newExtension());
|
|
104
108
|
}
|
|
@@ -169,13 +173,13 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
169
173
|
const subgraphField = addSubgraphField(field, subgraph, args.type);
|
|
170
174
|
assert(subgraphField, () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`);
|
|
171
175
|
if (args.requires) {
|
|
172
|
-
subgraphField.applyDirective(
|
|
176
|
+
subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': args.requires});
|
|
173
177
|
}
|
|
174
178
|
if (args.provides) {
|
|
175
|
-
subgraphField.applyDirective(
|
|
179
|
+
subgraphField.applyDirective(subgraph.metadata().providesDirective(), {'fields': args.provides});
|
|
176
180
|
}
|
|
177
181
|
if (args.external) {
|
|
178
|
-
subgraphField.applyDirective(
|
|
182
|
+
subgraphField.applyDirective(subgraph.metadata().externalDirective());
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
}
|
|
@@ -222,7 +226,7 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
222
226
|
// errors later.
|
|
223
227
|
addExternalFields(subgraph, supergraph, isFed1);
|
|
224
228
|
}
|
|
225
|
-
|
|
229
|
+
removeInactiveProvidesAndRequires(subgraph.schema);
|
|
226
230
|
|
|
227
231
|
// We now do an additional path on all types because we sometimes added types to subgraphs without
|
|
228
232
|
// being sure that the subgraph had the type in the first place (especially with the 0.1 join spec), and because
|
|
@@ -278,7 +282,7 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
278
282
|
// all entities in all subgraphs are reachable from a query and so are properly included in the "query graph" later).
|
|
279
283
|
for (const subgraph of subgraphs) {
|
|
280
284
|
try {
|
|
281
|
-
subgraph.
|
|
285
|
+
subgraph.validate();
|
|
282
286
|
} catch (e) {
|
|
283
287
|
// There is 2 reasons this could happen:
|
|
284
288
|
// 1. if the subgraph is a Fed1 one, because fed2 has stricter validation than fed1, this could be due to the supergraph having been generated by fed1 and
|
|
@@ -290,11 +294,11 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
290
294
|
// it'll solve the issue and that's good, or we'll hit the other message anyway.
|
|
291
295
|
const msg = `Error extracting subgraph ${subgraph.name} from the supergraph: this might due to errors in subgraphs that were mistakenly ignored by federation 0.x versions but are rejected by federation 2.\n`
|
|
292
296
|
+ 'Please try composing your subgraphs with federation 2: this should help precisely pinpoint the errors and generate a correct federation 2 supergraph.';
|
|
293
|
-
throw new Error(`${msg}.\n\nDetails:\n${errorToString(e
|
|
297
|
+
throw new Error(`${msg}.\n\nDetails:\n${errorToString(e)}`);
|
|
294
298
|
} else {
|
|
295
299
|
const msg = `Unexpected error extracting subgraph ${subgraph.name} from the supergraph: this is either a bug, or the supergraph has been corrupted.`;
|
|
296
300
|
const dumpMsg = maybeDumpSubgraphSchema(subgraph);
|
|
297
|
-
throw new Error(`${msg}.\n\nDetails:\n${errorToString(e
|
|
301
|
+
throw new Error(`${msg}.\n\nDetails:\n${errorToString(e)}\n\n${dumpMsg}`);
|
|
298
302
|
}
|
|
299
303
|
}
|
|
300
304
|
}
|
|
@@ -320,12 +324,13 @@ function maybeDumpSubgraphSchema(subgraph: Subgraph): string {
|
|
|
320
324
|
return `The (invalid) extracted subgraph has been written in: ${file}.`;
|
|
321
325
|
}
|
|
322
326
|
catch (e2) {
|
|
323
|
-
return `Was not able to print generated subgraph because: ${errorToString(e2
|
|
327
|
+
return `Was not able to print generated subgraph for "${subgraph.name}" because: ${errorToString(e2)}`;
|
|
324
328
|
}
|
|
325
329
|
}
|
|
326
330
|
|
|
327
|
-
function errorToString(e: any,
|
|
328
|
-
|
|
331
|
+
function errorToString(e: any,): string {
|
|
332
|
+
const causes = errorCauses(e);
|
|
333
|
+
return causes ? printErrors(causes) : String(e);
|
|
329
334
|
}
|
|
330
335
|
|
|
331
336
|
type AnyField = FieldDefinition<ObjectType | InterfaceType> | InputFieldDefinition;
|
|
@@ -396,13 +401,14 @@ function copyType(type: Type, subgraph: Schema, subgraphName: string): Type {
|
|
|
396
401
|
}
|
|
397
402
|
|
|
398
403
|
function addExternalFields(subgraph: Subgraph, supergraph: Schema, isFed1: boolean) {
|
|
404
|
+
const metadata = subgraph.metadata();
|
|
399
405
|
for (const type of subgraph.schema.types()) {
|
|
400
406
|
if (!isObjectType(type) && !isInterfaceType(type)) {
|
|
401
407
|
continue;
|
|
402
408
|
}
|
|
403
409
|
|
|
404
410
|
// First, handle @key
|
|
405
|
-
for (const keyApplication of type.appliedDirectivesOf(
|
|
411
|
+
for (const keyApplication of type.appliedDirectivesOf(metadata.keyDirective())) {
|
|
406
412
|
// Historically, the federation code for keys, when applied _to a type extension_:
|
|
407
413
|
// 1) required @external on any field of the key
|
|
408
414
|
// 2) but required the subgraph to resolve any field of that key
|
|
@@ -428,18 +434,18 @@ function addExternalFields(subgraph: Subgraph, supergraph: Schema, isFed1: boole
|
|
|
428
434
|
}
|
|
429
435
|
// Then any @requires or @provides on fields
|
|
430
436
|
for (const field of type.fields()) {
|
|
431
|
-
for (const requiresApplication of field.appliedDirectivesOf(
|
|
437
|
+
for (const requiresApplication of field.appliedDirectivesOf(metadata.requiresDirective())) {
|
|
432
438
|
addExternalFieldsFromDirectiveFieldSet(subgraph, type, requiresApplication, supergraph);
|
|
433
439
|
}
|
|
434
440
|
const fieldBaseType = baseType(field.type!);
|
|
435
|
-
for (const providesApplication of field.appliedDirectivesOf(
|
|
441
|
+
for (const providesApplication of field.appliedDirectivesOf(metadata.providesDirective())) {
|
|
436
442
|
assert(isObjectType(fieldBaseType) || isInterfaceType(fieldBaseType), () => `Found @provides on field ${field.coordinate} whose type ${field.type!} (${fieldBaseType.kind}) is not an object or interface `);
|
|
437
443
|
addExternalFieldsFromDirectiveFieldSet(subgraph, fieldBaseType, providesApplication, supergraph);
|
|
438
444
|
}
|
|
439
445
|
}
|
|
440
446
|
|
|
441
447
|
// And then any constraint due to implemented interfaces.
|
|
442
|
-
addExternalFieldsFromInterface(type);
|
|
448
|
+
addExternalFieldsFromInterface(metadata, type);
|
|
443
449
|
}
|
|
444
450
|
}
|
|
445
451
|
|
|
@@ -450,9 +456,9 @@ function addExternalFieldsFromDirectiveFieldSet(
|
|
|
450
456
|
supergraph: Schema,
|
|
451
457
|
forceNonExternal: boolean = false,
|
|
452
458
|
) {
|
|
453
|
-
const external =
|
|
459
|
+
const external = subgraph.metadata().externalDirective();
|
|
454
460
|
|
|
455
|
-
const
|
|
461
|
+
const fieldAccessor = function (type: CompositeType, fieldName: string): FieldDefinition<any> {
|
|
456
462
|
const field = type.field(fieldName);
|
|
457
463
|
if (field) {
|
|
458
464
|
if (forceNonExternal && field.hasAppliedDirective(external)) {
|
|
@@ -473,16 +479,16 @@ function addExternalFieldsFromDirectiveFieldSet(
|
|
|
473
479
|
}
|
|
474
480
|
return created;
|
|
475
481
|
};
|
|
476
|
-
parseFieldSetArgument(parentType, directive,
|
|
482
|
+
parseFieldSetArgument({parentType, directive, fieldAccessor});
|
|
477
483
|
}
|
|
478
484
|
|
|
479
|
-
function addExternalFieldsFromInterface(type: ObjectType | InterfaceType) {
|
|
485
|
+
function addExternalFieldsFromInterface(metadata: FederationMetadata, type: ObjectType | InterfaceType) {
|
|
480
486
|
for (const itf of type.interfaces()) {
|
|
481
487
|
for (const field of itf.fields()) {
|
|
482
488
|
const typeField = type.field(field.name);
|
|
483
489
|
if (!typeField) {
|
|
484
|
-
copyFieldAsExternal(field, type);
|
|
485
|
-
} else if (typeField.hasAppliedDirective(
|
|
490
|
+
copyFieldAsExternal(metadata, field, type);
|
|
491
|
+
} else if (typeField.hasAppliedDirective(metadata.externalDirective())) {
|
|
486
492
|
// A subtlety here is that a type may implements multiple interfaces providing a given field, and the field may
|
|
487
493
|
// not have the exact same definition in all interface. So if we may have added the field in a previous loop
|
|
488
494
|
// iteration, we need to check if we shouldn't update the field type.
|
|
@@ -492,12 +498,12 @@ function addExternalFieldsFromInterface(type: ObjectType | InterfaceType) {
|
|
|
492
498
|
}
|
|
493
499
|
}
|
|
494
500
|
|
|
495
|
-
function copyFieldAsExternal(field: FieldDefinition<InterfaceType>, type: ObjectType | InterfaceType) {
|
|
501
|
+
function copyFieldAsExternal(metadata: FederationMetadata, field: FieldDefinition<InterfaceType>, type: ObjectType | InterfaceType) {
|
|
496
502
|
const newField = type.addField(field.name, field.type);
|
|
497
503
|
for (const arg of field.arguments()) {
|
|
498
504
|
newField.addArgument(arg.name, arg.type, arg.defaultValue);
|
|
499
505
|
}
|
|
500
|
-
newField.applyDirective(
|
|
506
|
+
newField.applyDirective(metadata.externalDirective());
|
|
501
507
|
}
|
|
502
508
|
|
|
503
509
|
function maybeUpdateFieldForInterface(toModify: FieldDefinition<ObjectType | InterfaceType>, itfField: FieldDefinition<InterfaceType>) {
|
|
@@ -508,93 +514,3 @@ function maybeUpdateFieldForInterface(toModify: FieldDefinition<ObjectType | Int
|
|
|
508
514
|
toModify.type = itfField.type!;
|
|
509
515
|
}
|
|
510
516
|
}
|
|
511
|
-
|
|
512
|
-
/*
|
|
513
|
-
*
|
|
514
|
-
* It makes no sense to have a @provides on a non-external leaf field, and we usually reject it during schema
|
|
515
|
-
* validation but we may still have some when:
|
|
516
|
-
* 1. we get a fed 1 supergraph, where such validation hadn't been run.
|
|
517
|
-
* 2. in the special case of key fields of type extensions that are marked @external without being so (see details in
|
|
518
|
-
* `addExternalFields`). In that case, the validation will not have rejected it.
|
|
519
|
-
*
|
|
520
|
-
* This method checks for those cases and removes such fields (and often the whole @provides). The reason we do
|
|
521
|
-
* it is that such provides have a negative impact on later query planning, because it sometimes make us to
|
|
522
|
-
* try type-exploding some interfaces unnecessarily.
|
|
523
|
-
*/
|
|
524
|
-
function removeNeedlessProvides(subgraph: Subgraph) {
|
|
525
|
-
for (const type of subgraph.schema.types()) {
|
|
526
|
-
if (!isObjectType(type) && !isInterfaceType(type)) {
|
|
527
|
-
continue;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const providesDirective = federationBuiltIns.providesDirective(subgraph.schema);
|
|
531
|
-
for (const field of type.fields()) {
|
|
532
|
-
const fieldBaseType = baseType(field.type!);
|
|
533
|
-
for (const providesApplication of field.appliedDirectivesOf(providesDirective)) {
|
|
534
|
-
const selection = parseFieldSetArgument(fieldBaseType as CompositeType, providesApplication);
|
|
535
|
-
if (selectsNonExternalLeafField(selection)) {
|
|
536
|
-
providesApplication.remove();
|
|
537
|
-
const updated = withoutNonExternalLeafFields(selection);
|
|
538
|
-
if (!updated.isEmpty()) {
|
|
539
|
-
field.applyDirective(providesDirective, { fields: updated.toString(true, false) });
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function isExternalOrHasExternalImplementations(field: FieldDefinition<CompositeType>): boolean {
|
|
548
|
-
if (field.hasAppliedDirective(externalDirectiveName)) {
|
|
549
|
-
return true;
|
|
550
|
-
}
|
|
551
|
-
const parentType = field.parent;
|
|
552
|
-
if (isInterfaceType(parentType)) {
|
|
553
|
-
for (const implem of parentType.possibleRuntimeTypes()) {
|
|
554
|
-
const fieldInImplem = implem.field(field.name);
|
|
555
|
-
if (fieldInImplem && fieldInImplem.hasAppliedDirective(externalDirectiveName)) {
|
|
556
|
-
return true;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
return false;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function selectsNonExternalLeafField(selection: SelectionSet): boolean {
|
|
564
|
-
return selection.selections().some(s => {
|
|
565
|
-
if (s.kind === 'FieldSelection') {
|
|
566
|
-
// If it's external, we're good and don't need to recurse.
|
|
567
|
-
if (isExternalOrHasExternalImplementations(s.field.definition)) {
|
|
568
|
-
return false;
|
|
569
|
-
}
|
|
570
|
-
// Otherwise, we select a non-external if it's a leaf, or the sub-selection does.
|
|
571
|
-
return !s.selectionSet || selectsNonExternalLeafField(s.selectionSet);
|
|
572
|
-
} else {
|
|
573
|
-
return selectsNonExternalLeafField(s.selectionSet);
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
function withoutNonExternalLeafFields(selectionSet: SelectionSet): SelectionSet {
|
|
579
|
-
const newSelectionSet = new SelectionSet(selectionSet.parentType);
|
|
580
|
-
for (const selection of selectionSet.selections()) {
|
|
581
|
-
if (selection.kind === 'FieldSelection') {
|
|
582
|
-
if (isExternalOrHasExternalImplementations(selection.field.definition)) {
|
|
583
|
-
// That field is external, so we can add the selection back entirely.
|
|
584
|
-
newSelectionSet.add(selection);
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
// Note that for fragments will always be true (and we just recurse), while
|
|
589
|
-
// for fields, we'll only get here if the field is not external, and so
|
|
590
|
-
// we want to add the selection only if it's not a leaf and even then, only
|
|
591
|
-
// the part where we've recursed.
|
|
592
|
-
if (selection.selectionSet) {
|
|
593
|
-
const updated = withoutNonExternalLeafFields(selection.selectionSet);
|
|
594
|
-
if (!updated.isEmpty()) {
|
|
595
|
-
newSelectionSet.add(selectionOfElement(selection.element(), updated));
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
return newSelectionSet;
|
|
600
|
-
}
|