@apollo/federation-internals 2.4.11 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/argumentCompositionStrategies.d.ts +12 -7
  2. package/dist/argumentCompositionStrategies.d.ts.map +1 -1
  3. package/dist/argumentCompositionStrategies.js +26 -7
  4. package/dist/argumentCompositionStrategies.js.map +1 -1
  5. package/dist/authenticatedSpec.d.ts +13 -0
  6. package/dist/authenticatedSpec.d.ts.map +1 -0
  7. package/dist/authenticatedSpec.js +36 -0
  8. package/dist/authenticatedSpec.js.map +1 -0
  9. package/dist/coreSpec.d.ts +6 -5
  10. package/dist/coreSpec.d.ts.map +1 -1
  11. package/dist/coreSpec.js +42 -32
  12. package/dist/coreSpec.js.map +1 -1
  13. package/dist/definitions.d.ts +2 -3
  14. package/dist/definitions.d.ts.map +1 -1
  15. package/dist/definitions.js +12 -7
  16. package/dist/definitions.js.map +1 -1
  17. package/dist/directiveAndTypeSpecification.d.ts +8 -8
  18. package/dist/directiveAndTypeSpecification.d.ts.map +1 -1
  19. package/dist/directiveAndTypeSpecification.js +21 -16
  20. package/dist/directiveAndTypeSpecification.js.map +1 -1
  21. package/dist/error.d.ts +1 -1
  22. package/dist/extractSubgraphsFromSupergraph.d.ts +1 -1
  23. package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
  24. package/dist/extractSubgraphsFromSupergraph.js +450 -295
  25. package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
  26. package/dist/federation.d.ts +10 -5
  27. package/dist/federation.d.ts.map +1 -1
  28. package/dist/federation.js +58 -16
  29. package/dist/federation.js.map +1 -1
  30. package/dist/federationSpec.d.ts +3 -1
  31. package/dist/federationSpec.d.ts.map +1 -1
  32. package/dist/federationSpec.js +12 -3
  33. package/dist/federationSpec.js.map +1 -1
  34. package/dist/inaccessibleSpec.d.ts +1 -1
  35. package/dist/inaccessibleSpec.d.ts.map +1 -1
  36. package/dist/inaccessibleSpec.js +4 -4
  37. package/dist/inaccessibleSpec.js.map +1 -1
  38. package/dist/index.d.ts +2 -1
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +2 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/joinSpec.d.ts +19 -17
  43. package/dist/joinSpec.d.ts.map +1 -1
  44. package/dist/joinSpec.js +3 -3
  45. package/dist/joinSpec.js.map +1 -1
  46. package/dist/requiresScopesSpec.d.ts +16 -0
  47. package/dist/requiresScopesSpec.d.ts.map +1 -0
  48. package/dist/requiresScopesSpec.js +55 -0
  49. package/dist/requiresScopesSpec.js.map +1 -0
  50. package/dist/supergraphs.d.ts +19 -4
  51. package/dist/supergraphs.d.ts.map +1 -1
  52. package/dist/supergraphs.js +40 -14
  53. package/dist/supergraphs.js.map +1 -1
  54. package/dist/tagSpec.d.ts +1 -1
  55. package/dist/tagSpec.d.ts.map +1 -1
  56. package/dist/tagSpec.js +4 -4
  57. package/dist/tagSpec.js.map +1 -1
  58. package/dist/utils.d.ts +1 -0
  59. package/dist/utils.d.ts.map +1 -1
  60. package/dist/utils.js +11 -1
  61. package/dist/utils.js.map +1 -1
  62. package/package.json +1 -1
  63. package/src/argumentCompositionStrategies.ts +32 -9
  64. package/src/authenticatedSpec.ts +62 -0
  65. package/src/coreSpec.ts +57 -35
  66. package/src/definitions.ts +22 -9
  67. package/src/directiveAndTypeSpecification.ts +25 -24
  68. package/src/error.ts +2 -2
  69. package/src/extractSubgraphsFromSupergraph.ts +647 -393
  70. package/src/federation.ts +95 -16
  71. package/src/federationSpec.ts +13 -5
  72. package/src/inaccessibleSpec.ts +4 -4
  73. package/src/index.ts +2 -1
  74. package/src/joinSpec.ts +23 -13
  75. package/src/precompute.ts +1 -1
  76. package/src/requiresScopesSpec.ts +76 -0
  77. package/src/supergraphs.ts +64 -16
  78. package/src/tagSpec.ts +4 -4
  79. 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
- // Lastly, we add all the "executable" directives from the supergraph to each subgraphs, as those may be part
515
- // of a query and end up in any subgraph fetches. We do this "last" to make sure that if one of the directive
516
- // use a type for an argument, that argument exists.
517
- // Note that we don't bother with non-executable directives at the moment since we've don't extract their
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
- // TODO: Not sure that code is needed anymore (any field necessary to validate an interface will have been marked
533
- // external)?
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
- // We now make a pass on every field of every interface and check that all implementers do have that field (even if
536
- // external). If not (which can happen because, again, the v0.1 spec had no information on where an interface was
537
- // truly defined, so we've so far added them everywhere with all their fields, but some fields may have been part
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
- try {
560
- subgraph.validate();
561
- } catch (e) {
562
- // This is going to be caught directly by the enclosing try-catch, but this is so we indicate the subgraph having the issue.
563
- throw new SubgraphExtractionError(e, subgraph);
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
- type AnyField = FieldDefinition<ObjectType | InterfaceType> | InputFieldDefinition;
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
- function addSubgraphField(supergraphField: AnyField, subgraph: Subgraph, encodedType?: string): AnyField | undefined {
631
- if (supergraphField instanceof FieldDefinition) {
632
- return addSubgraphObjectOrInterfaceField(supergraphField, subgraph, encodedType);
633
- } else {
634
- return addSubgraphInputField(supergraphField, subgraph, encodedType);
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 addSubgraphObjectOrInterfaceField(
639
- supergraphField: FieldDefinition<ObjectType | InterfaceType>,
630
+ function addSubgraphInputField({
631
+ field,
632
+ type,
633
+ subgraph,
634
+ joinFieldArgs,
635
+ }: {
636
+ field: InputFieldDefinition,
637
+ type: InputObjectType,
640
638
  subgraph: Subgraph,
641
- encodedType?: string
642
- ): FieldDefinition<ObjectType | InterfaceType> | undefined {
643
- const subgraphType = subgraph.schema.type(supergraphField.parent.name);
644
- if (subgraphType) {
645
- const copiedType = encodedType
646
- ? decodeType(encodedType, subgraph.schema, subgraph.name)
647
- : copyType(supergraphField.type!, subgraph.schema, subgraph.name);
648
- const field = (subgraphType as ObjectType | InterfaceType).addField(supergraphField.name, copiedType);
649
- for (const arg of supergraphField.arguments()) {
650
- field.addArgument(arg.name, copyType(arg.type!, subgraph.schema, subgraph.name), arg.defaultValue);
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
- function addSubgraphInputField(
659
- supergraphField: InputFieldDefinition,
660
- subgraph: Subgraph,
661
- encodedType?: string
662
- ): InputFieldDefinition | undefined {
663
- const subgraphType = subgraph.schema.type(supergraphField.parent.name);
664
- if (subgraphType) {
665
- const copiedType = encodedType
666
- ? decodeType(encodedType, subgraph.schema, subgraph.name)
667
- : copyType(supergraphField.type!, subgraph.schema, subgraph.name);
668
- const field = (subgraphType as InputObjectType).addField(supergraphField.name, copiedType);
669
- field.defaultValue = supergraphField.defaultValue
670
- return field
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
- return undefined;
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 = addSubgraphObjectOrInterfaceField(supergraphField, subgraph)!;
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
+ }