@wundergraph/composition 0.29.0 → 0.29.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.
@@ -26,13 +26,13 @@ class FederationFactory {
26
26
  concreteTypeNamesByAbstractTypeName;
27
27
  clientDefinitions = [constants_1.DEPRECATED_DEFINITION];
28
28
  currentSubgraphName = '';
29
+ subgraphNamesByNamedTypeNameByFieldCoordinates = new Map();
29
30
  entityDataByTypeName;
30
31
  entityInterfaceFederationDataByTypeName;
31
32
  errors = [];
32
33
  fieldConfigurationByFieldPath = new Map();
33
- graphEdges = new Set();
34
- graphPaths = new Map();
35
34
  inaccessiblePaths = new Set();
35
+ isMaxDepth = false;
36
36
  internalGraph;
37
37
  internalSubgraphBySubgraphName;
38
38
  invalidOrScopesHostPaths = new Set();
@@ -54,7 +54,6 @@ class FederationFactory {
54
54
  routerDefinitions = [constants_1.DEPRECATED_DEFINITION, constants_1.TAG_DEFINITION];
55
55
  shareableErrorTypeNames = new Map();
56
56
  subscriptionFilterDataByFieldPath = new Map();
57
- isMaxDepth = false;
58
57
  tagNamesByPath = new Map();
59
58
  warnings;
60
59
  constructor(options) {
@@ -390,6 +389,83 @@ class FederationFactory {
390
389
  });
391
390
  }
392
391
  }
392
+ federateOutputType({ current, other, hostPath, mostRestrictive }) {
393
+ other = (0, ast_1.getMutableTypeNode)(other, hostPath, this.errors); // current is already a deep copy
394
+ // The first type of the pair to diverge in restriction takes precedence in all future differences.
395
+ // If the other type of the pair also diverges, it's a src error.
396
+ // To keep the output link intact, it is not possible to spread assign "lastTypeNode".
397
+ const federatedTypeNode = { kind: current.kind };
398
+ let divergentType = type_merging_1.DivergentType.NONE;
399
+ let lastTypeNode = federatedTypeNode;
400
+ for (let i = 0; i < integer_constants_1.MAXIMUM_TYPE_NESTING; i++) {
401
+ if (current.kind === other.kind) {
402
+ switch (current.kind) {
403
+ case graphql_1.Kind.NAMED_TYPE:
404
+ lastTypeNode.kind = current.kind;
405
+ lastTypeNode.name = current.name;
406
+ return { success: true, typeNode: federatedTypeNode };
407
+ case graphql_1.Kind.LIST_TYPE:
408
+ lastTypeNode.kind = current.kind;
409
+ lastTypeNode.type = { kind: current.type.kind };
410
+ lastTypeNode = lastTypeNode.type;
411
+ current = current.type;
412
+ other = other.type;
413
+ continue;
414
+ case graphql_1.Kind.NON_NULL_TYPE:
415
+ lastTypeNode.kind = current.kind;
416
+ lastTypeNode.type = { kind: current.type.kind };
417
+ lastTypeNode = lastTypeNode.type;
418
+ current = current.type;
419
+ other = other.type;
420
+ continue;
421
+ }
422
+ }
423
+ if (current.kind === graphql_1.Kind.NON_NULL_TYPE) {
424
+ if (divergentType === type_merging_1.DivergentType.OTHER) {
425
+ this.errors.push((0, errors_1.incompatibleChildTypesError)(hostPath, current.kind, other.kind));
426
+ return { success: false };
427
+ }
428
+ else {
429
+ divergentType = type_merging_1.DivergentType.CURRENT;
430
+ }
431
+ if (mostRestrictive) {
432
+ lastTypeNode.kind = current.kind;
433
+ lastTypeNode.type = { kind: current.type.kind };
434
+ lastTypeNode = lastTypeNode.type;
435
+ }
436
+ current = current.type;
437
+ continue;
438
+ }
439
+ if (other.kind === graphql_1.Kind.NON_NULL_TYPE) {
440
+ if (divergentType === type_merging_1.DivergentType.CURRENT) {
441
+ this.errors.push((0, errors_1.incompatibleChildTypesError)(hostPath, current.kind, other.kind));
442
+ return { success: false };
443
+ }
444
+ else {
445
+ divergentType = type_merging_1.DivergentType.OTHER;
446
+ }
447
+ if (mostRestrictive) {
448
+ lastTypeNode.kind = other.kind;
449
+ lastTypeNode.type = { kind: other.type.kind };
450
+ lastTypeNode = lastTypeNode.type;
451
+ }
452
+ other = other.type;
453
+ continue;
454
+ }
455
+ // At least one of the types must be a non-null wrapper, or the types are inconsistent
456
+ this.errors.push((0, errors_1.incompatibleChildTypesError)(hostPath, current.kind, other.kind));
457
+ return { success: false };
458
+ }
459
+ this.errors.push((0, errors_1.maximumTypeNestingExceededError)(hostPath));
460
+ return { success: false };
461
+ }
462
+ addSubgraphNameToExistingFieldNamedTypeDisparity(incomingData) {
463
+ const subgraphNamesByNamedTypeName = this.subgraphNamesByNamedTypeNameByFieldCoordinates.get(`${incomingData.renamedParentTypeName}.${incomingData.name}`);
464
+ if (!subgraphNamesByNamedTypeName) {
465
+ return;
466
+ }
467
+ (0, utils_3.addIterableValuesToSet)(incomingData.subgraphNames, (0, utils_3.getValueOrDefault)(subgraphNamesByNamedTypeName, incomingData.namedTypeName, () => new Set()));
468
+ }
393
469
  upsertFieldData(fieldDataByFieldName, incomingData, isParentInaccessible) {
394
470
  const fieldPath = `${incomingData.renamedParentTypeName}.${incomingData.name}`;
395
471
  (0, utils_3.getValueOrDefault)(this.pathsByNamedTypeName, incomingData.namedTypeName, () => new Set()).add(fieldPath);
@@ -431,15 +507,36 @@ class FederationFactory {
431
507
  }
432
508
  return;
433
509
  }
434
- const { typeErrors, typeNode } = (0, type_merging_1.getLeastRestrictiveMergedTypeNode)(existingData.type, incomingData.type, fieldPath, this.errors);
435
- if (typeNode) {
436
- existingData.type = typeNode;
437
- }
438
- else {
439
- if (!typeErrors || typeErrors.length < 2) {
440
- throw (0, errors_1.fieldTypeMergeFatalError)(existingData.name);
510
+ const result = this.federateOutputType({
511
+ current: existingData.type,
512
+ other: incomingData.type,
513
+ hostPath: fieldPath,
514
+ mostRestrictive: false,
515
+ });
516
+ if (result.success) {
517
+ existingData.type = result.typeNode;
518
+ if (existingData.namedTypeName !== incomingData.namedTypeName) {
519
+ const subgraphNamesByNamedTypeName = (0, utils_3.getValueOrDefault)(this.subgraphNamesByNamedTypeNameByFieldCoordinates, `${existingData.renamedParentTypeName}.${existingData.name}`, () => new Map());
520
+ /* Only propagate the subgraph names of the existing data if it has never been propagated before.
521
+ * This is to prevent the propagation of subgraph names where that named type is not returned.
522
+ */
523
+ const existingSubgraphNames = (0, utils_3.getValueOrDefault)(subgraphNamesByNamedTypeName, existingData.namedTypeName, () => new Set());
524
+ if (existingSubgraphNames.size < 1) {
525
+ // Add all subgraph names that are not the subgraph name in the incoming data
526
+ for (const subgraphName of existingData.subgraphNames) {
527
+ if (!incomingData.subgraphNames.has(subgraphName)) {
528
+ existingSubgraphNames.add(subgraphName);
529
+ }
530
+ }
531
+ }
532
+ (0, utils_3.addIterableValuesToSet)(incomingData.subgraphNames, (0, utils_3.getValueOrDefault)(subgraphNamesByNamedTypeName, incomingData.namedTypeName, () => new Set()));
533
+ }
534
+ else {
535
+ /* If the named types match but there has already been a disparity in the named type names returned by the
536
+ * field, add the incoming subgraph name to the existing subgraph name set for that named type name.
537
+ */
538
+ this.addSubgraphNameToExistingFieldNamedTypeDisparity(incomingData);
441
539
  }
442
- this.errors.push((0, errors_1.incompatibleChildTypesError)(fieldPath, typeErrors[0], typeErrors[1]));
443
540
  }
444
541
  for (const [argumentName, inputValueData] of incomingData.argumentDataByArgumentName) {
445
542
  const namedArgumentTypeName = (0, ast_1.getTypeNodeNamedTypeName)(inputValueData.type);
@@ -693,9 +790,151 @@ class FederationFactory {
693
790
  existingData.repeatable &&= incomingData.repeatable;
694
791
  (0, utils_3.addIterableValuesToSet)(incomingData.subgraphNames, existingData.subgraphNames);
695
792
  }
793
+ shouldUpdateFederatedFieldAbstractNamedType(abstractTypeName, objectTypeNames) {
794
+ if (!abstractTypeName) {
795
+ return false;
796
+ }
797
+ const concreteTypeNames = this.concreteTypeNamesByAbstractTypeName.get(abstractTypeName);
798
+ if (!concreteTypeNames || concreteTypeNames.size < 1) {
799
+ return false;
800
+ }
801
+ for (const objectTypeName of objectTypeNames) {
802
+ if (!concreteTypeNames.has(objectTypeName)) {
803
+ return false;
804
+ }
805
+ }
806
+ return true;
807
+ }
808
+ updateTypeNodeNamedType(typeNode, namedTypeName) {
809
+ let lastTypeNode = typeNode;
810
+ for (let i = 0; i < integer_constants_1.MAXIMUM_TYPE_NESTING; i++) {
811
+ if (lastTypeNode.kind === graphql_1.Kind.NAMED_TYPE) {
812
+ lastTypeNode.name = (0, utils_1.stringToNameNode)(namedTypeName);
813
+ return;
814
+ }
815
+ lastTypeNode = lastTypeNode.type;
816
+ }
817
+ }
818
+ handleDisparateFieldNamedTypes() {
819
+ for (const [fieldCoordinates, subgraphNamesByNamedTypeName] of this
820
+ .subgraphNamesByNamedTypeNameByFieldCoordinates) {
821
+ const coordinates = fieldCoordinates.split(string_constants_1.PERIOD);
822
+ if (coordinates.length !== 2) {
823
+ continue;
824
+ }
825
+ const compositeOutputData = this.parentDefinitionDataByTypeName.get(coordinates[0]);
826
+ if (!compositeOutputData) {
827
+ this.errors.push((0, errors_1.undefinedTypeError)(coordinates[0]));
828
+ continue;
829
+ }
830
+ // This error should never happen
831
+ if (compositeOutputData.kind !== graphql_1.Kind.INTERFACE_TYPE_DEFINITION &&
832
+ compositeOutputData.kind !== graphql_1.Kind.OBJECT_TYPE_DEFINITION) {
833
+ this.errors.push((0, errors_1.unexpectedNonCompositeOutputTypeError)(coordinates[0], (0, utils_3.kindToTypeString)(compositeOutputData.kind)));
834
+ continue;
835
+ }
836
+ const fieldData = compositeOutputData.fieldDataByFieldName.get(coordinates[1]);
837
+ // This error should never happen
838
+ if (!fieldData) {
839
+ this.errors.push((0, errors_1.unknownFieldDataError)(fieldCoordinates));
840
+ continue;
841
+ }
842
+ const interfaceDataByTypeName = new Map();
843
+ const objectTypeNames = new Set();
844
+ let unionTypeName = '';
845
+ for (const namedTypeName of subgraphNamesByNamedTypeName.keys()) {
846
+ if (constants_1.BASE_SCALARS.has(namedTypeName)) {
847
+ this.errors.push((0, errors_1.incompatibleFederatedFieldNamedTypeError)(fieldCoordinates, subgraphNamesByNamedTypeName));
848
+ break;
849
+ }
850
+ const namedTypeData = this.parentDefinitionDataByTypeName.get(namedTypeName);
851
+ // This error should never happen
852
+ if (!namedTypeData) {
853
+ this.errors.push((0, errors_1.unknownNamedTypeError)(fieldCoordinates, namedTypeName));
854
+ break;
855
+ }
856
+ switch (namedTypeData.kind) {
857
+ case graphql_1.Kind.INTERFACE_TYPE_DEFINITION: {
858
+ interfaceDataByTypeName.set(namedTypeData.name, namedTypeData);
859
+ break;
860
+ }
861
+ case graphql_1.Kind.OBJECT_TYPE_DEFINITION: {
862
+ objectTypeNames.add(namedTypeData.name);
863
+ /* Multiple shared Field instances can explicitly return the same Object named type across subgraphs.
864
+ * However, the Field is invalid if *any* of the other shared Field instances return a different Object named
865
+ * type, even if each of those Objects named types could be coerced into the same mutual abstract type.
866
+ * This is because it would be impossible to return identical data from each subgraph if one shared Field
867
+ * instance explicitly returns a different Object named type to another shared Field instance.
868
+ */
869
+ if (objectTypeNames.size > 1) {
870
+ this.errors.push((0, errors_1.incompatibleFederatedFieldNamedTypeError)(fieldCoordinates, subgraphNamesByNamedTypeName));
871
+ continue;
872
+ }
873
+ break;
874
+ }
875
+ case graphql_1.Kind.UNION_TYPE_DEFINITION: {
876
+ if (unionTypeName) {
877
+ this.errors.push((0, errors_1.incompatibleFederatedFieldNamedTypeError)(fieldCoordinates, subgraphNamesByNamedTypeName));
878
+ continue;
879
+ }
880
+ unionTypeName = namedTypeName;
881
+ break;
882
+ }
883
+ default: {
884
+ this.errors.push((0, errors_1.incompatibleFederatedFieldNamedTypeError)(fieldCoordinates, subgraphNamesByNamedTypeName));
885
+ break;
886
+ }
887
+ }
888
+ }
889
+ if (interfaceDataByTypeName.size < 0 && !unionTypeName) {
890
+ this.errors.push((0, errors_1.incompatibleFederatedFieldNamedTypeError)(fieldCoordinates, subgraphNamesByNamedTypeName));
891
+ continue;
892
+ }
893
+ /* Default to the Union type name.
894
+ * If more than one type of abstract type is returned, an error will be propagated.
895
+ */
896
+ let abstractTypeName = unionTypeName;
897
+ if (interfaceDataByTypeName.size > 0) {
898
+ if (unionTypeName) {
899
+ this.errors.push((0, errors_1.incompatibleFederatedFieldNamedTypeError)(fieldCoordinates, subgraphNamesByNamedTypeName));
900
+ continue;
901
+ }
902
+ /* If there is more than one Interface, there must be an origin Interface.
903
+ * This is the "mutual Interface" that all the other Interfaces implement.
904
+ */
905
+ for (const interfaceTypeName of interfaceDataByTypeName.keys()) {
906
+ abstractTypeName = interfaceTypeName;
907
+ for (const [comparisonTypeName, comparisonData] of interfaceDataByTypeName) {
908
+ if (interfaceTypeName === comparisonTypeName) {
909
+ continue;
910
+ }
911
+ if (!comparisonData.implementedInterfaceTypeNames.has(interfaceTypeName)) {
912
+ abstractTypeName = '';
913
+ break;
914
+ }
915
+ }
916
+ if (abstractTypeName) {
917
+ break;
918
+ }
919
+ }
920
+ }
921
+ /* If the abstract type is:
922
+ * 1. An Interface: each returned Object types must implement that origin Interface.
923
+ * 2. A Union: all returned Object types must be Member of that Union.
924
+ * 3. Invalid (empty string): return an error
925
+ */
926
+ if (!this.shouldUpdateFederatedFieldAbstractNamedType(abstractTypeName, objectTypeNames)) {
927
+ this.errors.push((0, errors_1.incompatibleFederatedFieldNamedTypeError)(fieldCoordinates, subgraphNamesByNamedTypeName));
928
+ continue;
929
+ }
930
+ fieldData.namedTypeName = abstractTypeName;
931
+ this.updateTypeNodeNamedType(fieldData.type, abstractTypeName);
932
+ }
933
+ }
696
934
  /* federateInternalSubgraphData is responsible for merging each subgraph TypeScript representation of a GraphQL type
697
- ** into a single representation.
698
- ** This method is always necessary, regardless of whether federating a source graph or contract graph. */
935
+ * into a single representation.
936
+ * This method is always necessary, regardless of whether federating a source graph or contract graph.
937
+ * */
699
938
  federateInternalSubgraphData() {
700
939
  let subgraphNumber = 0;
701
940
  let shouldSkipPersistedExecutableDirectives = false;
@@ -725,6 +964,7 @@ class FederationFactory {
725
964
  shouldSkipPersistedExecutableDirectives = true;
726
965
  }
727
966
  }
967
+ this.handleDisparateFieldNamedTypes();
728
968
  }
729
969
  handleInterfaceObjectForInternalGraph({ entityData, internalSubgraph, interfaceObjectData, interfaceObjectNode, resolvableKeyFieldSets, subgraphName, }) {
730
970
  const entityGraphNode = this.internalGraph.addOrUpdateNode(entityData.typeName);
@@ -1080,6 +1320,20 @@ class FederationFactory {
1080
1320
  constants_1.SCOPE_SCALAR_DEFINITION,
1081
1321
  ];
1082
1322
  }
1323
+ validatePathSegmentInaccessibility(path) {
1324
+ const segments = path.split(string_constants_1.PERIOD);
1325
+ if (segments.length < 1) {
1326
+ return false;
1327
+ }
1328
+ let segment = segments[0];
1329
+ for (let i = 1; i < segments.length; i++) {
1330
+ if (this.inaccessiblePaths.has(segment)) {
1331
+ return true;
1332
+ }
1333
+ segment += `.${segments[i]}`;
1334
+ }
1335
+ return false;
1336
+ }
1083
1337
  validateReferencesOfInaccessibleType(data) {
1084
1338
  const paths = this.pathsByNamedTypeName.get(data.name);
1085
1339
  if (!paths || paths.size < 1) {
@@ -1087,7 +1341,10 @@ class FederationFactory {
1087
1341
  }
1088
1342
  const invalidPaths = [];
1089
1343
  for (const path of paths) {
1090
- if (!this.inaccessiblePaths.has(path)) {
1344
+ if (this.inaccessiblePaths.has(path)) {
1345
+ continue;
1346
+ }
1347
+ if (!this.validatePathSegmentInaccessibility(path)) {
1091
1348
  invalidPaths.push(path);
1092
1349
  }
1093
1350
  }