@graphql-tools/delegate 12.0.18 → 12.0.19-rc-fb25f307dd73dbdd994a65a01120efa6c42a2a52
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/CHANGELOG.md +37 -0
- package/dist/index.cjs +426 -36
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +426 -36
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# @graphql-tools/delegate
|
|
2
2
|
|
|
3
|
+
## 12.0.19-rc-fb25f307dd73dbdd994a65a01120efa6c42a2a52
|
|
4
|
+
### Patch Changes
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
- [#2351](https://github.com/graphql-hive/gateway/pull/2351) [`0bbdbbc`](https://github.com/graphql-hive/gateway/commit/0bbdbbc22b75d9705a77f96144af002c796a695d) Thanks [@ardatan](https://github.com/ardatan)! - Fix `@provides` so the gateway only requests the provided fields the client actually selected, and stops delegating to the owner subgraph when `@provides` already covers the request.
|
|
9
|
+
|
|
10
|
+
Previously, when a subgraph declared `@provides(fields: "...")` on a field, the gateway would still:
|
|
11
|
+
|
|
12
|
+
1. Forward **every** field listed in `@provides` to that subgraph, even when the client never asked for them.
|
|
13
|
+
2. After receiving the response, plan additional delegations to the owner subgraph for `@provides`-covered fields whenever the providing subgraph declared them as `@external`, even though the data was already returned.
|
|
14
|
+
|
|
15
|
+
For example with:
|
|
16
|
+
|
|
17
|
+
```graphql
|
|
18
|
+
# subgraph B (provider)
|
|
19
|
+
type Query {
|
|
20
|
+
entity: Entity @provides(fields: "name description")
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type Entity @key(fields: "id") {
|
|
24
|
+
id: ID!
|
|
25
|
+
name: String! @external
|
|
26
|
+
description: String! @external
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
a client query of `{ entity { id name } }` would still cause the gateway to ask subgraph B for `description` *and* fetch `name` again from subgraph A (the owner of `Entity`).
|
|
31
|
+
|
|
32
|
+
After this fix:
|
|
33
|
+
|
|
34
|
+
- Only the `@provides` fields the client actually selected are forwarded to the providing subgraph (request side).
|
|
35
|
+
- The delegation planner now recognises `@provides` declarations at every nested level (e.g. `@provides(fields: "nested { nestedNested { name description } }")`) and `@provides` declarations made via inline fragments on union/interface members (e.g. `@provides(fields: "... on Book { title }")`), so the gateway no longer round-trips to the owner subgraph for fields that the providing subgraph has already returned.
|
|
36
|
+
- Fragment spreads in the client query are correctly handled when selecting nested `@provides`-covered fields. Previously, using a fragment spread (e.g. `...MyFrag`) for nested `@external` fields could cause an unnecessary delegation to the owner because selection subtraction compared only the spread name with the explicit `@provides` fields. The planner now resolves fragment spreads before subtracting provided selections, while preserving the fragment type condition and directives when only part of a fragment remains.
|
|
37
|
+
|
|
38
|
+
Aliases, direct field selections, fragments, fragment spreads, `@include`/`@skip` directives wrapping a `@provides` field, and nested `@provides` selections are preserved without unnecessary owner delegations.
|
|
39
|
+
|
|
3
40
|
## 12.0.18
|
|
4
41
|
### Patch Changes
|
|
5
42
|
|
package/dist/index.cjs
CHANGED
|
@@ -590,7 +590,7 @@ const handleOverrideByDelegation = utils.memoize3(
|
|
|
590
590
|
}
|
|
591
591
|
);
|
|
592
592
|
|
|
593
|
-
function finalizeGatewayDocument(targetSchema, fragments, operations, onOverlappingAliases, delegationContext) {
|
|
593
|
+
function finalizeGatewayDocument(targetSchema, originalDocument, fragments, operations, onOverlappingAliases, delegationContext) {
|
|
594
594
|
let usedVariables = [];
|
|
595
595
|
let usedFragments = [];
|
|
596
596
|
const newOperations = [];
|
|
@@ -682,47 +682,128 @@ function finalizeGatewayDocument(targetSchema, fragments, operations, onOverlapp
|
|
|
682
682
|
}
|
|
683
683
|
);
|
|
684
684
|
}
|
|
685
|
+
const includedFragmentNames = new Set(newFragments.map((f) => f.name.value));
|
|
686
|
+
const hasDroppedFragments = includedFragmentNames.size < usedFragments.length;
|
|
685
687
|
let newDocument = {
|
|
686
688
|
kind: graphql.Kind.DOCUMENT,
|
|
687
689
|
definitions: [...newOperations, ...newFragments]
|
|
688
690
|
};
|
|
689
691
|
const stitchingInfo = delegationContext.info?.schema?.extensions?.["stitchingInfo"];
|
|
690
|
-
if (stitchingInfo != null
|
|
692
|
+
if (stitchingInfo != null && subschemaHasProvidedSelections(
|
|
693
|
+
stitchingInfo,
|
|
694
|
+
delegationContext.subschema
|
|
695
|
+
)) {
|
|
696
|
+
const {
|
|
697
|
+
selectionSetsByPath: originalFieldSelectionSetsByPath,
|
|
698
|
+
fragments: originalFragmentsByName
|
|
699
|
+
} = collectFieldSelectionSetsByPath(originalDocument);
|
|
691
700
|
const typeInfo = getTypeInfo(targetSchema);
|
|
701
|
+
const pathStack = [];
|
|
702
|
+
const inlineFragmentCounterStack = [];
|
|
692
703
|
newDocument = graphql.visit(
|
|
693
704
|
newDocument,
|
|
694
705
|
graphql.visitWithTypeInfo(typeInfo, {
|
|
695
|
-
[graphql.Kind.
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
706
|
+
[graphql.Kind.SELECTION_SET]: {
|
|
707
|
+
enter() {
|
|
708
|
+
inlineFragmentCounterStack.push(/* @__PURE__ */ new Map());
|
|
709
|
+
},
|
|
710
|
+
leave() {
|
|
711
|
+
inlineFragmentCounterStack.pop();
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
[graphql.Kind.OPERATION_DEFINITION]: {
|
|
715
|
+
enter(node) {
|
|
716
|
+
pathStack.push(`op:${node.operation}:${node.name?.value ?? ""}`);
|
|
717
|
+
},
|
|
718
|
+
leave() {
|
|
719
|
+
pathStack.pop();
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
[graphql.Kind.FRAGMENT_DEFINITION]: {
|
|
723
|
+
enter(node) {
|
|
724
|
+
pathStack.push(`frag:${node.name.value}`);
|
|
725
|
+
},
|
|
726
|
+
leave() {
|
|
727
|
+
pathStack.pop();
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
[graphql.Kind.INLINE_FRAGMENT]: {
|
|
731
|
+
enter(node) {
|
|
732
|
+
pathStack.push(
|
|
733
|
+
nextInlineFragmentPathSegment(
|
|
734
|
+
node,
|
|
735
|
+
inlineFragmentCounterStack[inlineFragmentCounterStack.length - 1]
|
|
736
|
+
)
|
|
737
|
+
);
|
|
738
|
+
},
|
|
739
|
+
leave() {
|
|
740
|
+
pathStack.pop();
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
[graphql.Kind.FIELD]: {
|
|
744
|
+
enter(fieldNode) {
|
|
745
|
+
pathStack.push(fieldNode.alias?.value ?? fieldNode.name.value);
|
|
746
|
+
},
|
|
747
|
+
leave(fieldNode) {
|
|
748
|
+
try {
|
|
749
|
+
const parentType = typeInfo.getParentType();
|
|
750
|
+
if (!parentType) {
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
const typeConfig = stitchingInfo?.mergedTypes?.[parentType.name];
|
|
701
754
|
const providedSelectionsByField = typeConfig?.providedSelectionsByField?.get(
|
|
702
755
|
delegationContext.subschema
|
|
703
756
|
);
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
return {
|
|
708
|
-
...fieldNode,
|
|
709
|
-
selectionSet: {
|
|
710
|
-
kind: graphql.Kind.SELECTION_SET,
|
|
711
|
-
selections: [
|
|
712
|
-
...providedSelection.selections,
|
|
713
|
-
...fieldNode.selectionSet?.selections ?? []
|
|
714
|
-
]
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
}
|
|
757
|
+
const providedSelection = providedSelectionsByField?.[fieldNode.name.value];
|
|
758
|
+
if (!providedSelection) {
|
|
759
|
+
return void 0;
|
|
718
760
|
}
|
|
761
|
+
const originalSelectionSet = lookupOriginalSelectionSet(
|
|
762
|
+
pathStack,
|
|
763
|
+
fieldNode,
|
|
764
|
+
originalFieldSelectionSetsByPath
|
|
765
|
+
);
|
|
766
|
+
const requestedProvidedSelections = intersectProvidedSelections(
|
|
767
|
+
providedSelection.selections,
|
|
768
|
+
originalSelectionSet,
|
|
769
|
+
originalFragmentsByName
|
|
770
|
+
);
|
|
771
|
+
if (!requestedProvidedSelections.length) {
|
|
772
|
+
return void 0;
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
...fieldNode,
|
|
776
|
+
selectionSet: {
|
|
777
|
+
kind: graphql.Kind.SELECTION_SET,
|
|
778
|
+
selections: [
|
|
779
|
+
...requestedProvidedSelections,
|
|
780
|
+
...fieldNode.selectionSet?.selections ?? []
|
|
781
|
+
]
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
} finally {
|
|
785
|
+
pathStack.pop();
|
|
719
786
|
}
|
|
720
787
|
}
|
|
721
|
-
return fieldNode;
|
|
722
788
|
}
|
|
723
789
|
})
|
|
724
790
|
);
|
|
725
791
|
}
|
|
792
|
+
if (hasDroppedFragments) {
|
|
793
|
+
const ops = newDocument.definitions.filter(
|
|
794
|
+
(d) => d.kind === graphql.Kind.OPERATION_DEFINITION
|
|
795
|
+
);
|
|
796
|
+
const frags = newDocument.definitions.filter(
|
|
797
|
+
(d) => d.kind === graphql.Kind.FRAGMENT_DEFINITION
|
|
798
|
+
);
|
|
799
|
+
newDocument = {
|
|
800
|
+
kind: graphql.Kind.DOCUMENT,
|
|
801
|
+
definitions: [
|
|
802
|
+
...removeDeadFragmentSpreads(ops, includedFragmentNames),
|
|
803
|
+
...frags
|
|
804
|
+
]
|
|
805
|
+
};
|
|
806
|
+
}
|
|
726
807
|
return {
|
|
727
808
|
usedVariables,
|
|
728
809
|
newDocument
|
|
@@ -733,6 +814,7 @@ function finalizeGatewayRequest(originalRequest, delegationContext, onOverlappin
|
|
|
733
814
|
const { targetSchema } = delegationContext;
|
|
734
815
|
const { usedVariables, newDocument } = finalizeGatewayDocument(
|
|
735
816
|
targetSchema,
|
|
817
|
+
originalRequest.document,
|
|
736
818
|
fragments,
|
|
737
819
|
operations,
|
|
738
820
|
onOverlappingAliases,
|
|
@@ -758,9 +840,293 @@ function finalizeGatewayRequest(originalRequest, delegationContext, onOverlappin
|
|
|
758
840
|
variables: newVariables
|
|
759
841
|
};
|
|
760
842
|
}
|
|
843
|
+
function removeDeadFragmentSpreads(operations, included) {
|
|
844
|
+
function cleanSelections(selections) {
|
|
845
|
+
const out = [];
|
|
846
|
+
for (const sel of selections) {
|
|
847
|
+
if (sel.kind === graphql.Kind.FRAGMENT_SPREAD) {
|
|
848
|
+
if (included.has(sel.name.value)) {
|
|
849
|
+
out.push(sel);
|
|
850
|
+
}
|
|
851
|
+
} else if ((sel.kind === graphql.Kind.FIELD || sel.kind === graphql.Kind.INLINE_FRAGMENT) && sel.selectionSet) {
|
|
852
|
+
const cleaned = cleanSelections(sel.selectionSet.selections);
|
|
853
|
+
if (cleaned.length === 0 && sel.kind === graphql.Kind.INLINE_FRAGMENT) {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (cleaned.length === 0 && sel.kind === graphql.Kind.FIELD) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
out.push(
|
|
860
|
+
cleaned === sel.selectionSet.selections ? sel : {
|
|
861
|
+
...sel,
|
|
862
|
+
selectionSet: {
|
|
863
|
+
kind: graphql.Kind.SELECTION_SET,
|
|
864
|
+
selections: cleaned
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
} else {
|
|
869
|
+
out.push(sel);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return out;
|
|
873
|
+
}
|
|
874
|
+
return operations.map((op) => ({
|
|
875
|
+
...op,
|
|
876
|
+
selectionSet: {
|
|
877
|
+
...op.selectionSet,
|
|
878
|
+
selections: cleanSelections(op.selectionSet.selections)
|
|
879
|
+
}
|
|
880
|
+
}));
|
|
881
|
+
}
|
|
761
882
|
function isTypeNameField(selection) {
|
|
762
883
|
return selection.kind === graphql.Kind.FIELD && !selection.alias && selection.name.value === "__typename";
|
|
763
884
|
}
|
|
885
|
+
function collectFieldSelectionSetsByPathImpl(document) {
|
|
886
|
+
const selectionSetsByPath = /* @__PURE__ */ new Map();
|
|
887
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
888
|
+
for (const def of document.definitions) {
|
|
889
|
+
if (def.kind === graphql.Kind.FRAGMENT_DEFINITION) {
|
|
890
|
+
fragments.set(def.name.value, def);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const pathStack = [];
|
|
894
|
+
const inlineFragmentCounterStack = [];
|
|
895
|
+
graphql.visit(document, {
|
|
896
|
+
[graphql.Kind.SELECTION_SET]: {
|
|
897
|
+
enter() {
|
|
898
|
+
inlineFragmentCounterStack.push(/* @__PURE__ */ new Map());
|
|
899
|
+
},
|
|
900
|
+
leave() {
|
|
901
|
+
inlineFragmentCounterStack.pop();
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
[graphql.Kind.OPERATION_DEFINITION]: {
|
|
905
|
+
enter(node) {
|
|
906
|
+
pathStack.push(`op:${node.operation}:${node.name?.value ?? ""}`);
|
|
907
|
+
},
|
|
908
|
+
leave() {
|
|
909
|
+
pathStack.pop();
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
[graphql.Kind.FRAGMENT_DEFINITION]: {
|
|
913
|
+
enter(node) {
|
|
914
|
+
pathStack.push(`frag:${node.name.value}`);
|
|
915
|
+
},
|
|
916
|
+
leave() {
|
|
917
|
+
pathStack.pop();
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
[graphql.Kind.INLINE_FRAGMENT]: {
|
|
921
|
+
enter(node) {
|
|
922
|
+
pathStack.push(
|
|
923
|
+
nextInlineFragmentPathSegment(
|
|
924
|
+
node,
|
|
925
|
+
inlineFragmentCounterStack[inlineFragmentCounterStack.length - 1]
|
|
926
|
+
)
|
|
927
|
+
);
|
|
928
|
+
},
|
|
929
|
+
leave() {
|
|
930
|
+
pathStack.pop();
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
[graphql.Kind.FIELD]: {
|
|
934
|
+
enter(node) {
|
|
935
|
+
pathStack.push(node.alias?.value ?? node.name.value);
|
|
936
|
+
if (node.selectionSet) {
|
|
937
|
+
selectionSetsByPath.set(pathStack.join(">"), node.selectionSet);
|
|
938
|
+
if (node.alias) {
|
|
939
|
+
const namePath = [...pathStack.slice(0, -1), node.name.value].join(
|
|
940
|
+
">"
|
|
941
|
+
);
|
|
942
|
+
if (!selectionSetsByPath.has(namePath)) {
|
|
943
|
+
selectionSetsByPath.set(namePath, node.selectionSet);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
leave() {
|
|
949
|
+
pathStack.pop();
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
return { selectionSetsByPath, fragments };
|
|
954
|
+
}
|
|
955
|
+
const collectFieldSelectionSetsByPath = utils.memoize1(
|
|
956
|
+
collectFieldSelectionSetsByPathImpl
|
|
957
|
+
);
|
|
958
|
+
function nextInlineFragmentPathSegment(node, counter) {
|
|
959
|
+
const typeName = node.typeCondition?.name.value ?? "";
|
|
960
|
+
if (!counter) {
|
|
961
|
+
return `inline:${typeName}:0`;
|
|
962
|
+
}
|
|
963
|
+
const idx = counter.get(typeName) ?? 0;
|
|
964
|
+
counter.set(typeName, idx + 1);
|
|
965
|
+
return `inline:${typeName}:${idx}`;
|
|
966
|
+
}
|
|
967
|
+
function subschemaHasProvidedSelectionsImpl(stitchingInfo, subschema) {
|
|
968
|
+
const mergedTypes = stitchingInfo.mergedTypes;
|
|
969
|
+
if (!mergedTypes) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
for (const typeConfig of Object.values(mergedTypes)) {
|
|
973
|
+
if (typeConfig?.providedSelectionsByField?.has(subschema)) {
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
const subschemaHasProvidedSelections = utils.memoize2(
|
|
980
|
+
subschemaHasProvidedSelectionsImpl
|
|
981
|
+
);
|
|
982
|
+
function lookupOriginalSelectionSet(pathStack, fieldNode, selectionSetsByPath) {
|
|
983
|
+
const exact = selectionSetsByPath.get(pathStack.join(">"));
|
|
984
|
+
if (exact || !fieldNode.alias) {
|
|
985
|
+
return exact;
|
|
986
|
+
}
|
|
987
|
+
const fallback = [...pathStack.slice(0, -1), fieldNode.name.value].join(">");
|
|
988
|
+
return selectionSetsByPath.get(fallback);
|
|
989
|
+
}
|
|
990
|
+
function intersectProvidedSelections(providedSelections, originalSelectionSet, fragments) {
|
|
991
|
+
if (!originalSelectionSet) {
|
|
992
|
+
return [];
|
|
993
|
+
}
|
|
994
|
+
const providedFieldsByName = /* @__PURE__ */ new Map();
|
|
995
|
+
const otherProvided = [];
|
|
996
|
+
for (const provided of providedSelections) {
|
|
997
|
+
if (provided.kind === graphql.Kind.FIELD) {
|
|
998
|
+
providedFieldsByName.set(provided.name.value, provided);
|
|
999
|
+
} else {
|
|
1000
|
+
otherProvided.push(provided);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const result = [];
|
|
1004
|
+
collectMatchingOriginalSelections(
|
|
1005
|
+
originalSelectionSet,
|
|
1006
|
+
providedFieldsByName,
|
|
1007
|
+
fragments,
|
|
1008
|
+
/* @__PURE__ */ new Set(),
|
|
1009
|
+
/* @__PURE__ */ new Set(),
|
|
1010
|
+
result
|
|
1011
|
+
);
|
|
1012
|
+
return [...result, ...otherProvided];
|
|
1013
|
+
}
|
|
1014
|
+
function collectMatchingOriginalSelections(selectionSet, providedFieldsByName, fragments, seenFieldNames, seenFragmentNames, result) {
|
|
1015
|
+
for (const sel of selectionSet.selections) {
|
|
1016
|
+
if (sel.kind === graphql.Kind.FIELD) {
|
|
1017
|
+
const provided = providedFieldsByName.get(sel.name.value);
|
|
1018
|
+
if (!provided) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
const dedupKey = sel.alias?.value ?? sel.name.value;
|
|
1022
|
+
if (seenFieldNames.has(dedupKey)) {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
seenFieldNames.add(dedupKey);
|
|
1026
|
+
if (provided.selectionSet && sel.selectionSet) {
|
|
1027
|
+
const nested = intersectProvidedSelections(
|
|
1028
|
+
provided.selectionSet.selections,
|
|
1029
|
+
sel.selectionSet,
|
|
1030
|
+
fragments
|
|
1031
|
+
);
|
|
1032
|
+
if (!nested.length) {
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
result.push({
|
|
1036
|
+
...sel,
|
|
1037
|
+
selectionSet: {
|
|
1038
|
+
kind: graphql.Kind.SELECTION_SET,
|
|
1039
|
+
selections: nested
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
} else {
|
|
1043
|
+
result.push(sel);
|
|
1044
|
+
}
|
|
1045
|
+
} else if (sel.kind === graphql.Kind.INLINE_FRAGMENT && sel.selectionSet) {
|
|
1046
|
+
const hasDirectives = (sel.directives?.length ?? 0) > 0;
|
|
1047
|
+
const hasTypeCondition = sel.typeCondition != null;
|
|
1048
|
+
if (hasDirectives || hasTypeCondition) {
|
|
1049
|
+
const inner = [];
|
|
1050
|
+
collectMatchingOriginalSelections(
|
|
1051
|
+
sel.selectionSet,
|
|
1052
|
+
providedFieldsByName,
|
|
1053
|
+
fragments,
|
|
1054
|
+
/* @__PURE__ */ new Set(),
|
|
1055
|
+
/* @__PURE__ */ new Set(),
|
|
1056
|
+
inner
|
|
1057
|
+
);
|
|
1058
|
+
if (inner.length === 0) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
result.push({
|
|
1062
|
+
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
1063
|
+
typeCondition: sel.typeCondition,
|
|
1064
|
+
directives: sel.directives,
|
|
1065
|
+
selectionSet: {
|
|
1066
|
+
kind: graphql.Kind.SELECTION_SET,
|
|
1067
|
+
selections: inner
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
} else {
|
|
1071
|
+
collectMatchingOriginalSelections(
|
|
1072
|
+
sel.selectionSet,
|
|
1073
|
+
providedFieldsByName,
|
|
1074
|
+
fragments,
|
|
1075
|
+
seenFieldNames,
|
|
1076
|
+
seenFragmentNames,
|
|
1077
|
+
result
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
} else if (sel.kind === graphql.Kind.FRAGMENT_SPREAD) {
|
|
1081
|
+
const fragmentDef = fragments.get(sel.name.value);
|
|
1082
|
+
if (!fragmentDef) {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
const hasSpreadDirectives = (sel.directives?.length ?? 0) > 0;
|
|
1086
|
+
const hasFragmentDirectives = (fragmentDef.directives?.length ?? 0) > 0;
|
|
1087
|
+
if (hasSpreadDirectives || hasFragmentDirectives) {
|
|
1088
|
+
const inner = [];
|
|
1089
|
+
collectMatchingOriginalSelections(
|
|
1090
|
+
fragmentDef.selectionSet,
|
|
1091
|
+
providedFieldsByName,
|
|
1092
|
+
fragments,
|
|
1093
|
+
/* @__PURE__ */ new Set(),
|
|
1094
|
+
/* @__PURE__ */ new Set(),
|
|
1095
|
+
inner
|
|
1096
|
+
);
|
|
1097
|
+
if (inner.length === 0) {
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
const combinedDirectives = [
|
|
1101
|
+
...sel.directives ?? [],
|
|
1102
|
+
...fragmentDef.directives ?? []
|
|
1103
|
+
];
|
|
1104
|
+
result.push({
|
|
1105
|
+
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
1106
|
+
typeCondition: fragmentDef.typeCondition,
|
|
1107
|
+
directives: combinedDirectives.length > 0 ? combinedDirectives : void 0,
|
|
1108
|
+
selectionSet: {
|
|
1109
|
+
kind: graphql.Kind.SELECTION_SET,
|
|
1110
|
+
selections: inner
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
} else {
|
|
1114
|
+
if (seenFragmentNames.has(sel.name.value)) {
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
seenFragmentNames.add(sel.name.value);
|
|
1118
|
+
collectMatchingOriginalSelections(
|
|
1119
|
+
fragmentDef.selectionSet,
|
|
1120
|
+
providedFieldsByName,
|
|
1121
|
+
fragments,
|
|
1122
|
+
seenFieldNames,
|
|
1123
|
+
seenFragmentNames,
|
|
1124
|
+
result
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
764
1130
|
function filterTypenameFields(selections) {
|
|
765
1131
|
let hasTypeNameField = false;
|
|
766
1132
|
const filteredSelections = selections.filter((selection) => {
|
|
@@ -809,15 +1175,17 @@ function collectFragmentVariables(targetSchema, fragmentSet, validFragments, val
|
|
|
809
1175
|
usedVariables = union(usedVariables, fragmentUsedVariables);
|
|
810
1176
|
if (name && !(name in fragmentSet)) {
|
|
811
1177
|
fragmentSet[name] = true;
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1178
|
+
if (selectionSet.selections.length > 0) {
|
|
1179
|
+
newFragments.push({
|
|
1180
|
+
kind: graphql.Kind.FRAGMENT_DEFINITION,
|
|
1181
|
+
name: {
|
|
1182
|
+
kind: graphql.Kind.NAME,
|
|
1183
|
+
value: name
|
|
1184
|
+
},
|
|
1185
|
+
typeCondition: fragment.typeCondition,
|
|
1186
|
+
selectionSet
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
821
1189
|
}
|
|
822
1190
|
}
|
|
823
1191
|
}
|
|
@@ -2967,7 +3335,7 @@ function extractUnavailableFields(schema, field, fieldNode, shouldAdd, fragments
|
|
|
2967
3335
|
}
|
|
2968
3336
|
return [];
|
|
2969
3337
|
}
|
|
2970
|
-
function subtractSelectionSets(selectionSetA, selectionSetB) {
|
|
3338
|
+
function subtractSelectionSets(selectionSetA, selectionSetB, fragments = {}) {
|
|
2971
3339
|
const newSelections = [];
|
|
2972
3340
|
for (const selectionA of selectionSetA.selections) {
|
|
2973
3341
|
switch (selectionA.kind) {
|
|
@@ -2983,7 +3351,7 @@ function subtractSelectionSets(selectionSetA, selectionSetB) {
|
|
|
2983
3351
|
);
|
|
2984
3352
|
if (fieldsInOtherSelectionSet.length > 0 && fieldA.selectionSet?.selections?.length) {
|
|
2985
3353
|
const newSubSelection = fieldsInOtherSelectionSet.reduce(
|
|
2986
|
-
(acc, fieldB) => fieldB.selectionSet ? subtractSelectionSets(acc, fieldB.selectionSet) : acc,
|
|
3354
|
+
(acc, fieldB) => fieldB.selectionSet ? subtractSelectionSets(acc, fieldB.selectionSet, fragments) : acc,
|
|
2987
3355
|
{
|
|
2988
3356
|
kind: graphql.Kind.SELECTION_SET,
|
|
2989
3357
|
selections: fieldA.selectionSet.selections
|
|
@@ -3013,7 +3381,11 @@ function subtractSelectionSets(selectionSetA, selectionSetB) {
|
|
|
3013
3381
|
);
|
|
3014
3382
|
if (inlineFragmentsFromB.length > 0) {
|
|
3015
3383
|
const newSubSelection = inlineFragmentsFromB.reduce(
|
|
3016
|
-
(acc, subselectionB) => subselectionB.selectionSet ? subtractSelectionSets(
|
|
3384
|
+
(acc, subselectionB) => subselectionB.selectionSet ? subtractSelectionSets(
|
|
3385
|
+
acc,
|
|
3386
|
+
subselectionB.selectionSet,
|
|
3387
|
+
fragments
|
|
3388
|
+
) : acc,
|
|
3017
3389
|
{
|
|
3018
3390
|
kind: graphql.Kind.SELECTION_SET,
|
|
3019
3391
|
selections: inlineFragmentA.selectionSet.selections
|
|
@@ -3041,7 +3413,25 @@ function subtractSelectionSets(selectionSetA, selectionSetB) {
|
|
|
3041
3413
|
}
|
|
3042
3414
|
case graphql.Kind.FRAGMENT_SPREAD: {
|
|
3043
3415
|
const fragmentSpreadA = selectionA;
|
|
3044
|
-
|
|
3416
|
+
const fragment = fragments[fragmentSpreadA.name.value];
|
|
3417
|
+
if (fragment) {
|
|
3418
|
+
const newSubSelection = subtractSelectionSets(
|
|
3419
|
+
fragment.selectionSet,
|
|
3420
|
+
selectionSetB,
|
|
3421
|
+
fragments
|
|
3422
|
+
);
|
|
3423
|
+
if (newSubSelection.selections.length) {
|
|
3424
|
+
newSelections.push({
|
|
3425
|
+
kind: graphql.Kind.INLINE_FRAGMENT,
|
|
3426
|
+
typeCondition: fragment.typeCondition,
|
|
3427
|
+
directives: [
|
|
3428
|
+
...fragment.directives ?? [],
|
|
3429
|
+
...fragmentSpreadA.directives ?? []
|
|
3430
|
+
],
|
|
3431
|
+
selectionSet: newSubSelection
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
3434
|
+
} else if (!selectionSetB.selections.some(
|
|
3045
3435
|
(subselectionB) => subselectionB.kind === graphql.Kind.FRAGMENT_SPREAD && subselectionB.name.value === fragmentSpreadA.name.value
|
|
3046
3436
|
)) {
|
|
3047
3437
|
newSelections.push(selectionA);
|
package/dist/index.d.cts
CHANGED
|
@@ -210,7 +210,7 @@ declare function cloneSubschemaConfig(subschemaConfig: SubschemaConfig): Subsche
|
|
|
210
210
|
|
|
211
211
|
declare function extractUnavailableFieldsFromSelectionSet(schema: GraphQLSchema, fieldType: GraphQLNamedOutputType, fieldSelectionSet: SelectionSetNode, shouldAdd: (fieldType: GraphQLObjectType | GraphQLInterfaceType, selection: FieldNode) => boolean, fragments?: Record<string, FragmentDefinitionNode>): SelectionNode[];
|
|
212
212
|
declare function extractUnavailableFields(schema: GraphQLSchema, field: GraphQLField<any, any>, fieldNode: FieldNode, shouldAdd: (fieldType: GraphQLObjectType | GraphQLInterfaceType, selection: FieldNode) => boolean, fragments?: Record<string, FragmentDefinitionNode>): SelectionNode[];
|
|
213
|
-
declare function subtractSelectionSets(selectionSetA: SelectionSetNode, selectionSetB: SelectionSetNode): SelectionSetNode;
|
|
213
|
+
declare function subtractSelectionSets(selectionSetA: SelectionSetNode, selectionSetB: SelectionSetNode, fragments?: Record<string, FragmentDefinitionNode>): SelectionSetNode;
|
|
214
214
|
|
|
215
215
|
type Deferred<T = unknown> = {
|
|
216
216
|
promise: Promise<T>;
|
package/dist/index.d.ts
CHANGED
|
@@ -210,7 +210,7 @@ declare function cloneSubschemaConfig(subschemaConfig: SubschemaConfig): Subsche
|
|
|
210
210
|
|
|
211
211
|
declare function extractUnavailableFieldsFromSelectionSet(schema: GraphQLSchema, fieldType: GraphQLNamedOutputType, fieldSelectionSet: SelectionSetNode, shouldAdd: (fieldType: GraphQLObjectType | GraphQLInterfaceType, selection: FieldNode) => boolean, fragments?: Record<string, FragmentDefinitionNode>): SelectionNode[];
|
|
212
212
|
declare function extractUnavailableFields(schema: GraphQLSchema, field: GraphQLField<any, any>, fieldNode: FieldNode, shouldAdd: (fieldType: GraphQLObjectType | GraphQLInterfaceType, selection: FieldNode) => boolean, fragments?: Record<string, FragmentDefinitionNode>): SelectionNode[];
|
|
213
|
-
declare function subtractSelectionSets(selectionSetA: SelectionSetNode, selectionSetB: SelectionSetNode): SelectionSetNode;
|
|
213
|
+
declare function subtractSelectionSets(selectionSetA: SelectionSetNode, selectionSetB: SelectionSetNode, fragments?: Record<string, FragmentDefinitionNode>): SelectionSetNode;
|
|
214
214
|
|
|
215
215
|
type Deferred<T = unknown> = {
|
|
216
216
|
promise: Promise<T>;
|
package/dist/index.js
CHANGED
|
@@ -590,7 +590,7 @@ const handleOverrideByDelegation = memoize3(
|
|
|
590
590
|
}
|
|
591
591
|
);
|
|
592
592
|
|
|
593
|
-
function finalizeGatewayDocument(targetSchema, fragments, operations, onOverlappingAliases, delegationContext) {
|
|
593
|
+
function finalizeGatewayDocument(targetSchema, originalDocument, fragments, operations, onOverlappingAliases, delegationContext) {
|
|
594
594
|
let usedVariables = [];
|
|
595
595
|
let usedFragments = [];
|
|
596
596
|
const newOperations = [];
|
|
@@ -682,47 +682,128 @@ function finalizeGatewayDocument(targetSchema, fragments, operations, onOverlapp
|
|
|
682
682
|
}
|
|
683
683
|
);
|
|
684
684
|
}
|
|
685
|
+
const includedFragmentNames = new Set(newFragments.map((f) => f.name.value));
|
|
686
|
+
const hasDroppedFragments = includedFragmentNames.size < usedFragments.length;
|
|
685
687
|
let newDocument = {
|
|
686
688
|
kind: Kind.DOCUMENT,
|
|
687
689
|
definitions: [...newOperations, ...newFragments]
|
|
688
690
|
};
|
|
689
691
|
const stitchingInfo = delegationContext.info?.schema?.extensions?.["stitchingInfo"];
|
|
690
|
-
if (stitchingInfo != null
|
|
692
|
+
if (stitchingInfo != null && subschemaHasProvidedSelections(
|
|
693
|
+
stitchingInfo,
|
|
694
|
+
delegationContext.subschema
|
|
695
|
+
)) {
|
|
696
|
+
const {
|
|
697
|
+
selectionSetsByPath: originalFieldSelectionSetsByPath,
|
|
698
|
+
fragments: originalFragmentsByName
|
|
699
|
+
} = collectFieldSelectionSetsByPath(originalDocument);
|
|
691
700
|
const typeInfo = getTypeInfo(targetSchema);
|
|
701
|
+
const pathStack = [];
|
|
702
|
+
const inlineFragmentCounterStack = [];
|
|
692
703
|
newDocument = visit(
|
|
693
704
|
newDocument,
|
|
694
705
|
visitWithTypeInfo(typeInfo, {
|
|
695
|
-
[Kind.
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
706
|
+
[Kind.SELECTION_SET]: {
|
|
707
|
+
enter() {
|
|
708
|
+
inlineFragmentCounterStack.push(/* @__PURE__ */ new Map());
|
|
709
|
+
},
|
|
710
|
+
leave() {
|
|
711
|
+
inlineFragmentCounterStack.pop();
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
[Kind.OPERATION_DEFINITION]: {
|
|
715
|
+
enter(node) {
|
|
716
|
+
pathStack.push(`op:${node.operation}:${node.name?.value ?? ""}`);
|
|
717
|
+
},
|
|
718
|
+
leave() {
|
|
719
|
+
pathStack.pop();
|
|
720
|
+
}
|
|
721
|
+
},
|
|
722
|
+
[Kind.FRAGMENT_DEFINITION]: {
|
|
723
|
+
enter(node) {
|
|
724
|
+
pathStack.push(`frag:${node.name.value}`);
|
|
725
|
+
},
|
|
726
|
+
leave() {
|
|
727
|
+
pathStack.pop();
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
[Kind.INLINE_FRAGMENT]: {
|
|
731
|
+
enter(node) {
|
|
732
|
+
pathStack.push(
|
|
733
|
+
nextInlineFragmentPathSegment(
|
|
734
|
+
node,
|
|
735
|
+
inlineFragmentCounterStack[inlineFragmentCounterStack.length - 1]
|
|
736
|
+
)
|
|
737
|
+
);
|
|
738
|
+
},
|
|
739
|
+
leave() {
|
|
740
|
+
pathStack.pop();
|
|
741
|
+
}
|
|
742
|
+
},
|
|
743
|
+
[Kind.FIELD]: {
|
|
744
|
+
enter(fieldNode) {
|
|
745
|
+
pathStack.push(fieldNode.alias?.value ?? fieldNode.name.value);
|
|
746
|
+
},
|
|
747
|
+
leave(fieldNode) {
|
|
748
|
+
try {
|
|
749
|
+
const parentType = typeInfo.getParentType();
|
|
750
|
+
if (!parentType) {
|
|
751
|
+
return void 0;
|
|
752
|
+
}
|
|
753
|
+
const typeConfig = stitchingInfo?.mergedTypes?.[parentType.name];
|
|
701
754
|
const providedSelectionsByField = typeConfig?.providedSelectionsByField?.get(
|
|
702
755
|
delegationContext.subschema
|
|
703
756
|
);
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
return {
|
|
708
|
-
...fieldNode,
|
|
709
|
-
selectionSet: {
|
|
710
|
-
kind: Kind.SELECTION_SET,
|
|
711
|
-
selections: [
|
|
712
|
-
...providedSelection.selections,
|
|
713
|
-
...fieldNode.selectionSet?.selections ?? []
|
|
714
|
-
]
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
}
|
|
757
|
+
const providedSelection = providedSelectionsByField?.[fieldNode.name.value];
|
|
758
|
+
if (!providedSelection) {
|
|
759
|
+
return void 0;
|
|
718
760
|
}
|
|
761
|
+
const originalSelectionSet = lookupOriginalSelectionSet(
|
|
762
|
+
pathStack,
|
|
763
|
+
fieldNode,
|
|
764
|
+
originalFieldSelectionSetsByPath
|
|
765
|
+
);
|
|
766
|
+
const requestedProvidedSelections = intersectProvidedSelections(
|
|
767
|
+
providedSelection.selections,
|
|
768
|
+
originalSelectionSet,
|
|
769
|
+
originalFragmentsByName
|
|
770
|
+
);
|
|
771
|
+
if (!requestedProvidedSelections.length) {
|
|
772
|
+
return void 0;
|
|
773
|
+
}
|
|
774
|
+
return {
|
|
775
|
+
...fieldNode,
|
|
776
|
+
selectionSet: {
|
|
777
|
+
kind: Kind.SELECTION_SET,
|
|
778
|
+
selections: [
|
|
779
|
+
...requestedProvidedSelections,
|
|
780
|
+
...fieldNode.selectionSet?.selections ?? []
|
|
781
|
+
]
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
} finally {
|
|
785
|
+
pathStack.pop();
|
|
719
786
|
}
|
|
720
787
|
}
|
|
721
|
-
return fieldNode;
|
|
722
788
|
}
|
|
723
789
|
})
|
|
724
790
|
);
|
|
725
791
|
}
|
|
792
|
+
if (hasDroppedFragments) {
|
|
793
|
+
const ops = newDocument.definitions.filter(
|
|
794
|
+
(d) => d.kind === Kind.OPERATION_DEFINITION
|
|
795
|
+
);
|
|
796
|
+
const frags = newDocument.definitions.filter(
|
|
797
|
+
(d) => d.kind === Kind.FRAGMENT_DEFINITION
|
|
798
|
+
);
|
|
799
|
+
newDocument = {
|
|
800
|
+
kind: Kind.DOCUMENT,
|
|
801
|
+
definitions: [
|
|
802
|
+
...removeDeadFragmentSpreads(ops, includedFragmentNames),
|
|
803
|
+
...frags
|
|
804
|
+
]
|
|
805
|
+
};
|
|
806
|
+
}
|
|
726
807
|
return {
|
|
727
808
|
usedVariables,
|
|
728
809
|
newDocument
|
|
@@ -733,6 +814,7 @@ function finalizeGatewayRequest(originalRequest, delegationContext, onOverlappin
|
|
|
733
814
|
const { targetSchema } = delegationContext;
|
|
734
815
|
const { usedVariables, newDocument } = finalizeGatewayDocument(
|
|
735
816
|
targetSchema,
|
|
817
|
+
originalRequest.document,
|
|
736
818
|
fragments,
|
|
737
819
|
operations,
|
|
738
820
|
onOverlappingAliases,
|
|
@@ -758,9 +840,293 @@ function finalizeGatewayRequest(originalRequest, delegationContext, onOverlappin
|
|
|
758
840
|
variables: newVariables
|
|
759
841
|
};
|
|
760
842
|
}
|
|
843
|
+
function removeDeadFragmentSpreads(operations, included) {
|
|
844
|
+
function cleanSelections(selections) {
|
|
845
|
+
const out = [];
|
|
846
|
+
for (const sel of selections) {
|
|
847
|
+
if (sel.kind === Kind.FRAGMENT_SPREAD) {
|
|
848
|
+
if (included.has(sel.name.value)) {
|
|
849
|
+
out.push(sel);
|
|
850
|
+
}
|
|
851
|
+
} else if ((sel.kind === Kind.FIELD || sel.kind === Kind.INLINE_FRAGMENT) && sel.selectionSet) {
|
|
852
|
+
const cleaned = cleanSelections(sel.selectionSet.selections);
|
|
853
|
+
if (cleaned.length === 0 && sel.kind === Kind.INLINE_FRAGMENT) {
|
|
854
|
+
continue;
|
|
855
|
+
}
|
|
856
|
+
if (cleaned.length === 0 && sel.kind === Kind.FIELD) {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
out.push(
|
|
860
|
+
cleaned === sel.selectionSet.selections ? sel : {
|
|
861
|
+
...sel,
|
|
862
|
+
selectionSet: {
|
|
863
|
+
kind: Kind.SELECTION_SET,
|
|
864
|
+
selections: cleaned
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
} else {
|
|
869
|
+
out.push(sel);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return out;
|
|
873
|
+
}
|
|
874
|
+
return operations.map((op) => ({
|
|
875
|
+
...op,
|
|
876
|
+
selectionSet: {
|
|
877
|
+
...op.selectionSet,
|
|
878
|
+
selections: cleanSelections(op.selectionSet.selections)
|
|
879
|
+
}
|
|
880
|
+
}));
|
|
881
|
+
}
|
|
761
882
|
function isTypeNameField(selection) {
|
|
762
883
|
return selection.kind === Kind.FIELD && !selection.alias && selection.name.value === "__typename";
|
|
763
884
|
}
|
|
885
|
+
function collectFieldSelectionSetsByPathImpl(document) {
|
|
886
|
+
const selectionSetsByPath = /* @__PURE__ */ new Map();
|
|
887
|
+
const fragments = /* @__PURE__ */ new Map();
|
|
888
|
+
for (const def of document.definitions) {
|
|
889
|
+
if (def.kind === Kind.FRAGMENT_DEFINITION) {
|
|
890
|
+
fragments.set(def.name.value, def);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
const pathStack = [];
|
|
894
|
+
const inlineFragmentCounterStack = [];
|
|
895
|
+
visit(document, {
|
|
896
|
+
[Kind.SELECTION_SET]: {
|
|
897
|
+
enter() {
|
|
898
|
+
inlineFragmentCounterStack.push(/* @__PURE__ */ new Map());
|
|
899
|
+
},
|
|
900
|
+
leave() {
|
|
901
|
+
inlineFragmentCounterStack.pop();
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
[Kind.OPERATION_DEFINITION]: {
|
|
905
|
+
enter(node) {
|
|
906
|
+
pathStack.push(`op:${node.operation}:${node.name?.value ?? ""}`);
|
|
907
|
+
},
|
|
908
|
+
leave() {
|
|
909
|
+
pathStack.pop();
|
|
910
|
+
}
|
|
911
|
+
},
|
|
912
|
+
[Kind.FRAGMENT_DEFINITION]: {
|
|
913
|
+
enter(node) {
|
|
914
|
+
pathStack.push(`frag:${node.name.value}`);
|
|
915
|
+
},
|
|
916
|
+
leave() {
|
|
917
|
+
pathStack.pop();
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
[Kind.INLINE_FRAGMENT]: {
|
|
921
|
+
enter(node) {
|
|
922
|
+
pathStack.push(
|
|
923
|
+
nextInlineFragmentPathSegment(
|
|
924
|
+
node,
|
|
925
|
+
inlineFragmentCounterStack[inlineFragmentCounterStack.length - 1]
|
|
926
|
+
)
|
|
927
|
+
);
|
|
928
|
+
},
|
|
929
|
+
leave() {
|
|
930
|
+
pathStack.pop();
|
|
931
|
+
}
|
|
932
|
+
},
|
|
933
|
+
[Kind.FIELD]: {
|
|
934
|
+
enter(node) {
|
|
935
|
+
pathStack.push(node.alias?.value ?? node.name.value);
|
|
936
|
+
if (node.selectionSet) {
|
|
937
|
+
selectionSetsByPath.set(pathStack.join(">"), node.selectionSet);
|
|
938
|
+
if (node.alias) {
|
|
939
|
+
const namePath = [...pathStack.slice(0, -1), node.name.value].join(
|
|
940
|
+
">"
|
|
941
|
+
);
|
|
942
|
+
if (!selectionSetsByPath.has(namePath)) {
|
|
943
|
+
selectionSetsByPath.set(namePath, node.selectionSet);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
leave() {
|
|
949
|
+
pathStack.pop();
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
return { selectionSetsByPath, fragments };
|
|
954
|
+
}
|
|
955
|
+
const collectFieldSelectionSetsByPath = memoize1(
|
|
956
|
+
collectFieldSelectionSetsByPathImpl
|
|
957
|
+
);
|
|
958
|
+
function nextInlineFragmentPathSegment(node, counter) {
|
|
959
|
+
const typeName = node.typeCondition?.name.value ?? "";
|
|
960
|
+
if (!counter) {
|
|
961
|
+
return `inline:${typeName}:0`;
|
|
962
|
+
}
|
|
963
|
+
const idx = counter.get(typeName) ?? 0;
|
|
964
|
+
counter.set(typeName, idx + 1);
|
|
965
|
+
return `inline:${typeName}:${idx}`;
|
|
966
|
+
}
|
|
967
|
+
function subschemaHasProvidedSelectionsImpl(stitchingInfo, subschema) {
|
|
968
|
+
const mergedTypes = stitchingInfo.mergedTypes;
|
|
969
|
+
if (!mergedTypes) {
|
|
970
|
+
return false;
|
|
971
|
+
}
|
|
972
|
+
for (const typeConfig of Object.values(mergedTypes)) {
|
|
973
|
+
if (typeConfig?.providedSelectionsByField?.has(subschema)) {
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
const subschemaHasProvidedSelections = memoize2(
|
|
980
|
+
subschemaHasProvidedSelectionsImpl
|
|
981
|
+
);
|
|
982
|
+
function lookupOriginalSelectionSet(pathStack, fieldNode, selectionSetsByPath) {
|
|
983
|
+
const exact = selectionSetsByPath.get(pathStack.join(">"));
|
|
984
|
+
if (exact || !fieldNode.alias) {
|
|
985
|
+
return exact;
|
|
986
|
+
}
|
|
987
|
+
const fallback = [...pathStack.slice(0, -1), fieldNode.name.value].join(">");
|
|
988
|
+
return selectionSetsByPath.get(fallback);
|
|
989
|
+
}
|
|
990
|
+
function intersectProvidedSelections(providedSelections, originalSelectionSet, fragments) {
|
|
991
|
+
if (!originalSelectionSet) {
|
|
992
|
+
return [];
|
|
993
|
+
}
|
|
994
|
+
const providedFieldsByName = /* @__PURE__ */ new Map();
|
|
995
|
+
const otherProvided = [];
|
|
996
|
+
for (const provided of providedSelections) {
|
|
997
|
+
if (provided.kind === Kind.FIELD) {
|
|
998
|
+
providedFieldsByName.set(provided.name.value, provided);
|
|
999
|
+
} else {
|
|
1000
|
+
otherProvided.push(provided);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
const result = [];
|
|
1004
|
+
collectMatchingOriginalSelections(
|
|
1005
|
+
originalSelectionSet,
|
|
1006
|
+
providedFieldsByName,
|
|
1007
|
+
fragments,
|
|
1008
|
+
/* @__PURE__ */ new Set(),
|
|
1009
|
+
/* @__PURE__ */ new Set(),
|
|
1010
|
+
result
|
|
1011
|
+
);
|
|
1012
|
+
return [...result, ...otherProvided];
|
|
1013
|
+
}
|
|
1014
|
+
function collectMatchingOriginalSelections(selectionSet, providedFieldsByName, fragments, seenFieldNames, seenFragmentNames, result) {
|
|
1015
|
+
for (const sel of selectionSet.selections) {
|
|
1016
|
+
if (sel.kind === Kind.FIELD) {
|
|
1017
|
+
const provided = providedFieldsByName.get(sel.name.value);
|
|
1018
|
+
if (!provided) {
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
const dedupKey = sel.alias?.value ?? sel.name.value;
|
|
1022
|
+
if (seenFieldNames.has(dedupKey)) {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
seenFieldNames.add(dedupKey);
|
|
1026
|
+
if (provided.selectionSet && sel.selectionSet) {
|
|
1027
|
+
const nested = intersectProvidedSelections(
|
|
1028
|
+
provided.selectionSet.selections,
|
|
1029
|
+
sel.selectionSet,
|
|
1030
|
+
fragments
|
|
1031
|
+
);
|
|
1032
|
+
if (!nested.length) {
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
result.push({
|
|
1036
|
+
...sel,
|
|
1037
|
+
selectionSet: {
|
|
1038
|
+
kind: Kind.SELECTION_SET,
|
|
1039
|
+
selections: nested
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
} else {
|
|
1043
|
+
result.push(sel);
|
|
1044
|
+
}
|
|
1045
|
+
} else if (sel.kind === Kind.INLINE_FRAGMENT && sel.selectionSet) {
|
|
1046
|
+
const hasDirectives = (sel.directives?.length ?? 0) > 0;
|
|
1047
|
+
const hasTypeCondition = sel.typeCondition != null;
|
|
1048
|
+
if (hasDirectives || hasTypeCondition) {
|
|
1049
|
+
const inner = [];
|
|
1050
|
+
collectMatchingOriginalSelections(
|
|
1051
|
+
sel.selectionSet,
|
|
1052
|
+
providedFieldsByName,
|
|
1053
|
+
fragments,
|
|
1054
|
+
/* @__PURE__ */ new Set(),
|
|
1055
|
+
/* @__PURE__ */ new Set(),
|
|
1056
|
+
inner
|
|
1057
|
+
);
|
|
1058
|
+
if (inner.length === 0) {
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
result.push({
|
|
1062
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
1063
|
+
typeCondition: sel.typeCondition,
|
|
1064
|
+
directives: sel.directives,
|
|
1065
|
+
selectionSet: {
|
|
1066
|
+
kind: Kind.SELECTION_SET,
|
|
1067
|
+
selections: inner
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
} else {
|
|
1071
|
+
collectMatchingOriginalSelections(
|
|
1072
|
+
sel.selectionSet,
|
|
1073
|
+
providedFieldsByName,
|
|
1074
|
+
fragments,
|
|
1075
|
+
seenFieldNames,
|
|
1076
|
+
seenFragmentNames,
|
|
1077
|
+
result
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
} else if (sel.kind === Kind.FRAGMENT_SPREAD) {
|
|
1081
|
+
const fragmentDef = fragments.get(sel.name.value);
|
|
1082
|
+
if (!fragmentDef) {
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
const hasSpreadDirectives = (sel.directives?.length ?? 0) > 0;
|
|
1086
|
+
const hasFragmentDirectives = (fragmentDef.directives?.length ?? 0) > 0;
|
|
1087
|
+
if (hasSpreadDirectives || hasFragmentDirectives) {
|
|
1088
|
+
const inner = [];
|
|
1089
|
+
collectMatchingOriginalSelections(
|
|
1090
|
+
fragmentDef.selectionSet,
|
|
1091
|
+
providedFieldsByName,
|
|
1092
|
+
fragments,
|
|
1093
|
+
/* @__PURE__ */ new Set(),
|
|
1094
|
+
/* @__PURE__ */ new Set(),
|
|
1095
|
+
inner
|
|
1096
|
+
);
|
|
1097
|
+
if (inner.length === 0) {
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
const combinedDirectives = [
|
|
1101
|
+
...sel.directives ?? [],
|
|
1102
|
+
...fragmentDef.directives ?? []
|
|
1103
|
+
];
|
|
1104
|
+
result.push({
|
|
1105
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
1106
|
+
typeCondition: fragmentDef.typeCondition,
|
|
1107
|
+
directives: combinedDirectives.length > 0 ? combinedDirectives : void 0,
|
|
1108
|
+
selectionSet: {
|
|
1109
|
+
kind: Kind.SELECTION_SET,
|
|
1110
|
+
selections: inner
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
} else {
|
|
1114
|
+
if (seenFragmentNames.has(sel.name.value)) {
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
seenFragmentNames.add(sel.name.value);
|
|
1118
|
+
collectMatchingOriginalSelections(
|
|
1119
|
+
fragmentDef.selectionSet,
|
|
1120
|
+
providedFieldsByName,
|
|
1121
|
+
fragments,
|
|
1122
|
+
seenFieldNames,
|
|
1123
|
+
seenFragmentNames,
|
|
1124
|
+
result
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
764
1130
|
function filterTypenameFields(selections) {
|
|
765
1131
|
let hasTypeNameField = false;
|
|
766
1132
|
const filteredSelections = selections.filter((selection) => {
|
|
@@ -809,15 +1175,17 @@ function collectFragmentVariables(targetSchema, fragmentSet, validFragments, val
|
|
|
809
1175
|
usedVariables = union(usedVariables, fragmentUsedVariables);
|
|
810
1176
|
if (name && !(name in fragmentSet)) {
|
|
811
1177
|
fragmentSet[name] = true;
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1178
|
+
if (selectionSet.selections.length > 0) {
|
|
1179
|
+
newFragments.push({
|
|
1180
|
+
kind: Kind.FRAGMENT_DEFINITION,
|
|
1181
|
+
name: {
|
|
1182
|
+
kind: Kind.NAME,
|
|
1183
|
+
value: name
|
|
1184
|
+
},
|
|
1185
|
+
typeCondition: fragment.typeCondition,
|
|
1186
|
+
selectionSet
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
821
1189
|
}
|
|
822
1190
|
}
|
|
823
1191
|
}
|
|
@@ -2967,7 +3335,7 @@ function extractUnavailableFields(schema, field, fieldNode, shouldAdd, fragments
|
|
|
2967
3335
|
}
|
|
2968
3336
|
return [];
|
|
2969
3337
|
}
|
|
2970
|
-
function subtractSelectionSets(selectionSetA, selectionSetB) {
|
|
3338
|
+
function subtractSelectionSets(selectionSetA, selectionSetB, fragments = {}) {
|
|
2971
3339
|
const newSelections = [];
|
|
2972
3340
|
for (const selectionA of selectionSetA.selections) {
|
|
2973
3341
|
switch (selectionA.kind) {
|
|
@@ -2983,7 +3351,7 @@ function subtractSelectionSets(selectionSetA, selectionSetB) {
|
|
|
2983
3351
|
);
|
|
2984
3352
|
if (fieldsInOtherSelectionSet.length > 0 && fieldA.selectionSet?.selections?.length) {
|
|
2985
3353
|
const newSubSelection = fieldsInOtherSelectionSet.reduce(
|
|
2986
|
-
(acc, fieldB) => fieldB.selectionSet ? subtractSelectionSets(acc, fieldB.selectionSet) : acc,
|
|
3354
|
+
(acc, fieldB) => fieldB.selectionSet ? subtractSelectionSets(acc, fieldB.selectionSet, fragments) : acc,
|
|
2987
3355
|
{
|
|
2988
3356
|
kind: Kind.SELECTION_SET,
|
|
2989
3357
|
selections: fieldA.selectionSet.selections
|
|
@@ -3013,7 +3381,11 @@ function subtractSelectionSets(selectionSetA, selectionSetB) {
|
|
|
3013
3381
|
);
|
|
3014
3382
|
if (inlineFragmentsFromB.length > 0) {
|
|
3015
3383
|
const newSubSelection = inlineFragmentsFromB.reduce(
|
|
3016
|
-
(acc, subselectionB) => subselectionB.selectionSet ? subtractSelectionSets(
|
|
3384
|
+
(acc, subselectionB) => subselectionB.selectionSet ? subtractSelectionSets(
|
|
3385
|
+
acc,
|
|
3386
|
+
subselectionB.selectionSet,
|
|
3387
|
+
fragments
|
|
3388
|
+
) : acc,
|
|
3017
3389
|
{
|
|
3018
3390
|
kind: Kind.SELECTION_SET,
|
|
3019
3391
|
selections: inlineFragmentA.selectionSet.selections
|
|
@@ -3041,7 +3413,25 @@ function subtractSelectionSets(selectionSetA, selectionSetB) {
|
|
|
3041
3413
|
}
|
|
3042
3414
|
case Kind.FRAGMENT_SPREAD: {
|
|
3043
3415
|
const fragmentSpreadA = selectionA;
|
|
3044
|
-
|
|
3416
|
+
const fragment = fragments[fragmentSpreadA.name.value];
|
|
3417
|
+
if (fragment) {
|
|
3418
|
+
const newSubSelection = subtractSelectionSets(
|
|
3419
|
+
fragment.selectionSet,
|
|
3420
|
+
selectionSetB,
|
|
3421
|
+
fragments
|
|
3422
|
+
);
|
|
3423
|
+
if (newSubSelection.selections.length) {
|
|
3424
|
+
newSelections.push({
|
|
3425
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
3426
|
+
typeCondition: fragment.typeCondition,
|
|
3427
|
+
directives: [
|
|
3428
|
+
...fragment.directives ?? [],
|
|
3429
|
+
...fragmentSpreadA.directives ?? []
|
|
3430
|
+
],
|
|
3431
|
+
selectionSet: newSubSelection
|
|
3432
|
+
});
|
|
3433
|
+
}
|
|
3434
|
+
} else if (!selectionSetB.selections.some(
|
|
3045
3435
|
(subselectionB) => subselectionB.kind === Kind.FRAGMENT_SPREAD && subselectionB.name.value === fragmentSpreadA.name.value
|
|
3046
3436
|
)) {
|
|
3047
3437
|
newSelections.push(selectionA);
|
package/package.json
CHANGED