@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.
- package/dist/ast/utils.d.ts +1 -1
- package/dist/ast/utils.js +35 -8
- package/dist/ast/utils.js.map +1 -1
- package/dist/errors/errors.d.ts +7 -1
- package/dist/errors/errors.js +52 -10
- package/dist/errors/errors.js.map +1 -1
- package/dist/federation/federation-factory.d.ts +10 -4
- package/dist/federation/federation-factory.js +271 -14
- package/dist/federation/federation-factory.js.map +1 -1
- package/dist/normalization/normalization-factory.d.ts +0 -1
- package/dist/normalization/normalization-factory.js +14 -13
- package/dist/normalization/normalization-factory.js.map +1 -1
- package/dist/normalization/utils.js +35 -17
- package/dist/normalization/utils.js.map +1 -1
- package/dist/schema-building/type-merging.d.ts +19 -0
- package/dist/schema-building/type-merging.js +2 -1
- package/dist/schema-building/type-merging.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
698
|
-
|
|
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 (
|
|
1344
|
+
if (this.inaccessiblePaths.has(path)) {
|
|
1345
|
+
continue;
|
|
1346
|
+
}
|
|
1347
|
+
if (!this.validatePathSegmentInaccessibility(path)) {
|
|
1091
1348
|
invalidPaths.push(path);
|
|
1092
1349
|
}
|
|
1093
1350
|
}
|