@apollo/federation-internals 2.4.9 → 2.5.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/dist/argumentCompositionStrategies.d.ts +12 -7
- package/dist/argumentCompositionStrategies.d.ts.map +1 -1
- package/dist/argumentCompositionStrategies.js +26 -7
- package/dist/argumentCompositionStrategies.js.map +1 -1
- package/dist/authenticatedSpec.d.ts +13 -0
- package/dist/authenticatedSpec.d.ts.map +1 -0
- package/dist/authenticatedSpec.js +36 -0
- package/dist/authenticatedSpec.js.map +1 -0
- package/dist/coreSpec.d.ts +6 -5
- package/dist/coreSpec.d.ts.map +1 -1
- package/dist/coreSpec.js +42 -32
- package/dist/coreSpec.js.map +1 -1
- package/dist/definitions.d.ts +2 -3
- package/dist/definitions.d.ts.map +1 -1
- package/dist/definitions.js +12 -7
- package/dist/definitions.js.map +1 -1
- package/dist/directiveAndTypeSpecification.d.ts +8 -8
- package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
- package/dist/directiveAndTypeSpecification.js +21 -16
- package/dist/directiveAndTypeSpecification.js.map +1 -1
- package/dist/error.d.ts +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts +1 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +450 -295
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/federation.d.ts +10 -5
- package/dist/federation.d.ts.map +1 -1
- package/dist/federation.js +58 -16
- package/dist/federation.js.map +1 -1
- package/dist/federationSpec.d.ts +3 -1
- package/dist/federationSpec.d.ts.map +1 -1
- package/dist/federationSpec.js +12 -3
- package/dist/federationSpec.js.map +1 -1
- package/dist/inaccessibleSpec.d.ts +1 -1
- package/dist/inaccessibleSpec.d.ts.map +1 -1
- package/dist/inaccessibleSpec.js +4 -4
- package/dist/inaccessibleSpec.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/joinSpec.d.ts +19 -17
- package/dist/joinSpec.d.ts.map +1 -1
- package/dist/joinSpec.js +3 -3
- package/dist/joinSpec.js.map +1 -1
- package/dist/operations.d.ts +9 -43
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +44 -97
- package/dist/operations.js.map +1 -1
- package/dist/requiresScopesSpec.d.ts +16 -0
- package/dist/requiresScopesSpec.d.ts.map +1 -0
- package/dist/requiresScopesSpec.js +55 -0
- package/dist/requiresScopesSpec.js.map +1 -0
- package/dist/supergraphs.d.ts +19 -4
- package/dist/supergraphs.d.ts.map +1 -1
- package/dist/supergraphs.js +40 -14
- package/dist/supergraphs.js.map +1 -1
- package/dist/tagSpec.d.ts +1 -1
- package/dist/tagSpec.d.ts.map +1 -1
- package/dist/tagSpec.js +4 -4
- package/dist/tagSpec.js.map +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +11 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/argumentCompositionStrategies.ts +32 -9
- package/src/authenticatedSpec.ts +62 -0
- package/src/coreSpec.ts +57 -35
- package/src/definitions.ts +22 -9
- package/src/directiveAndTypeSpecification.ts +25 -24
- package/src/error.ts +2 -2
- package/src/extractSubgraphsFromSupergraph.ts +647 -393
- package/src/federation.ts +95 -16
- package/src/federationSpec.ts +13 -5
- package/src/inaccessibleSpec.ts +4 -4
- package/src/index.ts +2 -1
- package/src/joinSpec.ts +23 -13
- package/src/operations.ts +62 -182
- package/src/precompute.ts +1 -1
- package/src/requiresScopesSpec.ts +76 -0
- package/src/supergraphs.ts +64 -16
- package/src/tagSpec.ts +4 -4
- package/src/utils.ts +10 -0
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
ObjectType,
|
|
21
21
|
Schema,
|
|
22
22
|
Type,
|
|
23
|
+
EnumType,
|
|
24
|
+
UnionType,
|
|
23
25
|
} from "./definitions";
|
|
24
26
|
import {
|
|
25
27
|
newEmptyFederation2Schema,
|
|
@@ -27,7 +29,7 @@ import {
|
|
|
27
29
|
removeInactiveProvidesAndRequires,
|
|
28
30
|
} from "./federation";
|
|
29
31
|
import { CoreSpecDefinition, FeatureVersion } from "./coreSpec";
|
|
30
|
-
import { JoinSpecDefinition } from "./joinSpec";
|
|
32
|
+
import { JoinFieldDirectiveArguments, JoinSpecDefinition, JoinTypeDirectiveArguments } from "./joinSpec";
|
|
31
33
|
import { FederationMetadata, Subgraph, Subgraphs } from "./federation";
|
|
32
34
|
import { assert } from "./utils";
|
|
33
35
|
import { validateSupergraph } from "./supergraphs";
|
|
@@ -191,376 +193,51 @@ function typesUsedInFederationDirective(fieldSet: string | undefined, parentType
|
|
|
191
193
|
return usedTypes;
|
|
192
194
|
}
|
|
193
195
|
|
|
194
|
-
export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
196
|
+
export function extractSubgraphsFromSupergraph(supergraph: Schema, validateExtractedSubgraphs: boolean = true): Subgraphs {
|
|
195
197
|
const [coreFeatures, joinSpec] = validateSupergraph(supergraph);
|
|
196
198
|
const isFed1 = joinSpec.version.equals(new FeatureVersion(0, 1));
|
|
197
199
|
try {
|
|
198
200
|
// We first collect the subgraphs (creating an empty schema that we'll populate next for each).
|
|
199
201
|
const [subgraphs, graphEnumNameToSubgraphName] = collectEmptySubgraphs(supergraph, joinSpec);
|
|
200
|
-
const typeDirective = joinSpec.typeDirective(supergraph);
|
|
201
|
-
const implementsDirective = joinSpec.implementsDirective(supergraph);
|
|
202
|
-
const ownerDirective = joinSpec.ownerDirective(supergraph);
|
|
203
|
-
const fieldDirective = joinSpec.fieldDirective(supergraph);
|
|
204
|
-
const unionMemberDirective = joinSpec.unionMemberDirective(supergraph);
|
|
205
|
-
const enumValueDirective = joinSpec.enumValueDirective(supergraph);
|
|
206
|
-
|
|
207
|
-
const getSubgraph = (application: Directive<any, { graph?: string }>) => {
|
|
208
|
-
const graph = application.arguments().graph;
|
|
209
|
-
return graph ? graphEnumNameToSubgraphName.get(graph) : undefined;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
/*
|
|
213
|
-
* Fed2 supergraph have "provenance" information for all types and fields, so we can faithfully extract subgraph relatively easily.
|
|
214
|
-
* For fed1 supergraph however, only entity types are marked with `@join__type` and `@join__field`. Which mean that for value types,
|
|
215
|
-
* we cannot directly know in which subgraphs they were initially defined. One strategy consists in "extracting" value types into
|
|
216
|
-
* all subgraphs blindly: functionally, having some unused types in an extracted subgraph schema does not matter much. However, adding
|
|
217
|
-
* those useless types increases memory usage, and we've seen some case with lots of subgraphs and lots of value types where those
|
|
218
|
-
* unused types balloon up memory usage (from 100MB to 1GB in one example; obviously, this is made worst by the fact that javascript
|
|
219
|
-
* is pretty memory heavy in the first place). So to avoid that problem, for fed1 supergraph, we do a first pass where we collect
|
|
220
|
-
* for all the subgraphs the set of types that are actually reachable in that subgraph. As we extract do the actual type extraction,
|
|
221
|
-
* we use this to ignore non-reachable types for any given subgraph.
|
|
222
|
-
*/
|
|
223
|
-
let includeTypeInSubgraph: (t: NamedType, name: string) => boolean = () => true;
|
|
224
|
-
if (isFed1) {
|
|
225
|
-
const reachableTypesBySubgraph = collectFieldReachableTypesForAllSubgraphs(
|
|
226
|
-
supergraph,
|
|
227
|
-
subgraphs.names(),
|
|
228
|
-
(f, name) => {
|
|
229
|
-
const fieldApplications: Directive<any, { graph?: string, requires?: string, provides?: string }>[] = f.appliedDirectivesOf(fieldDirective);
|
|
230
|
-
if (fieldApplications.length) {
|
|
231
|
-
const application = fieldApplications.find((application) => getSubgraph(application) === name);
|
|
232
|
-
if (application) {
|
|
233
|
-
const args = application.arguments();
|
|
234
|
-
const typesInFederationDirectives =
|
|
235
|
-
typesUsedInFederationDirective(args.provides, baseType(f.type!) as CompositeType)
|
|
236
|
-
.concat(typesUsedInFederationDirective(args.requires, f.parent));
|
|
237
|
-
return { isInSubgraph: true, typesInFederationDirectives };
|
|
238
|
-
} else {
|
|
239
|
-
return { isInSubgraph: false, typesInFederationDirectives: [] };
|
|
240
|
-
}
|
|
241
|
-
} else {
|
|
242
|
-
// No field application depends on the "owner" directive on the type. If we have no owner, then the
|
|
243
|
-
// field is in all subgraph and we return true. Otherwise, the field is only in the owner subgraph.
|
|
244
|
-
// In any case, the field cannot have a requires or provides
|
|
245
|
-
const ownerApplications = ownerDirective ? f.parent.appliedDirectivesOf(ownerDirective) : [];
|
|
246
|
-
return { isInSubgraph: !ownerApplications.length || getSubgraph(ownerApplications[0]) == name, typesInFederationDirectives: [] };
|
|
247
|
-
}
|
|
248
|
-
},
|
|
249
|
-
(t, name) => {
|
|
250
|
-
const typeApplications: Directive<any, { graph: string, key?: string}>[] = t.appliedDirectivesOf(typeDirective);
|
|
251
|
-
const application = typeApplications.find((application) => (application.arguments().key && (getSubgraph(application) === name)));
|
|
252
|
-
if (application) {
|
|
253
|
-
const typesInFederationDirectives = typesUsedInFederationDirective(application.arguments().key, t as CompositeType);
|
|
254
|
-
return { isEntityWithKeyInSubgraph: true, typesInFederationDirectives };
|
|
255
|
-
} else {
|
|
256
|
-
return { isEntityWithKeyInSubgraph: false, typesInFederationDirectives: [] };
|
|
257
|
-
}
|
|
258
|
-
},
|
|
259
|
-
);
|
|
260
|
-
includeTypeInSubgraph = (t, name) => reachableTypesBySubgraph.get(name)?.has(t.name) ?? false;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Next, we iterate on all types and add it to the proper subgraphs (along with any @key).
|
|
264
|
-
// Note that we first add all types empty and populate the types next. This avoids having to care about the iteration
|
|
265
|
-
// order if we have fields than depends on other types.
|
|
266
|
-
for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) {
|
|
267
|
-
const typeApplications = type.appliedDirectivesOf(typeDirective);
|
|
268
|
-
if (!typeApplications.length) {
|
|
269
|
-
// Imply we don't know in which subgraph the type is, so we had it in all subgraph in which the type is reachable.
|
|
270
|
-
subgraphs
|
|
271
|
-
.values()
|
|
272
|
-
.filter((sg) => includeTypeInSubgraph(type, sg.name))
|
|
273
|
-
.map(sg => sg.schema).forEach(schema => schema.addType(newNamedType(type.kind, type.name)));
|
|
274
|
-
} else {
|
|
275
|
-
for (const application of typeApplications) {
|
|
276
|
-
const args = application.arguments();
|
|
277
|
-
const subgraphName = getSubgraph(application)!;
|
|
278
|
-
const schema = subgraphs.get(subgraphName)!.schema;
|
|
279
|
-
// We can have more than one type directive for a given subgraph
|
|
280
|
-
let subgraphType = schema.type(type.name);
|
|
281
|
-
if (!subgraphType) {
|
|
282
|
-
const kind = args.isInterfaceObject ? 'ObjectType' : type.kind;
|
|
283
|
-
subgraphType = schema.addType(newNamedType(kind, type.name));
|
|
284
|
-
if (args.isInterfaceObject) {
|
|
285
|
-
subgraphType.applyDirective('interfaceObject');
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (args.key) {
|
|
289
|
-
const { resolvable } = args;
|
|
290
|
-
const directive = subgraphType.applyDirective('key', {'fields': args.key, resolvable});
|
|
291
|
-
if (args.extension) {
|
|
292
|
-
directive.setOfExtension(subgraphType.newExtension());
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// We can now populate all those types (with relevant @provides and @requires on fields).
|
|
300
|
-
for (const type of filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition)) {
|
|
301
|
-
switch (type.kind) {
|
|
302
|
-
case 'ObjectType':
|
|
303
|
-
// @ts-expect-error: we fall-through the inputObjectType for fields.
|
|
304
|
-
case 'InterfaceType':
|
|
305
|
-
const addedInterfaces = [];
|
|
306
|
-
const implementsApplications = implementsDirective ? type.appliedDirectivesOf(implementsDirective) : [];
|
|
307
|
-
for (const application of implementsApplications) {
|
|
308
|
-
const args = application.arguments();
|
|
309
|
-
const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!;
|
|
310
|
-
const schema = subgraph.schema;
|
|
311
|
-
(schema.type(type.name)! as (ObjectType | InterfaceType)).addImplementedInterface(args.interface);
|
|
312
|
-
addedInterfaces.push(args.interface);
|
|
313
|
-
}
|
|
314
|
-
for (const implementations of type.interfaceImplementations()) {
|
|
315
|
-
// If the object/interface implements an interface but we had no @join__implements for it (which will
|
|
316
|
-
// always be the case for join v0.1 in particular), then that means the object/interface should implement
|
|
317
|
-
// the interface in all subgraphs (which contains both types).
|
|
318
|
-
const name = implementations.interface.name;
|
|
319
|
-
if (!addedInterfaces.includes(name)) {
|
|
320
|
-
for (const subgraph of subgraphs) {
|
|
321
|
-
const subgraphType = subgraph.schema.type(type.name);
|
|
322
|
-
const subgraphItf = subgraph.schema.type(name);
|
|
323
|
-
if (subgraphType && subgraphItf) {
|
|
324
|
-
(subgraphType as (ObjectType | InterfaceType)).addImplementedInterface(name);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
// Fall-through on purpose.
|
|
330
|
-
case 'InputObjectType':
|
|
331
|
-
const typeApplications = type.appliedDirectivesOf(typeDirective);
|
|
332
|
-
// Note that we can have more that one `@join__type` for a given graph if there is multiple keys, so we collect
|
|
333
|
-
// the actual set of subgraphs defining the type.
|
|
334
|
-
const subgraphsDefiningType: Subgraph[] = [];
|
|
335
|
-
for (const app of typeApplications) {
|
|
336
|
-
const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(app.arguments().graph)!)!;
|
|
337
|
-
if (!subgraphsDefiningType.includes(subgraph)) {
|
|
338
|
-
subgraphsDefiningType.push(subgraph);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
for (const field of type.fields()) {
|
|
343
|
-
const fieldApplications = field.appliedDirectivesOf(fieldDirective);
|
|
344
|
-
if (!fieldApplications.length) {
|
|
345
|
-
// Where there is no join__field, there is roughly 3 main cases:
|
|
346
|
-
// 1. if the type has an `@join__owner` directive (old fed1 supergraph), then the field belong to that owner subgraph.
|
|
347
|
-
// 2. otherwise, if the type has some `@join__type` directives (which it will have in post-"fed1" supergraph), then
|
|
348
|
-
// the field is in all the supergraph in which the type is.
|
|
349
|
-
// 2. otherwise, we kind of don't know, so we add it to all subgraphs that have the parent type and, if the
|
|
350
|
-
// field base type is a named type, know that field type. Note that this last case only happens for old fed1
|
|
351
|
-
// supergraphs which were lacking information and force a bit of guessing. All fed2 generated supergraph
|
|
352
|
-
// use `@join__type` systematically on all types, and will always be case 2.
|
|
353
|
-
const ownerApplications = ownerDirective ? type.appliedDirectivesOf(ownerDirective) : [];
|
|
354
|
-
if (ownerApplications.length > 0) {
|
|
355
|
-
assert(ownerApplications.length == 1, () => `Found multiple join__owner directives on type ${type}`)
|
|
356
|
-
const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(ownerApplications[0].arguments().graph)!)!;
|
|
357
|
-
const subgraphField = addSubgraphField(field, subgraph);
|
|
358
|
-
assert(subgraphField, () => `Found join__owner directive on ${type} but no corresponding join__type`);
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (subgraphsDefiningType.length > 0) {
|
|
363
|
-
const isShareable = isObjectType(type) && subgraphsDefiningType.length > 1;
|
|
364
|
-
for (const subgraph of subgraphsDefiningType) {
|
|
365
|
-
const subgraphField = addSubgraphField(field, subgraph);
|
|
366
|
-
if (subgraphField && isShareable) {
|
|
367
|
-
subgraphField.applyDirective(subgraph.metadata().shareableDirective());
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
} else {
|
|
371
|
-
const fieldBaseType = baseType(field.type!);
|
|
372
|
-
const isShareable = isObjectType(type) && subgraphs.values().filter((s) => s.schema.type(type.name)).length > 1;
|
|
373
|
-
for (const subgraph of subgraphs) {
|
|
374
|
-
if (subgraph.schema.type(fieldBaseType.name)) {
|
|
375
|
-
const subgraphField = addSubgraphField(field, subgraph);
|
|
376
|
-
if (subgraphField && isShareable) {
|
|
377
|
-
subgraphField.applyDirective(subgraph.metadata().shareableDirective());
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
} else {
|
|
383
|
-
const isShareable = isObjectType(type)
|
|
384
|
-
&& (fieldApplications as Directive<any, { external?: boolean, usedOverridden?: boolean }>[]).filter((application) => {
|
|
385
|
-
const args = application.arguments();
|
|
386
|
-
return !args.external && !args.usedOverridden;
|
|
387
|
-
}).length > 1;
|
|
388
|
-
|
|
389
|
-
for (const application of fieldApplications) {
|
|
390
|
-
const args = application.arguments();
|
|
391
|
-
// We use a @join__field with no graph to indicates when a field in the supergraph does not come
|
|
392
|
-
// directly from any subgraph and there is thus nothing to do to "extract" it.
|
|
393
|
-
if (!args.graph) {
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
const subgraph = subgraphs.get(graphEnumNameToSubgraphName.get(args.graph)!)!;
|
|
397
|
-
const subgraphField = addSubgraphField(field, subgraph, args.type);
|
|
398
|
-
if (!subgraphField) {
|
|
399
|
-
// It's unlikely but possible that a fed1 supergraph has a `@provides` on a field of a value type,
|
|
400
|
-
// and that value type is actually unreachable. Because we trim unreachable types for fed1 supergraph
|
|
401
|
-
// (see comment on `includeTypeInSubgraph` above), it would mean we get `undefined` here. It's fine
|
|
402
|
-
// however: the type is unreachable in this subgraph, so ignoring that field application is fine too.
|
|
403
|
-
assert(!includeTypeInSubgraph(type, subgraph.name), () => `Found join__field directive for graph ${subgraph.name} on field ${field.coordinate} but no corresponding join__type on ${type}`);
|
|
404
|
-
continue;
|
|
405
|
-
}
|
|
406
|
-
if (args.requires) {
|
|
407
|
-
subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': args.requires});
|
|
408
|
-
}
|
|
409
|
-
if (args.provides) {
|
|
410
|
-
subgraphField.applyDirective(subgraph.metadata().providesDirective(), {'fields': args.provides});
|
|
411
|
-
}
|
|
412
|
-
if (args.external) {
|
|
413
|
-
subgraphField.applyDirective(subgraph.metadata().externalDirective());
|
|
414
|
-
}
|
|
415
|
-
if (args.usedOverridden) {
|
|
416
|
-
subgraphField.applyDirective(subgraph.metadata().externalDirective(), {'reason': '[overridden]'});
|
|
417
|
-
}
|
|
418
|
-
if (args.override) {
|
|
419
|
-
subgraphField.applyDirective(subgraph.metadata().overrideDirective(), {'from': args.override});
|
|
420
|
-
}
|
|
421
|
-
if (isShareable && !args.external && !args.usedOverridden) {
|
|
422
|
-
subgraphField.applyDirective(subgraph.metadata().shareableDirective());
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
break;
|
|
428
|
-
case 'EnumType':
|
|
429
|
-
// TODO: it's not guaranteed that every enum value was in every subgraph declaring the enum and we should preserve
|
|
430
|
-
// that info with the join spec. But for now, we add every values to all subgraphs (having the enum)
|
|
431
|
-
for (const subgraph of subgraphs) {
|
|
432
|
-
const subgraphEnum = subgraph.schema.type(type.name);
|
|
433
|
-
if (!subgraphEnum) {
|
|
434
|
-
continue;
|
|
435
|
-
}
|
|
436
|
-
assert(isEnumType(subgraphEnum), () => `${subgraphEnum} should be an enum but found a ${subgraphEnum.kind}`);
|
|
437
|
-
|
|
438
|
-
for (const value of type.values) {
|
|
439
|
-
// Before version 0.3 of the join spec (before `enumValueDirective`), we were not recording which subgraph defined which values,
|
|
440
|
-
// and instead aded all values to all subgraphs (at least if the type existed there).
|
|
441
|
-
const addValue = !enumValueDirective
|
|
442
|
-
|| value.appliedDirectivesOf(enumValueDirective).some((d) =>
|
|
443
|
-
graphEnumNameToSubgraphName.get(d.arguments().graph) === subgraph.name
|
|
444
|
-
);
|
|
445
|
-
if (addValue) {
|
|
446
|
-
subgraphEnum.addValue(value.name);
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
break;
|
|
451
|
-
case 'UnionType':
|
|
452
|
-
for (const subgraph of subgraphs) {
|
|
453
|
-
const subgraphUnion = subgraph.schema.type(type.name);
|
|
454
|
-
if (!subgraphUnion) {
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
457
|
-
assert(isUnionType(subgraphUnion), () => `${subgraphUnion} should be an enum but found a ${subgraphUnion.kind}`);
|
|
458
|
-
let membersInSubgraph: string[];
|
|
459
|
-
if (unionMemberDirective) {
|
|
460
|
-
membersInSubgraph = type
|
|
461
|
-
.appliedDirectivesOf(unionMemberDirective)
|
|
462
|
-
.filter((d) => graphEnumNameToSubgraphName.get(d.arguments().graph) === subgraph.name)
|
|
463
|
-
.map((d) => d.arguments().member);
|
|
464
|
-
} else {
|
|
465
|
-
// Before version 0.3 of the join spec, we were not recording which subgraph defined which members,
|
|
466
|
-
// and instead aded all members to all subgraphs (at least if the type existed there).
|
|
467
|
-
membersInSubgraph = type.types().map((t) => t.name);
|
|
468
|
-
}
|
|
469
|
-
for (const memberTypeName of membersInSubgraph) {
|
|
470
|
-
const subgraphType = subgraph.schema.type(memberTypeName);
|
|
471
|
-
if (subgraphType) {
|
|
472
|
-
subgraphUnion.addType(subgraphType as ObjectType);
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const allExecutableDirectives = supergraph.directives().filter((def) => def.hasExecutableLocations());
|
|
481
|
-
for (const subgraph of subgraphs) {
|
|
482
|
-
if (isFed1) {
|
|
483
|
-
// The join spec in fed1 was not including external fields. Let's make sure we had them or we'll get validation
|
|
484
|
-
// errors later.
|
|
485
|
-
addExternalFields(subgraph, supergraph, isFed1);
|
|
486
|
-
}
|
|
487
|
-
removeInactiveProvidesAndRequires(subgraph.schema);
|
|
488
|
-
|
|
489
|
-
// We now do an additional path on all types because we sometimes added types to subgraphs without
|
|
490
|
-
// being sure that the subgraph had the type in the first place (especially with the 0.1 join spec), and because
|
|
491
|
-
// we later might not have added any fields/members to said type, they may be empty (indicating they clearly
|
|
492
|
-
// didn't belong to the subgraph in the first) and we need to remove them.
|
|
493
|
-
// Note that need to do this _after_ the `addExternalFields` call above since it may have added (external) fields
|
|
494
|
-
// to some of the types.
|
|
495
|
-
for (const type of subgraph.schema.types()) {
|
|
496
|
-
switch (type.kind) {
|
|
497
|
-
case 'ObjectType':
|
|
498
|
-
case 'InterfaceType':
|
|
499
|
-
case 'InputObjectType':
|
|
500
|
-
if (!type.hasFields()) {
|
|
501
|
-
// Note that we have to use removeRecursive or this could leave the subgraph invalid. But if the
|
|
502
|
-
// type was not in this subgraphs, nothing that depends on it should be either.
|
|
503
|
-
type.removeRecursive();
|
|
504
|
-
}
|
|
505
|
-
break;
|
|
506
|
-
case 'UnionType':
|
|
507
|
-
if (type.membersCount() === 0) {
|
|
508
|
-
type.removeRecursive();
|
|
509
|
-
}
|
|
510
|
-
break;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
202
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
// applications. It might become something we need later, but we don't so far.
|
|
519
|
-
for (const definition of allExecutableDirectives) {
|
|
520
|
-
// Note that we skip any potentially applied directives in the argument of the copied definition, because as said
|
|
521
|
-
// in the comment above, we haven't copied type-system directives. And so far, we really don't care about those
|
|
522
|
-
// applications.
|
|
523
|
-
copyDirectiveDefinitionToSchema({
|
|
524
|
-
definition,
|
|
525
|
-
schema: subgraph.schema,
|
|
526
|
-
copyDirectiveApplicationsInArguments: false,
|
|
527
|
-
locationFilter: (loc) => isExecutableDirectiveLocation(loc),
|
|
528
|
-
});
|
|
203
|
+
const getSubgraph = (application: Directive<any, { graph?: string }>): Subgraph | undefined => {
|
|
204
|
+
const graph = application.arguments().graph;
|
|
205
|
+
if (!graph) {
|
|
206
|
+
return undefined;
|
|
529
207
|
}
|
|
530
|
-
|
|
208
|
+
const subgraphName = graphEnumNameToSubgraphName.get(graph);
|
|
209
|
+
assert(subgraphName, () => `Invalid graph name ${graph} found in ${application} on ${application.parent}: does not match a graph defined in the @join__Graph enum`);
|
|
210
|
+
const subgraph = subgraphs.get(subgraphName);
|
|
211
|
+
assert(subgraph, 'All subgraphs should have been created by `collectEmptySubgraphs`');
|
|
212
|
+
return subgraph;
|
|
213
|
+
};
|
|
531
214
|
|
|
532
|
-
|
|
533
|
-
|
|
215
|
+
const types = filteredTypes(supergraph, joinSpec, coreFeatures.coreDefinition);
|
|
216
|
+
const args: ExtractArguments = {
|
|
217
|
+
supergraph,
|
|
218
|
+
subgraphs,
|
|
219
|
+
joinSpec,
|
|
220
|
+
filteredTypes: types,
|
|
221
|
+
getSubgraph,
|
|
222
|
+
};
|
|
534
223
|
if (isFed1) {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
// of an extension and be only in a few subgraphs), we remove the field or the subgraph would be invalid.
|
|
539
|
-
for (const subgraph of subgraphs) {
|
|
540
|
-
for (const itf of subgraph.schema.interfaceTypes()) {
|
|
541
|
-
// We only look at objects because interfaces are handled by this own loop in practice.
|
|
542
|
-
const implementations = itf.possibleRuntimeTypes();
|
|
543
|
-
for (const field of itf.fields()) {
|
|
544
|
-
if (!implementations.every(implem => implem.field(field.name))) {
|
|
545
|
-
field.remove();
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
// And it may be that the interface wasn't part of the subgraph at all!
|
|
549
|
-
if (!itf.hasFields()) {
|
|
550
|
-
itf.remove();
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
224
|
+
extractSubgraphsFromFed1Supergraph(args);
|
|
225
|
+
} else {
|
|
226
|
+
extractSubgraphsFromFed2Supergraph(args);
|
|
554
227
|
}
|
|
555
228
|
|
|
556
229
|
// We're done with the subgraphs, so call validate (which, amongst other things, sets up the _entities query field, which ensures
|
|
557
230
|
// all entities in all subgraphs are reachable from a query and so are properly included in the "query graph" later).
|
|
558
231
|
for (const subgraph of subgraphs) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
232
|
+
if (validateExtractedSubgraphs) {
|
|
233
|
+
try {
|
|
234
|
+
subgraph.validate();
|
|
235
|
+
} catch (e) {
|
|
236
|
+
// This is going to be caught directly by the enclosing try-catch, but this is so we indicate the subgraph having the issue.
|
|
237
|
+
throw new SubgraphExtractionError(e, subgraph);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
subgraph.assumeValid();
|
|
564
241
|
}
|
|
565
242
|
}
|
|
566
243
|
|
|
@@ -598,6 +275,287 @@ export function extractSubgraphsFromSupergraph(supergraph: Schema): Subgraphs {
|
|
|
598
275
|
}
|
|
599
276
|
}
|
|
600
277
|
|
|
278
|
+
type ExtractArguments = {
|
|
279
|
+
supergraph: Schema,
|
|
280
|
+
subgraphs: Subgraphs,
|
|
281
|
+
joinSpec: JoinSpecDefinition,
|
|
282
|
+
filteredTypes: NamedType[],
|
|
283
|
+
getSubgraph: (application: Directive<any, { graph?: string }>) => Subgraph | undefined,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
type SubgraphTypeInfo<T extends NamedType> = Map<string, { type: T, subgraph: Subgraph }>;
|
|
287
|
+
|
|
288
|
+
type TypeInfo<T extends NamedType> = {
|
|
289
|
+
type: T,
|
|
290
|
+
subgraphsInfo: SubgraphTypeInfo<T>,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
type TypesInfo = {
|
|
294
|
+
objOrItfTypes: TypeInfo<ObjectType | InterfaceType>[],
|
|
295
|
+
inputObjTypes: TypeInfo<InputObjectType>[],
|
|
296
|
+
enumTypes: TypeInfo<EnumType>[],
|
|
297
|
+
unionTypes: TypeInfo<UnionType>[],
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
function addAllEmptySubgraphTypes({
|
|
301
|
+
supergraph,
|
|
302
|
+
joinSpec,
|
|
303
|
+
filteredTypes,
|
|
304
|
+
getSubgraph,
|
|
305
|
+
}: ExtractArguments): TypesInfo {
|
|
306
|
+
const typeDirective = joinSpec.typeDirective(supergraph);
|
|
307
|
+
|
|
308
|
+
const objOrItfTypes: TypeInfo<ObjectType | InterfaceType>[] = [];
|
|
309
|
+
const inputObjTypes: TypeInfo<InputObjectType>[] = [];
|
|
310
|
+
const enumTypes: TypeInfo<EnumType>[] = [];
|
|
311
|
+
const unionTypes: TypeInfo<UnionType>[] = [];
|
|
312
|
+
for (const type of filteredTypes) {
|
|
313
|
+
const typeApplications = type.appliedDirectivesOf(typeDirective);
|
|
314
|
+
switch (type.kind) {
|
|
315
|
+
// See comment in `addEmptyType` for why it actually matters that object and interface are handled together.
|
|
316
|
+
// (on top of it making sense code-wise since both type behave exactly the same for most of what we're doing here).
|
|
317
|
+
case 'InterfaceType':
|
|
318
|
+
case 'ObjectType':
|
|
319
|
+
objOrItfTypes.push({ type, subgraphsInfo: addEmptyType(type, type.appliedDirectivesOf(typeDirective), getSubgraph) });
|
|
320
|
+
break;
|
|
321
|
+
case 'InputObjectType':
|
|
322
|
+
inputObjTypes.push({ type, subgraphsInfo: addEmptyType(type, type.appliedDirectivesOf(typeDirective), getSubgraph) });
|
|
323
|
+
break;
|
|
324
|
+
case 'EnumType':
|
|
325
|
+
enumTypes.push({ type, subgraphsInfo: addEmptyType(type, type.appliedDirectivesOf(typeDirective), getSubgraph) });
|
|
326
|
+
break;
|
|
327
|
+
case 'UnionType':
|
|
328
|
+
unionTypes.push({ type, subgraphsInfo: addEmptyType(type, type.appliedDirectivesOf(typeDirective), getSubgraph) });
|
|
329
|
+
break;
|
|
330
|
+
case 'ScalarType':
|
|
331
|
+
// Scalar are a bit special in that they don't have any sub-component, so we don't track them beyond adding them to the
|
|
332
|
+
// proper subgraphs. It's also simple because there is no possible key so there is exactly on @join__type application for
|
|
333
|
+
// each subgraph having the scalar (and most arg cannot be present).
|
|
334
|
+
for (const application of typeApplications) {
|
|
335
|
+
const subgraph = getSubgraph(application);
|
|
336
|
+
assert(subgraph, () => `Should have found the subgraph for ${application}`);
|
|
337
|
+
subgraph.schema.addType(newNamedType(type.kind, type.name));
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
objOrItfTypes,
|
|
344
|
+
inputObjTypes,
|
|
345
|
+
enumTypes,
|
|
346
|
+
unionTypes,
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function addEmptyType<T extends NamedType>(
|
|
351
|
+
type: T,
|
|
352
|
+
typeApplications: Directive<T, JoinTypeDirectiveArguments>[],
|
|
353
|
+
getSubgraph: (application: Directive<any, { graph?: string }>) => Subgraph | undefined,
|
|
354
|
+
): SubgraphTypeInfo<T> {
|
|
355
|
+
// In fed2, we always mark all types with `@join__type` but making sure.
|
|
356
|
+
assert(typeApplications.length > 0, `Missing @join__type on ${type}`)
|
|
357
|
+
const subgraphsInfo: SubgraphTypeInfo<T> = new Map<string, { type: T, subgraph: Subgraph }>();
|
|
358
|
+
for (const application of typeApplications) {
|
|
359
|
+
const { graph, key, extension, resolvable, isInterfaceObject } = application.arguments();
|
|
360
|
+
let subgraphInfo = subgraphsInfo.get(graph);
|
|
361
|
+
if (!subgraphInfo) {
|
|
362
|
+
const subgraph = getSubgraph(application);
|
|
363
|
+
assert(subgraph, () => `Should have found the subgraph for ${application}`);
|
|
364
|
+
const kind = isInterfaceObject ? 'ObjectType' : type.kind;
|
|
365
|
+
// Note that we have to cast to `T` below. First because going through `type.kind` and `newNamedType`
|
|
366
|
+
// does not give a `T`. But even if we were to bend the type-system to work for that, there is the
|
|
367
|
+
// case of interface objects where an interface in the supergraph ends up being an object in the
|
|
368
|
+
// subgraph. But this is ok because we the object and interface type cases are lumped together (and
|
|
369
|
+
// this also means we "need" it to be this way).
|
|
370
|
+
const subgraphType = subgraph.schema.addType(newNamedType(kind, type.name)) as T;
|
|
371
|
+
if (isInterfaceObject) {
|
|
372
|
+
subgraphType.applyDirective('interfaceObject');
|
|
373
|
+
}
|
|
374
|
+
subgraphInfo = { type: subgraphType, subgraph };
|
|
375
|
+
subgraphsInfo.set(graph, subgraphInfo);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (key) {
|
|
379
|
+
const directive = subgraphInfo.type.applyDirective('key', {'fields': key, resolvable});
|
|
380
|
+
if (extension) {
|
|
381
|
+
directive.setOfExtension(subgraphInfo.type.newExtension());
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return subgraphsInfo;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function extractObjOrItfContent(args: ExtractArguments, info: TypeInfo<ObjectType | InterfaceType>[]) {
|
|
389
|
+
const fieldDirective = args.joinSpec.fieldDirective(args.supergraph);
|
|
390
|
+
|
|
391
|
+
// join_implements was added in join 0.2, and this method does not run for join 0.1, so it should be defined.
|
|
392
|
+
const implementsDirective = args.joinSpec.implementsDirective(args.supergraph);
|
|
393
|
+
assert(implementsDirective, '@join__implements should existing for a fed2 supergraph');
|
|
394
|
+
|
|
395
|
+
for (const { type, subgraphsInfo } of info) {
|
|
396
|
+
const implementsApplications = type.appliedDirectivesOf(implementsDirective);
|
|
397
|
+
for (const application of implementsApplications) {
|
|
398
|
+
const args = application.arguments();
|
|
399
|
+
// Note that if we have a `@join__implements` for a subgraph, then we must have a `@join__type` too, so
|
|
400
|
+
// the `get` below is guaranteed to not be undefined.
|
|
401
|
+
const subgraphInfo = subgraphsInfo.get(args.graph)!;
|
|
402
|
+
subgraphInfo.type.addImplementedInterface(args.interface);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
for (const field of type.fields()) {
|
|
406
|
+
const fieldApplications = field.appliedDirectivesOf(fieldDirective);
|
|
407
|
+
if (fieldApplications.length === 0) {
|
|
408
|
+
// In fed2 subgraph, no @join__field means that the field is in all the subgraphs in which the type is.
|
|
409
|
+
const isShareable = isObjectType(type) && subgraphsInfo.size > 1;
|
|
410
|
+
for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
|
|
411
|
+
addSubgraphField({ field, type: subgraphType, subgraph, isShareable });
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
const isShareable = isObjectType(type)
|
|
415
|
+
&& (fieldApplications as Directive<any, { external?: boolean, usedOverridden?: boolean }>[]).filter((application) => {
|
|
416
|
+
const args = application.arguments();
|
|
417
|
+
return !args.external && !args.usedOverridden;
|
|
418
|
+
}).length > 1;
|
|
419
|
+
|
|
420
|
+
for (const application of fieldApplications) {
|
|
421
|
+
const args = application.arguments();
|
|
422
|
+
// We use a @join__field with no graph to indicates when a field in the supergraph does not come
|
|
423
|
+
// directly from any subgraph and there is thus nothing to do to "extract" it.
|
|
424
|
+
if (!args.graph) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const { type: subgraphType, subgraph } = subgraphsInfo.get(args.graph)!;
|
|
429
|
+
addSubgraphField({ field, type: subgraphType, subgraph, isShareable, joinFieldArgs: args});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function extractInputObjContent(args: ExtractArguments, info: TypeInfo<InputObjectType>[]) {
|
|
437
|
+
const fieldDirective = args.joinSpec.fieldDirective(args.supergraph);
|
|
438
|
+
|
|
439
|
+
for (const { type, subgraphsInfo } of info) {
|
|
440
|
+
for (const field of type.fields()) {
|
|
441
|
+
const fieldApplications = field.appliedDirectivesOf(fieldDirective);
|
|
442
|
+
if (fieldApplications.length === 0) {
|
|
443
|
+
// In fed2 subgraph, no @join__field means that the field is in all the subgraphs in which the type is.
|
|
444
|
+
for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
|
|
445
|
+
addSubgraphInputField({ field, type: subgraphType, subgraph });
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
for (const application of fieldApplications) {
|
|
449
|
+
const args = application.arguments();
|
|
450
|
+
// We use a @join__field with no graph to indicates when a field in the supergraph does not come
|
|
451
|
+
// directly from any subgraph and there is thus nothing to do to "extract" it.
|
|
452
|
+
if (!args.graph) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const { type: subgraphType, subgraph } = subgraphsInfo.get(args.graph)!;
|
|
457
|
+
addSubgraphInputField({ field, type: subgraphType, subgraph, joinFieldArgs: args});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function extractEnumTypeContent(args: ExtractArguments, info: TypeInfo<EnumType>[]) {
|
|
465
|
+
// This was added in join 0.3, so it can genuinely be undefined.
|
|
466
|
+
const enumValueDirective = args.joinSpec.enumValueDirective(args.supergraph);
|
|
467
|
+
|
|
468
|
+
for (const { type, subgraphsInfo } of info) {
|
|
469
|
+
for (const value of type.values) {
|
|
470
|
+
const enumValueApplications = enumValueDirective ? value.appliedDirectivesOf(enumValueDirective) : [];
|
|
471
|
+
if (enumValueApplications.length === 0) {
|
|
472
|
+
for (const { type: subgraphType } of subgraphsInfo.values()) {
|
|
473
|
+
subgraphType.addValue(value.name);
|
|
474
|
+
}
|
|
475
|
+
} else {
|
|
476
|
+
for (const application of enumValueApplications) {
|
|
477
|
+
const args = application.arguments();
|
|
478
|
+
const { type: subgraphType } = subgraphsInfo.get(args.graph)!;
|
|
479
|
+
subgraphType.addValue(value.name);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function extractUnionTypeContent(args: ExtractArguments, info: TypeInfo<UnionType>[]) {
|
|
487
|
+
// This was added in join 0.3, so it can genuinely be undefined.
|
|
488
|
+
const unionMemberDirective = args.joinSpec.unionMemberDirective(args.supergraph);
|
|
489
|
+
|
|
490
|
+
// Note that union members works a bit differently from fields or enum values, and this because we cannot have
|
|
491
|
+
// directive applications on type members. So the `unionMemberDirective` applications are on the type itself,
|
|
492
|
+
// and they mention the member that they target.
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
for (const { type, subgraphsInfo } of info) {
|
|
496
|
+
const unionMemberApplications = unionMemberDirective ? type.appliedDirectivesOf(unionMemberDirective) : [];
|
|
497
|
+
if (unionMemberApplications.length === 0) {
|
|
498
|
+
// No @join__unionMember; every member should be added to every subgraph having the union (at least as long
|
|
499
|
+
// as the subgraph has the member itself).
|
|
500
|
+
for (const { type: subgraphType, subgraph } of subgraphsInfo.values()) {
|
|
501
|
+
for (const member of type.types()) {
|
|
502
|
+
const subgraphMember = subgraph.schema.type(member.name);
|
|
503
|
+
if (subgraphMember) {
|
|
504
|
+
// Note that object types in the supergraph are guaranteed to be object types in subgraphs.
|
|
505
|
+
subgraphType.addType(subgraphMember as ObjectType);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
for (const application of unionMemberApplications) {
|
|
511
|
+
const args = application.arguments();
|
|
512
|
+
const { type: subgraphType, subgraph } = subgraphsInfo.get(args.graph)!;
|
|
513
|
+
// Note that object types in the supergraph are guaranteed to be object types in subgraphs.
|
|
514
|
+
// We also know that the type must exist in this case (we don't generate broken @join_unionMember).
|
|
515
|
+
subgraphType.addType(subgraph.schema.type(args.member) as ObjectType);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function extractSubgraphsFromFed2Supergraph(args: ExtractArguments) {
|
|
522
|
+
const {
|
|
523
|
+
objOrItfTypes,
|
|
524
|
+
inputObjTypes,
|
|
525
|
+
enumTypes,
|
|
526
|
+
unionTypes,
|
|
527
|
+
} = addAllEmptySubgraphTypes(args);
|
|
528
|
+
|
|
529
|
+
extractObjOrItfContent(args, objOrItfTypes);
|
|
530
|
+
extractInputObjContent(args, inputObjTypes);
|
|
531
|
+
extractEnumTypeContent(args, enumTypes);
|
|
532
|
+
extractUnionTypeContent(args, unionTypes);
|
|
533
|
+
|
|
534
|
+
// We add all the "executable" directives from the supergraph to each subgraphs, as those may be part
|
|
535
|
+
// of a query and end up in any subgraph fetches. We do this "last" to make sure that if one of the directive
|
|
536
|
+
// use a type for an argument, that argument exists.
|
|
537
|
+
// Note that we don't bother with non-executable directives at the moment since we've don't extract their
|
|
538
|
+
// applications. It might become something we need later, but we don't so far.
|
|
539
|
+
const allExecutableDirectives = args.supergraph.directives().filter((def) => def.hasExecutableLocations());
|
|
540
|
+
for (const subgraph of args.subgraphs) {
|
|
541
|
+
removeInactiveProvidesAndRequires(subgraph.schema);
|
|
542
|
+
|
|
543
|
+
removeUnusedTypesFromSubgraph(subgraph.schema);
|
|
544
|
+
|
|
545
|
+
for (const definition of allExecutableDirectives) {
|
|
546
|
+
// Note that we skip any potentially applied directives in the argument of the copied definition, because as said
|
|
547
|
+
// in the comment above, we haven't copied type-system directives. And so far, we really don't care about those
|
|
548
|
+
// applications.
|
|
549
|
+
copyDirectiveDefinitionToSchema({
|
|
550
|
+
definition,
|
|
551
|
+
schema: subgraph.schema,
|
|
552
|
+
copyDirectiveApplicationsInArguments: false,
|
|
553
|
+
locationFilter: (loc) => isExecutableDirectiveLocation(loc),
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
601
559
|
const DEBUG_SUBGRAPHS_ENV_VARIABLE_NAME = 'APOLLO_FEDERATION_DEBUG_SUBGRAPHS';
|
|
602
560
|
|
|
603
561
|
function maybeDumpSubgraphSchema(subgraph: Subgraph): string {
|
|
@@ -625,51 +583,315 @@ function errorToString(e: any,): string {
|
|
|
625
583
|
return causes ? printErrors(causes) : String(e);
|
|
626
584
|
}
|
|
627
585
|
|
|
628
|
-
|
|
586
|
+
function addSubgraphField({
|
|
587
|
+
field,
|
|
588
|
+
type,
|
|
589
|
+
subgraph,
|
|
590
|
+
isShareable,
|
|
591
|
+
joinFieldArgs,
|
|
592
|
+
}: {
|
|
593
|
+
field: FieldDefinition<ObjectType | InterfaceType>,
|
|
594
|
+
type: ObjectType | InterfaceType,
|
|
595
|
+
subgraph: Subgraph,
|
|
596
|
+
isShareable: boolean,
|
|
597
|
+
joinFieldArgs?: JoinFieldDirectiveArguments,
|
|
598
|
+
}): FieldDefinition<ObjectType | InterfaceType> {
|
|
599
|
+
const copiedFieldType = joinFieldArgs?.type
|
|
600
|
+
? decodeType(joinFieldArgs.type, subgraph.schema, subgraph.name)
|
|
601
|
+
: copyType(field.type!, subgraph.schema, subgraph.name);
|
|
629
602
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}
|
|
634
|
-
|
|
603
|
+
const subgraphField = type.addField(field.name, copiedFieldType);
|
|
604
|
+
for (const arg of field.arguments()) {
|
|
605
|
+
subgraphField.addArgument(arg.name, copyType(arg.type!, subgraph.schema, subgraph.name), arg.defaultValue);
|
|
606
|
+
}
|
|
607
|
+
if (joinFieldArgs?.requires) {
|
|
608
|
+
subgraphField.applyDirective(subgraph.metadata().requiresDirective(), {'fields': joinFieldArgs.requires});
|
|
609
|
+
}
|
|
610
|
+
if (joinFieldArgs?.provides) {
|
|
611
|
+
subgraphField.applyDirective(subgraph.metadata().providesDirective(), {'fields': joinFieldArgs.provides});
|
|
612
|
+
}
|
|
613
|
+
const external = !!joinFieldArgs?.external;
|
|
614
|
+
if (external) {
|
|
615
|
+
subgraphField.applyDirective(subgraph.metadata().externalDirective());
|
|
616
|
+
}
|
|
617
|
+
const usedOverridden = !!joinFieldArgs?.usedOverridden;
|
|
618
|
+
if (usedOverridden) {
|
|
619
|
+
subgraphField.applyDirective(subgraph.metadata().externalDirective(), {'reason': '[overridden]'});
|
|
620
|
+
}
|
|
621
|
+
if (joinFieldArgs?.override) {
|
|
622
|
+
subgraphField.applyDirective(subgraph.metadata().overrideDirective(), {'from': joinFieldArgs.override});
|
|
623
|
+
}
|
|
624
|
+
if (isShareable && !external && !usedOverridden) {
|
|
625
|
+
subgraphField.applyDirective(subgraph.metadata().shareableDirective());
|
|
635
626
|
}
|
|
627
|
+
return subgraphField;
|
|
636
628
|
}
|
|
637
629
|
|
|
638
|
-
function
|
|
639
|
-
|
|
630
|
+
function addSubgraphInputField({
|
|
631
|
+
field,
|
|
632
|
+
type,
|
|
633
|
+
subgraph,
|
|
634
|
+
joinFieldArgs,
|
|
635
|
+
}: {
|
|
636
|
+
field: InputFieldDefinition,
|
|
637
|
+
type: InputObjectType,
|
|
640
638
|
subgraph: Subgraph,
|
|
641
|
-
|
|
642
|
-
):
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
639
|
+
joinFieldArgs?: JoinFieldDirectiveArguments,
|
|
640
|
+
}): InputFieldDefinition {
|
|
641
|
+
const copiedType = joinFieldArgs?.type
|
|
642
|
+
? decodeType(joinFieldArgs?.type, subgraph.schema, subgraph.name)
|
|
643
|
+
: copyType(field.type!, subgraph.schema, subgraph.name);
|
|
644
|
+
|
|
645
|
+
const inputField = type.addField(field.name, copiedType);
|
|
646
|
+
inputField.defaultValue = field.defaultValue
|
|
647
|
+
return inputField;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function extractSubgraphsFromFed1Supergraph({
|
|
651
|
+
supergraph,
|
|
652
|
+
subgraphs,
|
|
653
|
+
joinSpec,
|
|
654
|
+
filteredTypes,
|
|
655
|
+
getSubgraph,
|
|
656
|
+
}: ExtractArguments): Subgraphs {
|
|
657
|
+
const typeDirective = joinSpec.typeDirective(supergraph);
|
|
658
|
+
const ownerDirective = joinSpec.ownerDirective(supergraph);
|
|
659
|
+
const fieldDirective = joinSpec.fieldDirective(supergraph);
|
|
660
|
+
|
|
661
|
+
/*
|
|
662
|
+
* For fed1 supergraph, only entity types are marked with `@join__type` and `@join__field`. Which mean that for value types,
|
|
663
|
+
* we cannot directly know in which subgraphs they were initially defined. One strategy consists in "extracting" value types into
|
|
664
|
+
* all subgraphs blindly: functionally, having some unused types in an extracted subgraph schema does not matter much. However, adding
|
|
665
|
+
* those useless types increases memory usage, and we've seen some case with lots of subgraphs and lots of value types where those
|
|
666
|
+
* unused types balloon up memory usage (from 100MB to 1GB in one example; obviously, this is made worst by the fact that javascript
|
|
667
|
+
* is pretty memory heavy in the first place). So to avoid that problem, for fed1 supergraph, we do a first pass where we collect
|
|
668
|
+
* for all the subgraphs the set of types that are actually reachable in that subgraph. As we extract do the actual type extraction,
|
|
669
|
+
* we use this to ignore non-reachable types for any given subgraph.
|
|
670
|
+
*/
|
|
671
|
+
const reachableTypesBySubgraph = collectFieldReachableTypesForAllSubgraphs(
|
|
672
|
+
supergraph,
|
|
673
|
+
subgraphs.names(),
|
|
674
|
+
(f, name) => {
|
|
675
|
+
const fieldApplications: Directive<any, { graph?: string, requires?: string, provides?: string }>[] = f.appliedDirectivesOf(fieldDirective);
|
|
676
|
+
if (fieldApplications.length) {
|
|
677
|
+
const application = fieldApplications.find((application) => getSubgraph(application)?.name === name);
|
|
678
|
+
if (application) {
|
|
679
|
+
const args = application.arguments();
|
|
680
|
+
const typesInFederationDirectives =
|
|
681
|
+
typesUsedInFederationDirective(args.provides, baseType(f.type!) as CompositeType)
|
|
682
|
+
.concat(typesUsedInFederationDirective(args.requires, f.parent));
|
|
683
|
+
return { isInSubgraph: true, typesInFederationDirectives };
|
|
684
|
+
} else {
|
|
685
|
+
return { isInSubgraph: false, typesInFederationDirectives: [] };
|
|
686
|
+
}
|
|
687
|
+
} else {
|
|
688
|
+
// No field application depends on the "owner" directive on the type. If we have no owner, then the
|
|
689
|
+
// field is in all subgraph and we return true. Otherwise, the field is only in the owner subgraph.
|
|
690
|
+
// In any case, the field cannot have a requires or provides
|
|
691
|
+
const ownerApplications = ownerDirective ? f.parent.appliedDirectivesOf(ownerDirective) : [];
|
|
692
|
+
return { isInSubgraph: !ownerApplications.length || getSubgraph(ownerApplications[0])?.name == name, typesInFederationDirectives: [] };
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
(t, name) => {
|
|
696
|
+
const typeApplications: Directive<any, { graph: string, key?: string}>[] = t.appliedDirectivesOf(typeDirective);
|
|
697
|
+
const application = typeApplications.find((application) => (application.arguments().key && (getSubgraph(application)?.name === name)));
|
|
698
|
+
if (application) {
|
|
699
|
+
const typesInFederationDirectives = typesUsedInFederationDirective(application.arguments().key, t as CompositeType);
|
|
700
|
+
return { isEntityWithKeyInSubgraph: true, typesInFederationDirectives };
|
|
701
|
+
} else {
|
|
702
|
+
return { isEntityWithKeyInSubgraph: false, typesInFederationDirectives: [] };
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
);
|
|
706
|
+
const includeTypeInSubgraph = (t: NamedType, name: string) => reachableTypesBySubgraph.get(name)?.has(t.name) ?? false;
|
|
707
|
+
|
|
708
|
+
// Next, we iterate on all types and add it to the proper subgraphs (along with any @key).
|
|
709
|
+
// Note that we first add all types empty and populate the types next. This avoids having to care about the iteration
|
|
710
|
+
// order if we have fields than depends on other types.
|
|
711
|
+
for (const type of filteredTypes) {
|
|
712
|
+
const typeApplications = type.appliedDirectivesOf(typeDirective);
|
|
713
|
+
if (!typeApplications.length) {
|
|
714
|
+
// Imply we don't know in which subgraph the type is, so we had it in all subgraph in which the type is reachable.
|
|
715
|
+
for (const subgraph of subgraphs) {
|
|
716
|
+
if (includeTypeInSubgraph(type, subgraph.name)) {
|
|
717
|
+
subgraph.schema.addType(newNamedType(type.kind, type.name));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
} else {
|
|
721
|
+
for (const application of typeApplications) {
|
|
722
|
+
const args = application.arguments();
|
|
723
|
+
const subgraph = getSubgraph(application)!;
|
|
724
|
+
assert(subgraph, () => `Should have found the subgraph for ${application}`);
|
|
725
|
+
const schema = subgraph.schema;
|
|
726
|
+
// We can have more than one type directive for a given subgraph
|
|
727
|
+
let subgraphType = schema.type(type.name);
|
|
728
|
+
if (!subgraphType) {
|
|
729
|
+
const kind = args.isInterfaceObject ? 'ObjectType' : type.kind;
|
|
730
|
+
subgraphType = schema.addType(newNamedType(kind, type.name));
|
|
731
|
+
if (args.isInterfaceObject) {
|
|
732
|
+
subgraphType.applyDirective('interfaceObject');
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (args.key) {
|
|
736
|
+
const { resolvable } = args;
|
|
737
|
+
const directive = subgraphType.applyDirective('key', {'fields': args.key, resolvable});
|
|
738
|
+
if (args.extension) {
|
|
739
|
+
directive.setOfExtension(subgraphType.newExtension());
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
651
743
|
}
|
|
652
|
-
return field;
|
|
653
|
-
} else {
|
|
654
|
-
return undefined;
|
|
655
744
|
}
|
|
745
|
+
|
|
746
|
+
// We can now populate all those types (with relevant @provides and @requires on fields).
|
|
747
|
+
for (const type of filteredTypes) {
|
|
748
|
+
switch (type.kind) {
|
|
749
|
+
case 'ObjectType':
|
|
750
|
+
// @ts-expect-error: we fall-through the inputObjectType for fields.
|
|
751
|
+
case 'InterfaceType':
|
|
752
|
+
for (const implementations of type.interfaceImplementations()) {
|
|
753
|
+
// There is no `@join__implements` in fed1 supergraphs, so we have no choice but to mark the
|
|
754
|
+
// object/interface as implementing the interface in all subgraphs (at least those that contains
|
|
755
|
+
// both types).
|
|
756
|
+
const name = implementations.interface.name;
|
|
757
|
+
for (const subgraph of subgraphs) {
|
|
758
|
+
const subgraphType = subgraph.schema.type(type.name);
|
|
759
|
+
const subgraphItf = subgraph.schema.type(name);
|
|
760
|
+
if (subgraphType && subgraphItf) {
|
|
761
|
+
(subgraphType as (ObjectType | InterfaceType)).addImplementedInterface(name);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
// Fall-through on purpose.
|
|
766
|
+
case 'InputObjectType':
|
|
767
|
+
for (const field of type.fields()) {
|
|
768
|
+
const fieldApplications = field.appliedDirectivesOf(fieldDirective);
|
|
769
|
+
if (!fieldApplications.length) {
|
|
770
|
+
// In fed1 supergraphs, the meaning of having no join__field depends on whether the parent type has a
|
|
771
|
+
// `@join__owner`. If it does, it means the field is only on that owner subgraph. Otherwise, we kind of
|
|
772
|
+
// don't know, so we add it to all subgraphs that have the parent type and, if the field base type
|
|
773
|
+
// is a named type, know that field type.
|
|
774
|
+
const ownerApplications = ownerDirective ? type.appliedDirectivesOf(ownerDirective) : [];
|
|
775
|
+
if (ownerApplications.length > 0) {
|
|
776
|
+
assert(ownerApplications.length == 1, () => `Found multiple join__owner directives on type ${type}`)
|
|
777
|
+
const subgraph = getSubgraph(ownerApplications[0]);
|
|
778
|
+
assert(subgraph, () => `Should have found the subgraph for ${ownerApplications[0]}`);
|
|
779
|
+
addSubgraphFieldForFed1(field, subgraph, false);
|
|
780
|
+
} else {
|
|
781
|
+
const fieldBaseType = baseType(field.type!);
|
|
782
|
+
const isShareable = isObjectType(type) && subgraphs.values().filter((s) => s.schema.type(type.name)).length > 1;
|
|
783
|
+
for (const subgraph of subgraphs) {
|
|
784
|
+
if (subgraph.schema.type(fieldBaseType.name)) {
|
|
785
|
+
addSubgraphFieldForFed1(field, subgraph, isShareable);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
} else {
|
|
790
|
+
// Note that fed1 supergraphs only include `@join__field` for non-external fields, so it needs shareable as soon
|
|
791
|
+
// as it has more than one `@join__field`.
|
|
792
|
+
const isShareable = isObjectType(type) && fieldApplications.length > 1;
|
|
793
|
+
for (const application of fieldApplications) {
|
|
794
|
+
const subgraph = getSubgraph(application);
|
|
795
|
+
// We use a @join__field with no graph to indicates when a field in the supergraph does not come
|
|
796
|
+
// directly from any subgraph and there is thus nothing to do to "extract" it.
|
|
797
|
+
if (!subgraph) {
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const args = application.arguments();
|
|
802
|
+
addSubgraphFieldForFed1(field, subgraph, isShareable, args);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
break;
|
|
807
|
+
case 'EnumType':
|
|
808
|
+
for (const subgraph of subgraphs) {
|
|
809
|
+
const subgraphEnum = subgraph.schema.type(type.name);
|
|
810
|
+
if (!subgraphEnum) {
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
assert(isEnumType(subgraphEnum), () => `${subgraphEnum} should be an enum but found a ${subgraphEnum.kind}`);
|
|
814
|
+
|
|
815
|
+
// There is not `@join__enumValue` in fed1, so we add to all graphs regardless.
|
|
816
|
+
for (const value of type.values) {
|
|
817
|
+
subgraphEnum.addValue(value.name);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
break;
|
|
821
|
+
case 'UnionType':
|
|
822
|
+
for (const subgraph of subgraphs) {
|
|
823
|
+
const subgraphUnion = subgraph.schema.type(type.name);
|
|
824
|
+
if (!subgraphUnion) {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
assert(isUnionType(subgraphUnion), () => `${subgraphUnion} should be an enum but found a ${subgraphUnion.kind}`);
|
|
828
|
+
|
|
829
|
+
// There is not `@join__unionMember` in fed1, so we add to all graphs regardless.
|
|
830
|
+
for (const memberTypeName of type.types().map((t) => t.name)) {
|
|
831
|
+
const subgraphType = subgraph.schema.type(memberTypeName);
|
|
832
|
+
if (subgraphType) {
|
|
833
|
+
subgraphUnion.addType(subgraphType as ObjectType);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const allExecutableDirectives = supergraph.directives().filter((def) => def.hasExecutableLocations());
|
|
842
|
+
for (const subgraph of subgraphs) {
|
|
843
|
+
// The join spec in fed1 was not including external fields. Let's make sure we had them or we'll get validation
|
|
844
|
+
// errors later.
|
|
845
|
+
addExternalFields(subgraph, supergraph, true);
|
|
846
|
+
removeInactiveProvidesAndRequires(subgraph.schema);
|
|
847
|
+
|
|
848
|
+
removeUnusedTypesFromSubgraph(subgraph.schema);
|
|
849
|
+
|
|
850
|
+
// Lastly, we add all the "executable" directives from the supergraph to each subgraphs, as those may be part
|
|
851
|
+
// of a query and end up in any subgraph fetches. We do this "last" to make sure that if one of the directive
|
|
852
|
+
// use a type for an argument, that argument exists.
|
|
853
|
+
// Note that we don't bother with non-executable directives at the moment since we've don't extract their
|
|
854
|
+
// applications. It might become something we need later, but we don't so far.
|
|
855
|
+
for (const definition of allExecutableDirectives) {
|
|
856
|
+
// Note that we skip any potentially applied directives in the argument of the copied definition, because as said
|
|
857
|
+
// in the comment above, we haven't copied type-system directives. And so far, we really don't care about those
|
|
858
|
+
// applications.
|
|
859
|
+
copyDirectiveDefinitionToSchema({
|
|
860
|
+
definition,
|
|
861
|
+
schema: subgraph.schema,
|
|
862
|
+
copyDirectiveApplicationsInArguments: false,
|
|
863
|
+
locationFilter: (loc) => isExecutableDirectiveLocation(loc),
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return subgraphs;
|
|
656
869
|
}
|
|
657
870
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
871
|
+
|
|
872
|
+
type AnyField = FieldDefinition<ObjectType | InterfaceType> | InputFieldDefinition;
|
|
873
|
+
|
|
874
|
+
function addSubgraphFieldForFed1(field: AnyField, subgraph: Subgraph, isShareable: boolean, joinFieldArgs?: JoinFieldDirectiveArguments): void {
|
|
875
|
+
const subgraphType = subgraph.schema.type(field.parent.name);
|
|
876
|
+
if (!subgraphType) {
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (field instanceof FieldDefinition) {
|
|
881
|
+
addSubgraphField({
|
|
882
|
+
field,
|
|
883
|
+
subgraph,
|
|
884
|
+
type: subgraphType as ObjectType | InterfaceType,
|
|
885
|
+
isShareable,
|
|
886
|
+
joinFieldArgs,
|
|
887
|
+
});
|
|
671
888
|
} else {
|
|
672
|
-
|
|
889
|
+
addSubgraphInputField({
|
|
890
|
+
field,
|
|
891
|
+
subgraph,
|
|
892
|
+
type: subgraphType as InputObjectType,
|
|
893
|
+
joinFieldArgs,
|
|
894
|
+
});
|
|
673
895
|
}
|
|
674
896
|
}
|
|
675
897
|
|
|
@@ -767,7 +989,12 @@ function addExternalFieldsFromDirectiveFieldSet(
|
|
|
767
989
|
const supergraphField = supergraphType.field(fieldName);
|
|
768
990
|
assert(supergraphField, () => `No field named ${fieldName} found on type ${type.name} in the supergraph`);
|
|
769
991
|
// We're know the parent type of the field exists in the subgraph (it's `type`), so we're guaranteed a field is created.
|
|
770
|
-
const created =
|
|
992
|
+
const created = addSubgraphField({
|
|
993
|
+
field: supergraphField,
|
|
994
|
+
subgraph,
|
|
995
|
+
type,
|
|
996
|
+
isShareable: false,
|
|
997
|
+
});
|
|
771
998
|
if (!forceNonExternal) {
|
|
772
999
|
created.applyDirective(external);
|
|
773
1000
|
}
|
|
@@ -820,3 +1047,30 @@ function maybeUpdateFieldForInterface(toModify: FieldDefinition<ObjectType | Int
|
|
|
820
1047
|
toModify.type = itfField.type!;
|
|
821
1048
|
}
|
|
822
1049
|
}
|
|
1050
|
+
|
|
1051
|
+
function removeUnusedTypesFromSubgraph(schema: Schema) {
|
|
1052
|
+
// We now do an additional path on all types because we sometimes added types to subgraphs without
|
|
1053
|
+
// being sure that the subgraph had the type in the first place (especially with the 0.1 join spec), and because
|
|
1054
|
+
// we later might not have added any fields/members to said type, they may be empty (indicating they clearly
|
|
1055
|
+
// didn't belong to the subgraph in the first) and we need to remove them.
|
|
1056
|
+
// Note that need to do this _after_ the `addExternalFields` call above since it may have added (external) fields
|
|
1057
|
+
// to some of the types.
|
|
1058
|
+
for (const type of schema.types()) {
|
|
1059
|
+
switch (type.kind) {
|
|
1060
|
+
case 'ObjectType':
|
|
1061
|
+
case 'InterfaceType':
|
|
1062
|
+
case 'InputObjectType':
|
|
1063
|
+
if (!type.hasFields()) {
|
|
1064
|
+
// Note that we have to use removeRecursive or this could leave the subgraph invalid. But if the
|
|
1065
|
+
// type was not in this subgraphs, nothing that depends on it should be either.
|
|
1066
|
+
type.removeRecursive();
|
|
1067
|
+
}
|
|
1068
|
+
break;
|
|
1069
|
+
case 'UnionType':
|
|
1070
|
+
if (type.membersCount() === 0) {
|
|
1071
|
+
type.removeRecursive();
|
|
1072
|
+
}
|
|
1073
|
+
break;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|