@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 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.FIELD](fieldNode) {
696
- const parentType = typeInfo.getParentType();
697
- if (parentType) {
698
- const parentTypeName = parentType.name;
699
- const typeConfig = stitchingInfo?.mergedTypes?.[parentTypeName];
700
- if (typeConfig) {
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
- if (providedSelectionsByField) {
705
- const providedSelection = providedSelectionsByField[fieldNode.name.value];
706
- if (providedSelection) {
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
- newFragments.push({
813
- kind: graphql.Kind.FRAGMENT_DEFINITION,
814
- name: {
815
- kind: graphql.Kind.NAME,
816
- value: name
817
- },
818
- typeCondition: fragment.typeCondition,
819
- selectionSet
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(acc, subselectionB.selectionSet) : acc,
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
- if (!selectionSetB.selections.some(
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.FIELD](fieldNode) {
696
- const parentType = typeInfo.getParentType();
697
- if (parentType) {
698
- const parentTypeName = parentType.name;
699
- const typeConfig = stitchingInfo?.mergedTypes?.[parentTypeName];
700
- if (typeConfig) {
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
- if (providedSelectionsByField) {
705
- const providedSelection = providedSelectionsByField[fieldNode.name.value];
706
- if (providedSelection) {
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
- newFragments.push({
813
- kind: Kind.FRAGMENT_DEFINITION,
814
- name: {
815
- kind: Kind.NAME,
816
- value: name
817
- },
818
- typeCondition: fragment.typeCondition,
819
- selectionSet
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(acc, subselectionB.selectionSet) : acc,
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
- if (!selectionSetB.selections.some(
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphql-tools/delegate",
3
- "version": "12.0.18",
3
+ "version": "12.0.19-rc-fb25f307dd73dbdd994a65a01120efa6c42a2a52",
4
4
  "type": "module",
5
5
  "description": "A set of utils for faster development of GraphQL tools",
6
6
  "repository": {