@wundergraph/composition 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ const constants_1 = require("../utils/constants");
16
16
  const normalization_factory_1 = require("../normalization/normalization-factory");
17
17
  class FederationFactory {
18
18
  abstractToConcreteTypeNames = new Map();
19
+ areFieldsExternal = false;
19
20
  areFieldsShareable = false;
20
21
  argumentTypeNameSet = new Set();
21
22
  argumentConfigurations = [];
@@ -38,11 +39,13 @@ class FederationFactory {
38
39
  isCurrentParentExtensionType = false;
39
40
  isParentRootType = false;
40
41
  isParentInputObject = false;
42
+ keyFieldsByParentTypeName = new Map();
41
43
  outputFieldTypeNameSet = new Set();
42
44
  parents = new Map();
43
45
  rootTypeNames = new Set([string_constants_1.DEFAULT_MUTATION, string_constants_1.DEFAULT_QUERY, string_constants_1.DEFAULT_SUBSCRIPTION]);
44
46
  subgraphs = [];
45
47
  shareableErrorTypeNames = new Map();
48
+ evaluatedObjectLikesBySubgraph = new Map();
46
49
  constructor(subgraphs) {
47
50
  this.subgraphs = subgraphs;
48
51
  }
@@ -70,12 +73,6 @@ class FederationFactory {
70
73
  (0, subgraph_1.walkSubgraphToCollectFields)(this, subgraph);
71
74
  }
72
75
  }
73
- isFieldEntityKey(parent) {
74
- if (parent.kind === graphql_1.Kind.OBJECT_TYPE_DEFINITION || parent.kind === graphql_1.Kind.OBJECT_TYPE_EXTENSION) {
75
- return parent.entityKeys.has(this.childName);
76
- }
77
- return false;
78
- }
79
76
  getEnumMergeMethod(enumName) {
80
77
  if (this.inputFieldTypeNameSet.has(enumName) || this.argumentTypeNameSet.has(enumName)) {
81
78
  if (this.outputFieldTypeNameSet.has(enumName)) {
@@ -141,6 +138,7 @@ class FederationFactory {
141
138
  const existingArgumentContainer = argumentMap.get(argName);
142
139
  if (!existingArgumentContainer) {
143
140
  argumentMap.set(argName, {
141
+ directives: this.extractPersistedDirectives(argumentNode.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
144
142
  includeDefaultValue: !!argumentNode.defaultValue,
145
143
  node: (0, ast_1.inputValueDefinitionNodeToMutable)(argumentNode, this.childName),
146
144
  requiredSubgraphs: this.upsertRequiredSubgraph(new Set(), isRequired),
@@ -148,6 +146,7 @@ class FederationFactory {
148
146
  });
149
147
  continue;
150
148
  }
149
+ this.extractPersistedDirectives(argumentNode.directives || [], existingArgumentContainer.directives);
151
150
  (0, utils_1.setLongestDescriptionForNode)(existingArgumentContainer.node, argumentNode.description);
152
151
  this.upsertRequiredSubgraph(existingArgumentContainer.requiredSubgraphs, isRequired);
153
152
  existingArgumentContainer.subgraphs.add(this.currentSubgraphName);
@@ -165,11 +164,22 @@ class FederationFactory {
165
164
  }
166
165
  return argumentMap;
167
166
  }
167
+ isFieldEntityKey() {
168
+ const parent = this.keyFieldsByParentTypeName.get(this.parentTypeName);
169
+ if (parent) {
170
+ return parent.has(this.childName);
171
+ }
172
+ return false;
173
+ }
174
+ isFieldExternal(node) {
175
+ return this.areFieldsExternal || (0, utils_1.isNodeExternal)(node);
176
+ }
168
177
  isFieldShareable(node, parent) {
169
- return (!this.isCurrentSubgraphVersionTwo ||
170
- this.areFieldsShareable ||
171
- (0, utils_1.isNodeShareable)(node) ||
172
- (this.isCurrentParentEntity && this.isFieldEntityKey(parent)));
178
+ return (!this.isCurrentSubgraphVersionTwo
179
+ || this.areFieldsShareable
180
+ || this.isFieldEntityKey()
181
+ || (0, utils_1.isNodeShareable)(node)
182
+ || (0, utils_1.isNodeOverridden)(node));
173
183
  }
174
184
  upsertDirectiveNode(node) {
175
185
  const directiveName = node.name.value;
@@ -199,6 +209,27 @@ class FederationFactory {
199
209
  this.executableDirectives.add(directiveName);
200
210
  }
201
211
  }
212
+ areAllFieldInstancesExternalOrShareable(fieldContainer) {
213
+ let shareableFields = 0;
214
+ let unshareableFields = 0;
215
+ for (const [subgraphName, isShareable] of fieldContainer.subgraphsByShareable) {
216
+ if (isShareable) {
217
+ shareableFields += 1;
218
+ if (shareableFields && unshareableFields) {
219
+ return false;
220
+ }
221
+ continue;
222
+ }
223
+ if (fieldContainer.subgraphsByExternal.get(subgraphName)) {
224
+ continue;
225
+ }
226
+ unshareableFields += 1;
227
+ if (shareableFields || unshareableFields > 1) {
228
+ return false;
229
+ }
230
+ }
231
+ return true;
232
+ }
202
233
  upsertFieldNode(node) {
203
234
  const parent = this.isCurrentParentExtensionType
204
235
  ? (0, utils_3.getOrThrowError)(this.extensions, this.parentTypeName, string_constants_1.EXTENSIONS)
@@ -209,6 +240,7 @@ class FederationFactory {
209
240
  throw (0, errors_1.unexpectedKindFatalError)(this.parentTypeName);
210
241
  }
211
242
  const fieldMap = parent.fields;
243
+ const isFieldExternal = this.isFieldExternal(node);
212
244
  const isFieldShareable = this.isFieldShareable(node, parent);
213
245
  const fieldPath = `${this.parentTypeName}.${this.childName}`;
214
246
  const fieldRootTypeName = (0, type_merging_1.getNamedTypeForChild)(fieldPath, node.type);
@@ -218,6 +250,7 @@ class FederationFactory {
218
250
  (0, utils_1.setLongestDescriptionForNode)(existingFieldContainer.node, node.description);
219
251
  existingFieldContainer.subgraphs.add(this.currentSubgraphName);
220
252
  existingFieldContainer.subgraphsByShareable.set(this.currentSubgraphName, isFieldShareable);
253
+ existingFieldContainer.subgraphsByExternal.set(this.currentSubgraphName, isFieldExternal);
221
254
  const { typeErrors, typeNode } = (0, type_merging_1.getLeastRestrictiveMergedTypeNode)(existingFieldContainer.node.type, node.type, this.parentTypeName, this.childName);
222
255
  if (typeNode) {
223
256
  existingFieldContainer.node.type = typeNode;
@@ -229,30 +262,38 @@ class FederationFactory {
229
262
  this.errors.push((0, errors_1.incompatibleChildTypesError)(this.parentTypeName, this.childName, typeErrors[0], typeErrors[1]));
230
263
  }
231
264
  this.upsertArguments(node, existingFieldContainer.arguments);
232
- // If the parent is not an interface and both fields are not shareable, is it is a shareable error
233
- if (!this.isCurrentParentInterface && (!existingFieldContainer.isShareable || !isFieldShareable)) {
234
- const shareableErrorTypeNames = this.shareableErrorTypeNames.get(this.parentTypeName);
235
- if (shareableErrorTypeNames) {
236
- shareableErrorTypeNames.add(this.childName);
237
- }
238
- else {
239
- this.shareableErrorTypeNames.set(this.parentTypeName, new Set([this.childName]));
240
- }
265
+ /* A field is valid if one of the following is true:
266
+ 1. The field is an interface
267
+ 2. The field is external
268
+ 3. The existing fields AND the current field are ALL shareable
269
+ 4. All other fields besides the current field are external
270
+ */
271
+ if (this.isCurrentParentInterface
272
+ || isFieldExternal
273
+ || (existingFieldContainer.isShareable && isFieldShareable)
274
+ || this.areAllFieldInstancesExternalOrShareable(existingFieldContainer)) {
275
+ return;
276
+ }
277
+ const shareableErrorTypeNames = this.shareableErrorTypeNames.get(this.parentTypeName);
278
+ if (shareableErrorTypeNames) {
279
+ shareableErrorTypeNames.add(this.childName);
241
280
  }
281
+ else {
282
+ this.shareableErrorTypeNames.set(this.parentTypeName, new Set([this.childName]));
283
+ }
284
+ // }
242
285
  return;
243
286
  }
244
287
  this.outputFieldTypeNameSet.add(fieldRootTypeName);
245
288
  fieldMap.set(this.childName, {
246
289
  arguments: this.upsertArguments(node, new Map()),
247
- directives: this.extractPersistedDirectives(node.directives || [], {
248
- directives: new Map(),
249
- tags: new Map(),
250
- }),
290
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
251
291
  isShareable: isFieldShareable,
252
292
  node: (0, ast_1.fieldDefinitionNodeToMutable)(node, this.parentTypeName),
253
293
  namedTypeName: fieldRootTypeName,
254
294
  subgraphs: new Set([this.currentSubgraphName]),
255
295
  subgraphsByShareable: new Map([[this.currentSubgraphName, isFieldShareable]]),
296
+ subgraphsByExternal: new Map([[this.currentSubgraphName, isFieldExternal]]),
256
297
  });
257
298
  }
258
299
  upsertValueNode(node) {
@@ -276,10 +317,7 @@ class FederationFactory {
276
317
  }
277
318
  enumValues.set(this.childName, {
278
319
  appearances: 1,
279
- directives: this.extractPersistedDirectives(node.directives || [], {
280
- directives: new Map(),
281
- tags: new Map(),
282
- }),
320
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
283
321
  node: (0, ast_1.enumValueDefinitionNodeToMutable)(node),
284
322
  });
285
323
  return;
@@ -314,10 +352,7 @@ class FederationFactory {
314
352
  this.inputFieldTypeNameSet.add(inputValueNamedType);
315
353
  inputValues.set(this.childName, {
316
354
  appearances: 1,
317
- directives: this.extractPersistedDirectives(node.directives || [], {
318
- directives: new Map(),
319
- tags: new Map(),
320
- }),
355
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
321
356
  includeDefaultValue: !!node.defaultValue,
322
357
  node: (0, ast_1.inputValueDefinitionNodeToMutable)(node, this.parentTypeName),
323
358
  });
@@ -344,10 +379,7 @@ class FederationFactory {
344
379
  }
345
380
  this.parents.set(parentTypeName, {
346
381
  appearances: 1,
347
- directives: this.extractPersistedDirectives(node.directives || [], {
348
- directives: new Map(),
349
- tags: new Map(),
350
- }),
382
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
351
383
  values: new Map(),
352
384
  kind: node.kind,
353
385
  node: (0, ast_1.enumTypeDefinitionNodeToMutable)(node),
@@ -363,10 +395,7 @@ class FederationFactory {
363
395
  }
364
396
  this.parents.set(parentTypeName, {
365
397
  appearances: 1,
366
- directives: this.extractPersistedDirectives(node.directives || [], {
367
- directives: new Map(),
368
- tags: new Map(),
369
- }),
398
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
370
399
  fields: new Map(),
371
400
  kind: node.kind,
372
401
  node: (0, ast_1.inputObjectTypeDefinitionNodeToMutable)(node),
@@ -384,10 +413,7 @@ class FederationFactory {
384
413
  const nestedInterfaces = new Set();
385
414
  (0, utils_1.extractInterfaces)(node, nestedInterfaces);
386
415
  this.parents.set(parentTypeName, {
387
- directives: this.extractPersistedDirectives(node.directives || [], {
388
- directives: new Map(),
389
- tags: new Map(),
390
- }),
416
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
391
417
  fields: new Map(),
392
418
  interfaces: nestedInterfaces,
393
419
  kind: node.kind,
@@ -403,10 +429,7 @@ class FederationFactory {
403
429
  return;
404
430
  }
405
431
  this.parents.set(parentTypeName, {
406
- directives: this.extractPersistedDirectives(node.directives || [], {
407
- directives: new Map(),
408
- tags: new Map(),
409
- }),
432
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
410
433
  kind: node.kind,
411
434
  node: (0, ast_1.scalarTypeDefinitionNodeToMutable)(node),
412
435
  });
@@ -426,10 +449,7 @@ class FederationFactory {
426
449
  const entityKeys = new Set();
427
450
  (0, utils_1.extractEntityKeys)(node, entityKeys, this.errors);
428
451
  this.parents.set(parentTypeName, {
429
- directives: this.extractPersistedDirectives(node.directives || [], {
430
- directives: new Map(),
431
- tags: new Map(),
432
- }),
452
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
433
453
  fields: new Map(),
434
454
  entityKeys,
435
455
  interfaces,
@@ -452,10 +472,7 @@ class FederationFactory {
452
472
  return;
453
473
  }
454
474
  this.parents.set(parentTypeName, {
455
- directives: this.extractPersistedDirectives(node.directives || [], {
456
- directives: new Map(),
457
- tags: new Map(),
458
- }),
475
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
459
476
  kind: node.kind,
460
477
  members: new Set(node.types?.map((member) => member.name.value)),
461
478
  node: (0, ast_1.unionTypeDefinitionNodeToMutable)(node),
@@ -478,10 +495,7 @@ class FederationFactory {
478
495
  const interfaces = (0, utils_1.extractInterfaces)(node, new Set());
479
496
  const entityKeys = (0, utils_1.extractEntityKeys)(node, new Set(), this.errors);
480
497
  this.extensions.set(this.parentTypeName, {
481
- directives: this.extractPersistedDirectives(node.directives || [], {
482
- directives: new Map(),
483
- tags: new Map(),
484
- }),
498
+ directives: this.extractPersistedDirectives(node.directives || [], (0, utils_2.newPersistedDirectivesContainer)()),
485
499
  entityKeys,
486
500
  fields: new Map(),
487
501
  interfaces,
@@ -621,7 +635,7 @@ class FederationFactory {
621
635
  errors.push({
622
636
  argumentName,
623
637
  missingSubgraphs,
624
- requiredSubgraphs: [...argumentContainer.requiredSubgraphs]
638
+ requiredSubgraphs: [...argumentContainer.requiredSubgraphs],
625
639
  });
626
640
  }
627
641
  // If the argument is always optional, but it's not defined in all subgraphs that define the field,
@@ -630,7 +644,7 @@ class FederationFactory {
630
644
  }
631
645
  argumentContainer.node.defaultValue = argumentContainer.includeDefaultValue
632
646
  ? argumentContainer.node.defaultValue : undefined;
633
- args.push(argumentContainer.node);
647
+ args.push((0, utils_1.pushPersistedDirectivesAndGetNode)(argumentContainer));
634
648
  if (argumentNames) {
635
649
  argumentNames.push(argumentName);
636
650
  }
@@ -662,7 +676,7 @@ class FederationFactory {
662
676
  if (!fieldContainer.arguments) {
663
677
  return fieldContainer.node;
664
678
  }
665
- (0, utils_1.pushPersistedDirectivesToNode)(fieldContainer);
679
+ (0, utils_1.pushPersistedDirectivesAndGetNode)(fieldContainer);
666
680
  const fieldName = fieldContainer.node.name.value;
667
681
  const fieldPath = `${parentTypeName}.${fieldName}`;
668
682
  const args = [];
@@ -676,12 +690,34 @@ class FederationFactory {
676
690
  this.argumentConfigurations.push({
677
691
  argumentNames,
678
692
  fieldName,
679
- typeName: parentTypeName
693
+ typeName: parentTypeName,
680
694
  });
681
695
  }
682
696
  fieldContainer.node.arguments = args;
683
697
  return fieldContainer.node;
684
698
  }
699
+ // the deprecated directive with the longest reason is kept
700
+ upsertDeprecatedDirective(directive, deprecatedDirectiveContainer) {
701
+ if (!directive.arguments || directive.arguments.length < 1) {
702
+ deprecatedDirectiveContainer.directive = directive;
703
+ return;
704
+ }
705
+ if (directive.arguments.length !== 1) {
706
+ this.errors.push(errors_1.invalidDeprecatedDirectiveError);
707
+ return;
708
+ }
709
+ const reasonArgument = directive.arguments[0].value;
710
+ if (reasonArgument.kind !== graphql_1.Kind.STRING) {
711
+ this.errors.push(errors_1.invalidDeprecatedDirectiveError);
712
+ return;
713
+ }
714
+ if (deprecatedDirectiveContainer.reason
715
+ && reasonArgument.value.length < deprecatedDirectiveContainer.reason.length) {
716
+ return;
717
+ }
718
+ deprecatedDirectiveContainer.reason = reasonArgument.value;
719
+ deprecatedDirectiveContainer.directive = directive;
720
+ }
685
721
  // tags with the same name string are merged
686
722
  mergeTagDirectives(directive, map) {
687
723
  // the directive has been validated in the normalizer
@@ -705,6 +741,10 @@ class FederationFactory {
705
741
  if (!this.persistedDirectives.has(directiveName)) {
706
742
  continue;
707
743
  }
744
+ if (directiveName == string_constants_1.DEPRECATED) {
745
+ this.upsertDeprecatedDirective(directive, container.deprecated);
746
+ continue;
747
+ }
708
748
  if (directiveName === string_constants_1.TAG) {
709
749
  this.mergeTagDirectives(directive, container.tags);
710
750
  continue;
@@ -723,13 +763,13 @@ class FederationFactory {
723
763
  }
724
764
  return container;
725
765
  }
726
- entityAncestor(entityAncestors, fieldSubgraphs, parentTypeName) {
766
+ isFieldResolvableByEntityAncestor(entityAncestors, fieldSubgraphs, parentTypeName) {
727
767
  if (!this.graph.hasNode(parentTypeName)) {
728
768
  return false;
729
769
  }
730
770
  for (const entityAncestorName of entityAncestors) {
731
771
  const path = `${entityAncestorName}.${parentTypeName}`;
732
- if (this.graphPaths.get(path)) {
772
+ if (entityAncestorName !== parentTypeName && this.graphPaths.get(path)) {
733
773
  return true;
734
774
  }
735
775
  if (entityAncestorName === parentTypeName) {
@@ -745,11 +785,67 @@ class FederationFactory {
745
785
  }
746
786
  return false;
747
787
  }
788
+ shouldEvaluateObjectLike(rootTypeFieldSubgraphs, parentTypeName) {
789
+ for (const subgraph of rootTypeFieldSubgraphs) {
790
+ const evaluatedObjectLikes = this.evaluatedObjectLikesBySubgraph.get(subgraph);
791
+ if (evaluatedObjectLikes && evaluatedObjectLikes.has(parentTypeName)) {
792
+ continue;
793
+ }
794
+ return true;
795
+ }
796
+ return false;
797
+ }
798
+ isFieldExternalInAllMutualSubgraphs(subgraphs, fieldContainer) {
799
+ const mutualSubgraphs = (0, utils_3.getAllMutualEntries)(subgraphs, fieldContainer.subgraphs);
800
+ if (mutualSubgraphs.size < 1) {
801
+ return false;
802
+ }
803
+ for (const mutualSubgraph of mutualSubgraphs) {
804
+ const isExternal = fieldContainer.subgraphsByExternal.get(mutualSubgraph);
805
+ if (isExternal) {
806
+ continue;
807
+ }
808
+ return false;
809
+ }
810
+ return true;
811
+ }
812
+ updateEvaluatedSubgraphOccurrences(rootTypeFieldSubgraphs, objectSubgraphs, entityAncestors, parentTypeName) {
813
+ const mutualSubgraphs = (0, utils_3.getAllMutualEntries)(rootTypeFieldSubgraphs, objectSubgraphs);
814
+ if (mutualSubgraphs.size > 0) {
815
+ for (const mutualSubgraph of mutualSubgraphs) {
816
+ const evaluatedObjects = this.evaluatedObjectLikesBySubgraph.get(mutualSubgraph);
817
+ if (evaluatedObjects) {
818
+ evaluatedObjects.add(parentTypeName);
819
+ }
820
+ else {
821
+ this.evaluatedObjectLikesBySubgraph.set(mutualSubgraph, new Set([parentTypeName]));
822
+ }
823
+ }
824
+ }
825
+ for (const entityAncestor of entityAncestors) {
826
+ const entityContainer = (0, utils_3.getOrThrowError)(this.parents, entityAncestor, string_constants_1.PARENTS);
827
+ const mutualEntityAncestorRootTypeFieldSubgraphs = (0, utils_3.getAllMutualEntries)(rootTypeFieldSubgraphs, entityContainer.subgraphs);
828
+ const mutualEntityAncestorSubgraphs = (0, utils_3.getAllMutualEntries)(mutualEntityAncestorRootTypeFieldSubgraphs, objectSubgraphs);
829
+ for (const mutualSubgraph of mutualEntityAncestorSubgraphs) {
830
+ const objects = this.evaluatedObjectLikesBySubgraph.get(mutualSubgraph);
831
+ if (objects) {
832
+ objects.add(parentTypeName);
833
+ }
834
+ else {
835
+ this.evaluatedObjectLikesBySubgraph.set(mutualSubgraph, new Set([parentTypeName]));
836
+ }
837
+ }
838
+ }
839
+ }
748
840
  evaluateResolvabilityOfObject(parentContainer, rootTypeFieldData, currentFieldPath, evaluatedObjectLikes, entityAncestors, isParentAbstract = false) {
749
841
  const parentTypeName = parentContainer.node.name.value;
750
842
  if (evaluatedObjectLikes.has(parentTypeName)) {
751
843
  return;
752
844
  }
845
+ if (!this.shouldEvaluateObjectLike(rootTypeFieldData.subgraphs, parentTypeName)) {
846
+ evaluatedObjectLikes.add(parentTypeName);
847
+ return;
848
+ }
753
849
  for (const [fieldName, fieldContainer] of parentContainer.fields) {
754
850
  const fieldNamedTypeName = fieldContainer.namedTypeName;
755
851
  if (string_constants_1.ROOT_TYPES.has(fieldNamedTypeName)) {
@@ -759,10 +855,16 @@ class FederationFactory {
759
855
  if (evaluatedObjectLikes.has(fieldNamedTypeName)) {
760
856
  continue;
761
857
  }
762
- const isFieldResolvable = (0, utils_3.doSetsHaveAnyOverlap)(rootTypeFieldData.subgraphs, fieldContainer.subgraphs);
858
+ if (this.isFieldExternalInAllMutualSubgraphs(rootTypeFieldData.subgraphs, fieldContainer)) {
859
+ continue;
860
+ }
861
+ this.updateEvaluatedSubgraphOccurrences(rootTypeFieldData.subgraphs, parentContainer.subgraphs, entityAncestors, parentTypeName);
862
+ evaluatedObjectLikes.add(parentTypeName);
863
+ const isFieldResolvable = (0, utils_3.doSetsHaveAnyOverlap)(rootTypeFieldData.subgraphs, fieldContainer.subgraphs)
864
+ || this.isFieldResolvableByEntityAncestor(entityAncestors, fieldContainer.subgraphs, parentTypeName);
763
865
  const newCurrentFieldPath = currentFieldPath + (isParentAbstract ? ' ' : '.') + fieldName;
764
866
  const entity = this.entities.get(fieldNamedTypeName);
765
- if (isFieldResolvable || this.entityAncestor(entityAncestors, fieldContainer.subgraphs, parentTypeName)) {
867
+ if (isFieldResolvable) {
766
868
  // The base scalars are not in this.parentMap
767
869
  if (constants_1.BASE_SCALARS.has(fieldNamedTypeName)) {
768
870
  continue;
@@ -774,12 +876,12 @@ class FederationFactory {
774
876
  case graphql_1.Kind.SCALAR_TYPE_DEFINITION:
775
877
  continue;
776
878
  case graphql_1.Kind.OBJECT_TYPE_DEFINITION:
777
- this.evaluateResolvabilityOfObject(childContainer, rootTypeFieldData, newCurrentFieldPath, new Set([...evaluatedObjectLikes, parentTypeName]), entity ? [...entityAncestors, fieldNamedTypeName] : [...entityAncestors]);
879
+ this.evaluateResolvabilityOfObject(childContainer, rootTypeFieldData, newCurrentFieldPath, evaluatedObjectLikes, entity ? [...entityAncestors, fieldNamedTypeName] : [...entityAncestors]);
778
880
  continue;
779
881
  case graphql_1.Kind.INTERFACE_TYPE_DEFINITION:
780
882
  // intentional fallthrough
781
883
  case graphql_1.Kind.UNION_TYPE_DEFINITION:
782
- this.evaluateResolvabilityOfAbstractType(fieldNamedTypeName, childContainer.kind, rootTypeFieldData, newCurrentFieldPath, new Set([...evaluatedObjectLikes, parentTypeName]), entity ? [...entityAncestors, fieldNamedTypeName] : [...entityAncestors], fieldContainer.subgraphs);
884
+ this.evaluateResolvabilityOfAbstractType(fieldNamedTypeName, childContainer.kind, rootTypeFieldData, newCurrentFieldPath, evaluatedObjectLikes, entity ? [...entityAncestors, fieldNamedTypeName] : [...entityAncestors], fieldContainer.subgraphs);
783
885
  continue;
784
886
  default:
785
887
  this.errors.push((0, errors_1.unexpectedObjectResponseType)(newCurrentFieldPath, (0, utils_3.kindToTypeString)(childContainer.kind)));
@@ -809,13 +911,14 @@ class FederationFactory {
809
911
  }
810
912
  }
811
913
  }
812
- evaluateResolvabilityOfAbstractType(fieldNamedTypeName, fieldNamedTypeKind, rootTypeFieldData, currentFieldPath, evaluatedObjectLikes, entityAncestors, parentSubgraphs) {
813
- if (evaluatedObjectLikes.has(fieldNamedTypeName)) {
914
+ evaluateResolvabilityOfAbstractType(abstractTypeName, abstractKind, rootTypeFieldData, currentFieldPath, evaluatedObjectLikes, entityAncestors, parentSubgraphs) {
915
+ if (evaluatedObjectLikes.has(abstractTypeName)) {
814
916
  return;
815
917
  }
816
- const concreteTypeNames = this.abstractToConcreteTypeNames.get(fieldNamedTypeName);
918
+ evaluatedObjectLikes.add(abstractTypeName);
919
+ const concreteTypeNames = this.abstractToConcreteTypeNames.get(abstractTypeName);
817
920
  if (!concreteTypeNames) {
818
- (0, errors_1.noConcreteTypesForAbstractTypeError)((0, utils_3.kindToTypeString)(fieldNamedTypeKind), fieldNamedTypeName);
921
+ (0, errors_1.noConcreteTypesForAbstractTypeError)((0, utils_3.kindToTypeString)(abstractKind), abstractTypeName);
819
922
  return;
820
923
  }
821
924
  for (const concreteTypeName of concreteTypeNames) {
@@ -824,14 +927,14 @@ class FederationFactory {
824
927
  }
825
928
  const concreteParentContainer = (0, utils_3.getOrThrowError)(this.parents, concreteTypeName, string_constants_1.PARENTS);
826
929
  if (concreteParentContainer.kind !== graphql_1.Kind.OBJECT_TYPE_DEFINITION) {
827
- // throw
828
- continue;
930
+ throw (0, errors_1.unexpectedParentKindErrorMessage)(concreteTypeName, 'Object', (0, utils_3.kindToTypeString)(concreteParentContainer.kind));
829
931
  }
932
+ // If the concrete type is unreachable through the abstract type, it is not an error
830
933
  if (!(0, utils_3.doSetsHaveAnyOverlap)(concreteParentContainer.subgraphs, parentSubgraphs)) {
831
934
  continue;
832
935
  }
833
936
  const entity = this.entities.get(concreteTypeName);
834
- this.evaluateResolvabilityOfObject(concreteParentContainer, rootTypeFieldData, currentFieldPath + ` ... on ` + concreteTypeName, new Set([...evaluatedObjectLikes, fieldNamedTypeName]), entity ? [...entityAncestors, concreteTypeName] : [...entityAncestors], true);
937
+ this.evaluateResolvabilityOfObject(concreteParentContainer, rootTypeFieldData, currentFieldPath + ` ... on ` + concreteTypeName, evaluatedObjectLikes, entity ? [...entityAncestors, concreteTypeName] : [...entityAncestors], true);
835
938
  }
836
939
  }
837
940
  federate() {
@@ -840,6 +943,7 @@ class FederationFactory {
840
943
  for (const subgraph of this.subgraphs) {
841
944
  this.isCurrentSubgraphVersionTwo = subgraph.isVersionTwo;
842
945
  this.currentSubgraphName = subgraph.name;
946
+ this.keyFieldsByParentTypeName = subgraph.keyFieldsByParentTypeName;
843
947
  (0, subgraph_1.walkSubgraphToFederate)(subgraph.definitions, factory);
844
948
  }
845
949
  const definitions = [];
@@ -900,7 +1004,7 @@ class FederationFactory {
900
1004
  const values = [];
901
1005
  const mergeMethod = this.getEnumMergeMethod(parentTypeName);
902
1006
  for (const enumValueContainer of parentContainer.values.values()) {
903
- (0, utils_1.pushPersistedDirectivesToNode)(enumValueContainer);
1007
+ (0, utils_1.pushPersistedDirectivesAndGetNode)(enumValueContainer);
904
1008
  switch (mergeMethod) {
905
1009
  case utils_2.MergeMethod.CONSISTENT:
906
1010
  if (enumValueContainer.appearances < parentContainer.appearances) {
@@ -919,12 +1023,12 @@ class FederationFactory {
919
1023
  }
920
1024
  }
921
1025
  parentContainer.node.values = values;
922
- definitions.push((0, utils_1.getNodeWithPersistedDirectives)(parentContainer));
1026
+ definitions.push((0, utils_1.pushPersistedDirectivesAndGetNode)(parentContainer));
923
1027
  break;
924
1028
  case graphql_1.Kind.INPUT_OBJECT_TYPE_DEFINITION:
925
1029
  const inputValues = [];
926
1030
  for (const inputValueContainer of parentContainer.fields.values()) {
927
- (0, utils_1.pushPersistedDirectivesToNode)(inputValueContainer);
1031
+ (0, utils_1.pushPersistedDirectivesAndGetNode)(inputValueContainer);
928
1032
  if (parentContainer.appearances === inputValueContainer.appearances) {
929
1033
  inputValues.push(inputValueContainer.node);
930
1034
  }
@@ -934,7 +1038,7 @@ class FederationFactory {
934
1038
  }
935
1039
  }
936
1040
  parentContainer.node.fields = inputValues;
937
- definitions.push((0, utils_1.getNodeWithPersistedDirectives)(parentContainer));
1041
+ definitions.push((0, utils_1.pushPersistedDirectivesAndGetNode)(parentContainer));
938
1042
  break;
939
1043
  case graphql_1.Kind.INTERFACE_TYPE_DEFINITION:
940
1044
  const interfaceFields = [];
@@ -942,7 +1046,7 @@ class FederationFactory {
942
1046
  interfaceFields.push(this.getMergedFieldDefinitionNode(fieldContainer, parentTypeName));
943
1047
  }
944
1048
  parentContainer.node.fields = interfaceFields;
945
- (0, utils_1.pushPersistedDirectivesToNode)(parentContainer);
1049
+ (0, utils_1.pushPersistedDirectivesAndGetNode)(parentContainer);
946
1050
  // Interface implementations can only be evaluated after they've been fully merged
947
1051
  if (parentContainer.interfaces.size > 0) {
948
1052
  objectLikeContainersWithInterfaces.push(parentContainer);
@@ -957,7 +1061,7 @@ class FederationFactory {
957
1061
  fields.push(this.getMergedFieldDefinitionNode(fieldContainer, parentTypeName));
958
1062
  }
959
1063
  parentContainer.node.fields = fields;
960
- (0, utils_1.pushPersistedDirectivesToNode)(parentContainer);
1064
+ (0, utils_1.pushPersistedDirectivesAndGetNode)(parentContainer);
961
1065
  // Interface implementations can only be evaluated after they've been fully merged
962
1066
  if (parentContainer.interfaces.size > 0) {
963
1067
  objectLikeContainersWithInterfaces.push(parentContainer);
@@ -967,7 +1071,7 @@ class FederationFactory {
967
1071
  }
968
1072
  break;
969
1073
  case graphql_1.Kind.SCALAR_TYPE_DEFINITION:
970
- definitions.push((0, utils_1.getNodeWithPersistedDirectives)(parentContainer));
1074
+ definitions.push((0, utils_1.pushPersistedDirectivesAndGetNode)(parentContainer));
971
1075
  break;
972
1076
  case graphql_1.Kind.UNION_TYPE_DEFINITION:
973
1077
  const types = [];
@@ -975,7 +1079,7 @@ class FederationFactory {
975
1079
  types.push((0, utils_1.stringToNamedTypeNode)(memberName));
976
1080
  }
977
1081
  parentContainer.node.types = types;
978
- definitions.push((0, utils_1.getNodeWithPersistedDirectives)(parentContainer));
1082
+ definitions.push((0, utils_1.pushPersistedDirectivesAndGetNode)(parentContainer));
979
1083
  break;
980
1084
  }
981
1085
  }
@@ -995,11 +1099,18 @@ class FederationFactory {
995
1099
  if (!rootTypeContainer || rootTypeContainer.kind !== graphql_1.Kind.OBJECT_TYPE_DEFINITION) {
996
1100
  continue;
997
1101
  }
1102
+ // After evaluating all of a root type's fields, break and return if there are errors
1103
+ if (this.errors.length > 0) {
1104
+ break;
1105
+ }
998
1106
  // If a root type field returns a Scalar or Enum, track it so that it is not evaluated it again
999
- const evaluatedNamedTypes = new Set(constants_1.BASE_SCALARS);
1107
+ const evaluatedRootScalarsAndEnums = new Set(constants_1.BASE_SCALARS);
1000
1108
  for (const [rootTypeFieldName, rootTypeFieldContainer] of rootTypeContainer.fields) {
1001
1109
  const rootTypeFieldNamedTypeName = rootTypeFieldContainer.namedTypeName;
1002
- if (evaluatedNamedTypes.has(rootTypeFieldNamedTypeName)) {
1110
+ if (evaluatedRootScalarsAndEnums.has(rootTypeFieldNamedTypeName)) {
1111
+ continue;
1112
+ }
1113
+ if (!this.shouldEvaluateObjectLike(rootTypeFieldContainer.subgraphs, rootTypeFieldNamedTypeName)) {
1003
1114
  continue;
1004
1115
  }
1005
1116
  const childContainer = (0, utils_3.getOrThrowError)(this.parents, rootTypeFieldNamedTypeName, string_constants_1.PARENTS);
@@ -1011,22 +1122,21 @@ class FederationFactory {
1011
1122
  typeName: rootTypeName,
1012
1123
  subgraphs: rootTypeFieldContainer.subgraphs,
1013
1124
  };
1014
- const evaluatedObjectLikes = new Set();
1015
1125
  switch (childContainer.kind) {
1016
1126
  case graphql_1.Kind.ENUM_TYPE_DEFINITION:
1017
1127
  // intentional fallthrough
1018
1128
  case graphql_1.Kind.SCALAR_TYPE_DEFINITION:
1019
1129
  // Root type fields whose response type is an Enums and Scalars will always be resolvable
1020
1130
  // Consequently, subsequent checks can be skipped
1021
- evaluatedNamedTypes.add(rootTypeFieldNamedTypeName);
1131
+ evaluatedRootScalarsAndEnums.add(rootTypeFieldNamedTypeName);
1022
1132
  continue;
1023
1133
  case graphql_1.Kind.OBJECT_TYPE_DEFINITION:
1024
- this.evaluateResolvabilityOfObject(childContainer, rootTypeFieldData, fieldPath, evaluatedObjectLikes, this.entities.has(rootTypeFieldNamedTypeName) ? [rootTypeFieldNamedTypeName] : []);
1134
+ this.evaluateResolvabilityOfObject(childContainer, rootTypeFieldData, fieldPath, new Set(), this.entities.has(rootTypeFieldNamedTypeName) ? [rootTypeFieldNamedTypeName] : []);
1025
1135
  continue;
1026
1136
  case graphql_1.Kind.INTERFACE_TYPE_DEFINITION:
1027
1137
  // intentional fallthrough
1028
1138
  case graphql_1.Kind.UNION_TYPE_DEFINITION:
1029
- this.evaluateResolvabilityOfAbstractType(rootTypeFieldNamedTypeName, childContainer.kind, rootTypeFieldData, fieldPath, evaluatedObjectLikes, this.entities.has(rootTypeFieldNamedTypeName) ? [rootTypeFieldNamedTypeName] : [], rootTypeFieldContainer.subgraphs);
1139
+ this.evaluateResolvabilityOfAbstractType(rootTypeFieldNamedTypeName, childContainer.kind, rootTypeFieldData, fieldPath, new Set(), this.entities.has(rootTypeFieldNamedTypeName) ? [rootTypeFieldNamedTypeName] : [], rootTypeFieldContainer.subgraphs);
1030
1140
  continue;
1031
1141
  default:
1032
1142
  this.errors.push((0, errors_1.unexpectedObjectResponseType)(fieldPath, (0, utils_3.kindToTypeString)(childContainer.kind)));
@@ -1045,7 +1155,7 @@ class FederationFactory {
1045
1155
  argumentConfigurations: this.argumentConfigurations,
1046
1156
  federatedGraphAST: newAst,
1047
1157
  federatedGraphSchema: (0, graphql_1.buildASTSchema)(newAst),
1048
- }
1158
+ },
1049
1159
  };
1050
1160
  }
1051
1161
  }
@@ -1079,6 +1189,7 @@ function federateSubgraphs(subgraphs) {
1079
1189
  }
1080
1190
  normalizedSubgraphs.push({
1081
1191
  definitions: normalizationResult.subgraphAST,
1192
+ keyFieldsByParentTypeName: normalizationResult.keyFieldsByParentTypeName,
1082
1193
  isVersionTwo: normalizationResult.isVersionTwo,
1083
1194
  name,
1084
1195
  operationTypes: normalizationResult.operationTypes,