@apollo/federation-internals 2.4.2 → 2.4.4

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.
Files changed (125) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/package.json +1 -1
  3. package/src/__tests__/operations.test.ts +175 -10
  4. package/src/operations.ts +184 -36
  5. package/dist/Subgraph.d.ts +0 -1
  6. package/dist/Subgraph.d.ts.map +0 -1
  7. package/dist/Subgraph.js +0 -2
  8. package/dist/Subgraph.js.map +0 -1
  9. package/dist/argumentCompositionStrategies.d.ts +0 -34
  10. package/dist/argumentCompositionStrategies.d.ts.map +0 -1
  11. package/dist/argumentCompositionStrategies.js +0 -35
  12. package/dist/argumentCompositionStrategies.js.map +0 -1
  13. package/dist/buildSchema.d.ts +0 -10
  14. package/dist/buildSchema.d.ts.map +0 -1
  15. package/dist/buildSchema.js +0 -362
  16. package/dist/buildSchema.js.map +0 -1
  17. package/dist/coreSpec.d.ts +0 -127
  18. package/dist/coreSpec.d.ts.map +0 -1
  19. package/dist/coreSpec.js +0 -590
  20. package/dist/coreSpec.js.map +0 -1
  21. package/dist/debug.d.ts +0 -15
  22. package/dist/debug.d.ts.map +0 -1
  23. package/dist/debug.js +0 -122
  24. package/dist/debug.js.map +0 -1
  25. package/dist/definitions.d.ts +0 -663
  26. package/dist/definitions.d.ts.map +0 -1
  27. package/dist/definitions.js +0 -2841
  28. package/dist/definitions.js.map +0 -1
  29. package/dist/directiveAndTypeSpecification.d.ts +0 -67
  30. package/dist/directiveAndTypeSpecification.d.ts.map +0 -1
  31. package/dist/directiveAndTypeSpecification.js +0 -271
  32. package/dist/directiveAndTypeSpecification.js.map +0 -1
  33. package/dist/error.d.ts +0 -128
  34. package/dist/error.d.ts.map +0 -1
  35. package/dist/error.js +0 -315
  36. package/dist/error.js.map +0 -1
  37. package/dist/extractSubgraphsFromSupergraph.d.ts +0 -8
  38. package/dist/extractSubgraphsFromSupergraph.d.ts.map +0 -1
  39. package/dist/extractSubgraphsFromSupergraph.js +0 -576
  40. package/dist/extractSubgraphsFromSupergraph.js.map +0 -1
  41. package/dist/federation.d.ts +0 -175
  42. package/dist/federation.d.ts.map +0 -1
  43. package/dist/federation.js +0 -1414
  44. package/dist/federation.js.map +0 -1
  45. package/dist/federationSpec.d.ts +0 -25
  46. package/dist/federationSpec.d.ts.map +0 -1
  47. package/dist/federationSpec.js +0 -125
  48. package/dist/federationSpec.js.map +0 -1
  49. package/dist/genErrorCodeDoc.d.ts +0 -2
  50. package/dist/genErrorCodeDoc.d.ts.map +0 -1
  51. package/dist/genErrorCodeDoc.js +0 -61
  52. package/dist/genErrorCodeDoc.js.map +0 -1
  53. package/dist/graphQLJSSchemaToAST.d.ts +0 -8
  54. package/dist/graphQLJSSchemaToAST.d.ts.map +0 -1
  55. package/dist/graphQLJSSchemaToAST.js +0 -96
  56. package/dist/graphQLJSSchemaToAST.js.map +0 -1
  57. package/dist/inaccessibleSpec.d.ts +0 -18
  58. package/dist/inaccessibleSpec.d.ts.map +0 -1
  59. package/dist/inaccessibleSpec.js +0 -655
  60. package/dist/inaccessibleSpec.js.map +0 -1
  61. package/dist/index.d.ts +0 -24
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js +0 -42
  64. package/dist/index.js.map +0 -1
  65. package/dist/introspection.d.ts +0 -6
  66. package/dist/introspection.d.ts.map +0 -1
  67. package/dist/introspection.js +0 -96
  68. package/dist/introspection.js.map +0 -1
  69. package/dist/joinSpec.d.ts +0 -51
  70. package/dist/joinSpec.d.ts.map +0 -1
  71. package/dist/joinSpec.js +0 -160
  72. package/dist/joinSpec.js.map +0 -1
  73. package/dist/knownCoreFeatures.d.ts +0 -5
  74. package/dist/knownCoreFeatures.d.ts.map +0 -1
  75. package/dist/knownCoreFeatures.js +0 -20
  76. package/dist/knownCoreFeatures.js.map +0 -1
  77. package/dist/operations.d.ts +0 -403
  78. package/dist/operations.d.ts.map +0 -1
  79. package/dist/operations.js +0 -1983
  80. package/dist/operations.js.map +0 -1
  81. package/dist/precompute.d.ts +0 -3
  82. package/dist/precompute.d.ts.map +0 -1
  83. package/dist/precompute.js +0 -54
  84. package/dist/precompute.js.map +0 -1
  85. package/dist/print.d.ts +0 -28
  86. package/dist/print.d.ts.map +0 -1
  87. package/dist/print.js +0 -299
  88. package/dist/print.js.map +0 -1
  89. package/dist/schemaUpgrader.d.ts +0 -121
  90. package/dist/schemaUpgrader.d.ts.map +0 -1
  91. package/dist/schemaUpgrader.js +0 -570
  92. package/dist/schemaUpgrader.js.map +0 -1
  93. package/dist/suggestions.d.ts +0 -3
  94. package/dist/suggestions.d.ts.map +0 -1
  95. package/dist/suggestions.js +0 -44
  96. package/dist/suggestions.js.map +0 -1
  97. package/dist/supergraphs.d.ts +0 -10
  98. package/dist/supergraphs.d.ts.map +0 -1
  99. package/dist/supergraphs.js +0 -76
  100. package/dist/supergraphs.js.map +0 -1
  101. package/dist/tagSpec.d.ts +0 -19
  102. package/dist/tagSpec.d.ts.map +0 -1
  103. package/dist/tagSpec.js +0 -66
  104. package/dist/tagSpec.js.map +0 -1
  105. package/dist/types.d.ts +0 -9
  106. package/dist/types.d.ts.map +0 -1
  107. package/dist/types.js +0 -64
  108. package/dist/types.js.map +0 -1
  109. package/dist/utils.d.ts +0 -64
  110. package/dist/utils.d.ts.map +0 -1
  111. package/dist/utils.js +0 -326
  112. package/dist/utils.js.map +0 -1
  113. package/dist/validate.d.ts +0 -4
  114. package/dist/validate.d.ts.map +0 -1
  115. package/dist/validate.js +0 -239
  116. package/dist/validate.js.map +0 -1
  117. package/dist/validation/KnownTypeNamesInFederationRule.d.ts +0 -4
  118. package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +0 -1
  119. package/dist/validation/KnownTypeNamesInFederationRule.js +0 -41
  120. package/dist/validation/KnownTypeNamesInFederationRule.js.map +0 -1
  121. package/dist/values.d.ts +0 -23
  122. package/dist/values.d.ts.map +0 -1
  123. package/dist/values.js +0 -580
  124. package/dist/values.js.map +0 -1
  125. package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG for `@apollo/federation-internals`
2
2
 
3
+ ## 2.4.4
4
+
5
+ ## 2.4.3
6
+ ### Patch Changes
7
+
8
+
9
+ - Improves the heuristics used to try to reuse the query named fragments in subgraph fetches. Said fragment will be reused ([#2541](https://github.com/apollographql/federation/pull/2541))
10
+ more often, which can lead to smaller subgraph queries (and hence overall faster processing).
11
+
3
12
  ## 2.4.2
4
13
  ### Patch Changes
5
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo/federation-internals",
3
- "version": "2.4.2",
3
+ "version": "2.4.4",
4
4
  "description": "Apollo Federation internal utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -154,8 +154,6 @@ describe('fragments optimization', () => {
154
154
  `);
155
155
 
156
156
  const optimized = withoutFragments.optimize(operation.selectionSet.fragments!);
157
- // Note that while we didn't use `onU` for `t` in the query, it's technically ok to use
158
- // it and it makes the query smaller, so it gets used.
159
157
  expect(optimized.toString()).toMatchString(`
160
158
  fragment OnT1 on T1 {
161
159
  a
@@ -171,17 +169,15 @@ describe('fragments optimization', () => {
171
169
  b
172
170
  }
173
171
 
174
- fragment OnU on U {
175
- ...OnI
176
- ...OnT1
177
- ...OnT2
178
- }
179
-
180
172
  {
181
173
  t {
182
- ...OnU
174
+ ...OnI
175
+ ...OnT1
176
+ ...OnT2
183
177
  u {
184
- ...OnU
178
+ ...OnI
179
+ ...OnT1
180
+ ...OnT2
185
181
  }
186
182
  }
187
183
  }
@@ -579,6 +575,175 @@ describe('fragments optimization', () => {
579
575
  });
580
576
  });
581
577
 
578
+ test('handles fragment matching at the top level of another fragment', () => {
579
+ const schema = parseSchema(`
580
+ type Query {
581
+ t: T
582
+ }
583
+
584
+ type T {
585
+ a: String
586
+ u: U
587
+ }
588
+
589
+ type U {
590
+ x: String
591
+ y: String
592
+ }
593
+ `);
594
+
595
+ testFragmentsRoundtrip({
596
+ schema,
597
+ query: `
598
+ fragment Frag1 on T {
599
+ a
600
+ }
601
+
602
+ fragment Frag2 on T {
603
+ u {
604
+ x
605
+ y
606
+ }
607
+ ...Frag1
608
+ }
609
+
610
+ fragment Frag3 on Query {
611
+ t {
612
+ ...Frag2
613
+ }
614
+ }
615
+
616
+ {
617
+ ...Frag3
618
+ }
619
+ `,
620
+ expanded: `
621
+ {
622
+ t {
623
+ u {
624
+ x
625
+ y
626
+ }
627
+ a
628
+ }
629
+ }
630
+ `,
631
+ });
632
+ });
633
+
634
+ test('handles fragments used in a context where they get trimmed', () => {
635
+ const schema = parseSchema(`
636
+ type Query {
637
+ t1: T1
638
+ }
639
+
640
+ interface I {
641
+ x: Int
642
+ }
643
+
644
+ type T1 implements I {
645
+ x: Int
646
+ y: Int
647
+ }
648
+
649
+ type T2 implements I {
650
+ x: Int
651
+ z: Int
652
+ }
653
+ `);
654
+
655
+ testFragmentsRoundtrip({
656
+ schema,
657
+ query: `
658
+ fragment FragOnI on I {
659
+ ... on T1 {
660
+ y
661
+ }
662
+ ... on T2 {
663
+ z
664
+ }
665
+ }
666
+
667
+ {
668
+ t1 {
669
+ ...FragOnI
670
+ }
671
+ }
672
+ `,
673
+ expanded: `
674
+ {
675
+ t1 {
676
+ y
677
+ }
678
+ }
679
+ `,
680
+ });
681
+ });
682
+
683
+ test('handles fragments used in the context of non-intersecting abstract types', () => {
684
+ const schema = parseSchema(`
685
+ type Query {
686
+ i2: I2
687
+ }
688
+
689
+ interface I1 {
690
+ x: Int
691
+ }
692
+
693
+ interface I2 {
694
+ y: Int
695
+ }
696
+
697
+ interface I3 {
698
+ z: Int
699
+ }
700
+
701
+ type T1 implements I1 & I2 {
702
+ x: Int
703
+ y: Int
704
+ }
705
+
706
+ type T2 implements I1 & I3 {
707
+ x: Int
708
+ z: Int
709
+ }
710
+ `);
711
+
712
+ testFragmentsRoundtrip({
713
+ schema,
714
+ query: `
715
+ fragment FragOnI1 on I1 {
716
+ ... on I2 {
717
+ y
718
+ }
719
+ ... on I3 {
720
+ z
721
+ }
722
+ }
723
+
724
+ {
725
+ i2 {
726
+ ...FragOnI1
727
+ }
728
+ }
729
+ `,
730
+ expanded: `
731
+ {
732
+ i2 {
733
+ ... on I1 {
734
+ ... on I2 {
735
+ y
736
+ }
737
+ ... on I3 {
738
+ z
739
+ }
740
+ }
741
+ }
742
+ }
743
+ `,
744
+ });
745
+ });
746
+
582
747
  describe('applied directives', () => {
583
748
  test('reuse fragments with directives on the fragment, but only when there is those directives', () => {
584
749
  const schema = parseSchema(`
package/src/operations.ts CHANGED
@@ -732,8 +732,13 @@ export class Operation {
732
732
  // probably not noticeable in practice so ...).
733
733
  const toDeoptimize = mapEntries(usages).filter(([_, count]) => count < minUsagesToOptimize).map(([name]) => name);
734
734
 
735
- const newFragments = optimizedSelection.fragments?.without(toDeoptimize);
736
- optimizedSelection = optimizedSelection.expandFragments(toDeoptimize, newFragments);
735
+ if (toDeoptimize.length > 0) {
736
+ const newFragments = optimizedSelection.fragments?.without(toDeoptimize);
737
+ optimizedSelection = optimizedSelection.expandFragments(toDeoptimize, newFragments);
738
+ // Expanding fragments could create some "inefficiencies" that we wouldn't have if we hadn't re-optimized
739
+ // the fragments to de-optimize it later, so we do a final "trim" pass to remove those.
740
+ optimizedSelection = optimizedSelection.trimUnsatisfiableBranches(optimizedSelection.parentType);
741
+ }
737
742
 
738
743
  return new Operation(this.schema, this.rootKind, optimizedSelection, this.variableDefinitions, this.name);
739
744
  }
@@ -841,6 +846,8 @@ export class Operation {
841
846
  export class NamedFragmentDefinition extends DirectiveTargetElement<NamedFragmentDefinition> {
842
847
  private _selectionSet: SelectionSet | undefined;
843
848
 
849
+ private readonly selectionSetsAtTypesCache = new Map<string, SelectionSet>();
850
+
844
851
  constructor(
845
852
  schema: Schema,
846
853
  readonly name: string,
@@ -852,6 +859,9 @@ export class NamedFragmentDefinition extends DirectiveTargetElement<NamedFragmen
852
859
 
853
860
  setSelectionSet(selectionSet: SelectionSet): NamedFragmentDefinition {
854
861
  assert(!this._selectionSet, 'Attempting to set the selection set of a fragment definition already built')
862
+ // We set the selection set post-construction to simplify the handling of fragments that use other fragments,
863
+ // but let's make sure we've properly used the fragment type condition as parent type of the selection set, as we should.
864
+ assert(selectionSet.parentType === this.typeCondition, `Fragment selection set parent is ${selectionSet.parentType} differs from the fragment condition type ${this.typeCondition}`);
855
865
  this._selectionSet = selectionSet;
856
866
  return this;
857
867
  }
@@ -894,32 +904,53 @@ export class NamedFragmentDefinition extends DirectiveTargetElement<NamedFragmen
894
904
  * @param type - the type at which we're looking at applying the fragment
895
905
  */
896
906
  canApplyAtType(type: CompositeType): boolean {
897
- const applyAtType = sameType(type, this.typeCondition) || runtimeTypesIntersects(type, this.typeCondition);
898
- return applyAtType
899
- && this.validForSchema(type.schema());
907
+ return sameType(type, this.typeCondition) || runtimeTypesIntersects(type, this.typeCondition);
900
908
  }
901
909
 
902
- // Checks whether this named fragment can be applied to the provided schema, which might be different
903
- // from the one the named fragment originate from.
904
- private validForSchema(schema: Schema): boolean {
905
- if (schema === this.schema()) {
906
- return true;
910
+ /**
911
+ * This methods *assumes* that `this.canApplyAtType(type)` is `true` (and may crash if this is not true), and returns
912
+ * a version fo this named fragment selection set that corresponds to the "expansion" of this named fragment at `type`
913
+ *
914
+ * The overall idea here is that if we have an interface I with 2 implementations T1 and T2, and we have a fragment like:
915
+ * ```graphql
916
+ * fragment X on I {
917
+ * ... on T1 {
918
+ * <stuff>
919
+ * }
920
+ * ... on T2 {
921
+ * <stuff>
922
+ * }
923
+ * }
924
+ * ```
925
+ * then if the current type is `T1`, then all we care about matching for this fragment is the `... on T1` part, and this method gives
926
+ * us that part.
927
+ */
928
+ selectionSetAtType(type: CompositeType): SelectionSet {
929
+ // First, if the candidate condition is an object or is the type passed, then there isn't any additional restriction to do.
930
+ if (sameType(type, this.typeCondition) || isObjectType(this.typeCondition)) {
931
+ return this.selectionSet;
907
932
  }
908
933
 
909
- const typeInSchema = schema.type(this.typeCondition.name);
910
- if (!typeInSchema || !isCompositeType(typeInSchema)) {
911
- return false;
934
+ // We should not call `trimUnsatisfiableBranches` where `type` is an abstract type (`interface` or `union`) as it currently could
935
+ // create an invalid selection set (and throw down the line). In theory, when `type` is an abstract type, we could look at the
936
+ // intersection of its runtime types with those of `this.typeCondition`, call `trimUnsatisfiableBranches` for each of the resulting
937
+ // object types, and merge all those selection sets, and this "may" result in a smaller selection at times. This is a bit complex
938
+ // and costly to do however, so we just return the selection unchanged for now, which is always valid but simply may not be absolutely
939
+ // optimal.
940
+ // Concretely, this means that there may be corner cases where a named fragment could be reused but isn't, but waiting on finding
941
+ // concrete examples where this matter to decide if it's worth the complexity.
942
+ if (!isObjectType(type)) {
943
+ return this.selectionSet;
912
944
  }
913
945
 
914
- // We try "rebasing" the selection into the provided schema and checks if that succeed.
915
- try {
916
- this.selectionSet.rebaseOn(typeInSchema);
917
- // If this succeed, it means the fragment could be applied to that schema and be valid.
918
- return true;
919
- } catch (e) {
920
- // We don't really care what kind of error was triggered; only that it doesn't work.
921
- return false;
946
+ let selectionSet = this.selectionSetsAtTypesCache.get(type.name);
947
+ if (!selectionSet) {
948
+ // Note that all we want is removing any top-level branches that don't apply due to the current type. There is no point
949
+ // in going recursive however: any simplification due to `type` stops as soon as we traverse a field. And so we don't bother.
950
+ selectionSet = this.selectionSet.trimUnsatisfiableBranches(type, { recursive: false });
951
+ this.selectionSetsAtTypesCache.set(type.name, selectionSet);
922
952
  }
953
+ return selectionSet;
923
954
  }
924
955
 
925
956
  toString(indent?: string): string {
@@ -995,6 +1026,72 @@ export class NamedFragments {
995
1026
  return mapped;
996
1027
  }
997
1028
 
1029
+ /**
1030
+ * This method:
1031
+ * - expands all nested fragments,
1032
+ * - applies the provided mapper to the selection set of the fragments,
1033
+ * - and finally re-fragments the nested fragments.
1034
+ */
1035
+ mapToExpandedSelectionSets(
1036
+ mapper: (selectionSet: SelectionSet) => SelectionSet | undefined,
1037
+ recreateFct: (frag: NamedFragmentDefinition, newSelectionSet: SelectionSet) => NamedFragmentDefinition = (f, s) => f.withUpdatedSelectionSet(s),
1038
+ ): NamedFragments | undefined {
1039
+ type FragmentInfo = {
1040
+ original: NamedFragmentDefinition,
1041
+ mappedSelectionSet: SelectionSet,
1042
+ dependsOn: string[],
1043
+ };
1044
+ const fragmentsMap = new Map<string, FragmentInfo>();
1045
+
1046
+ const removedFragments = new Set<string>();
1047
+ for (const fragment of this.definitions()) {
1048
+ const mappedSelectionSet = mapper(fragment.selectionSet.expandAllFragments().trimUnsatisfiableBranches(fragment.typeCondition));
1049
+ if (!mappedSelectionSet) {
1050
+ removedFragments.add(fragment.name);
1051
+ continue;
1052
+ }
1053
+
1054
+ const otherFragmentsUsages = new Map<string, number>();
1055
+ fragment.collectUsedFragmentNames(otherFragmentsUsages);
1056
+ fragmentsMap.set(fragment.name, {
1057
+ original: fragment,
1058
+ mappedSelectionSet,
1059
+ dependsOn: Array.from(otherFragmentsUsages.keys()),
1060
+ });
1061
+ }
1062
+
1063
+ const mappedFragments = new NamedFragments();
1064
+ while (fragmentsMap.size > 0) {
1065
+ for (const [name, info] of fragmentsMap) {
1066
+ // Note that graphQL specifies that named fragments cannot have cycles (https://spec.graphql.org/draft/#sec-Fragment-spreads-must-not-form-cycles)
1067
+ // and so we're guaranteed that on every iteration, at least one element of the map is removed (so the `while` loop will terminate).
1068
+ if (info.dependsOn.every((n) => mappedFragments.has(n) || removedFragments.has(n))) {
1069
+ const reoptimizedSelectionSet = info.mappedSelectionSet.optimize(mappedFragments);
1070
+ mappedFragments.add(recreateFct(info.original, reoptimizedSelectionSet));
1071
+ fragmentsMap.delete(name);
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ return mappedFragments.isEmpty() ? undefined : mappedFragments;
1077
+ }
1078
+
1079
+ rebaseOn(schema: Schema): NamedFragments | undefined {
1080
+ return this.mapToExpandedSelectionSets(
1081
+ (s) => {
1082
+ const rebasedType = schema.type(s.parentType.name);
1083
+ try {
1084
+ return rebasedType && isCompositeType(rebasedType) ? s.rebaseOn(rebasedType) : undefined;
1085
+ } catch (e) {
1086
+ // This means we cannot rebase this selection on the schema and thus cannot reuse that fragment on that
1087
+ // particular schema.
1088
+ return undefined;
1089
+ }
1090
+ },
1091
+ (orig, newSelection) => new NamedFragmentDefinition(schema, orig.name, newSelection.parentType).setSelectionSet(newSelection),
1092
+ );
1093
+ }
1094
+
998
1095
  validate(variableDefinitions: VariableDefinitions) {
999
1096
  for (const fragment of this.fragments.values()) {
1000
1097
  fragment.selectionSet.validate(variableDefinitions);
@@ -1142,6 +1239,36 @@ export class SelectionSet {
1142
1239
  return this;
1143
1240
  }
1144
1241
 
1242
+ // Calling optimizeSelections() will not match a fragment that would have expanded at top-level.
1243
+ // That is, say we have the selection set `{ x y }` for a top-level `Query`, and we have a fragment
1244
+ // ```
1245
+ // fragment F on Query {
1246
+ // x
1247
+ // y
1248
+ // }
1249
+ // ```
1250
+ // then calling `this.optimizeSelections(fragments)` would only apply check if F apply to `x` and
1251
+ // then `y`.
1252
+ //
1253
+ // To ensure the fragment match in this case, we "wrap" the selection into a trivial fragment of
1254
+ // the selection parent, so in the example above, we create selection `... on Query { x y}`.
1255
+ // With that, `optimizeSelections` will correctly match on the `on Query` fragment; after which
1256
+ // we can unpack the final result.
1257
+ const wrapped = new InlineFragmentSelection(new FragmentElement(this.parentType, this.parentType), this);
1258
+ const optimized = wrapped.optimize(fragments);
1259
+
1260
+ // Now, it's possible we matched a full fragment, in which case `optimized` will be just the named fragment,
1261
+ // and in that case we return a singleton selection with just that. Otherwise, it's our wrapping inline fragment
1262
+ // with the sub-selections optimized, and we just return that subselection.
1263
+ return optimized instanceof FragmentSpreadSelection
1264
+ ? selectionSetOf(this.parentType, optimized, fragments)
1265
+ : optimized.selectionSet;
1266
+ }
1267
+
1268
+ // Tries to match fragments inside each selections of this selection set, and this recursively. However, note that this
1269
+ // may not match fragments that would apply at top-level, so you should usually use `optimize` instead (this exists mostly
1270
+ // for the recursion).
1271
+ optimizeSelections(fragments: NamedFragments): SelectionSet {
1145
1272
  // Handling the case where the selection may alreayd have some fragments adds complexity,
1146
1273
  // not only because we need to deal with merging new and existing fragments, but also because
1147
1274
  // things get weird if some fragment names are in common to both. Since we currently only care
@@ -1164,8 +1291,8 @@ export class SelectionSet {
1164
1291
  return this.lazyMap((selection) => selection.expandFragments(names, updatedFragments), { fragments: updatedFragments ?? null });
1165
1292
  }
1166
1293
 
1167
- trimUnsatisfiableBranches(parentType: CompositeType): SelectionSet {
1168
- return this.lazyMap((selection) => selection.trimUnsatisfiableBranches(parentType), { parentType });
1294
+ trimUnsatisfiableBranches(parentType: CompositeType, options?: { recursive? : boolean }): SelectionSet {
1295
+ return this.lazyMap((selection) => selection.trimUnsatisfiableBranches(parentType, options), { parentType });
1169
1296
  }
1170
1297
 
1171
1298
  /**
@@ -1325,12 +1452,22 @@ export class SelectionSet {
1325
1452
  return true;
1326
1453
  }
1327
1454
 
1328
- diffIfContains(that: SelectionSet): { contains: boolean, diff?: SelectionSet } {
1455
+ // Please note that this method assumes that `candidate.canApplyAtType(parentType) === true` but it is left to the caller to
1456
+ // validate this (`canApplyAtType` is not free, and we want to avoid repeating it multiple times).
1457
+ diffWithNamedFragmentIfContained(candidate: NamedFragmentDefinition, parentType: CompositeType): { contains: boolean, diff?: SelectionSet } {
1458
+ const that = candidate.selectionSetAtType(parentType);
1329
1459
  if (this.contains(that)) {
1330
- const diff = this.minus(that);
1460
+ // One subtlety here is that at "this" sub-selections may already have been optimized with some fragments. It's
1461
+ // usually ok because `candidate` will also use those fragments, but one fragments that `candidate` can never be
1462
+ // using is itself (the `contains` check is fine with this, but it's harder to deal in `minus`). So we expand
1463
+ // the candidate we're currently looking at in "this" to avoid some issues.
1464
+ let updatedThis = this.expandFragments([candidate.name], this.fragments);
1465
+ if (updatedThis !== this) {
1466
+ updatedThis = updatedThis.trimUnsatisfiableBranches(parentType);
1467
+ }
1468
+ const diff = updatedThis.minus(that);
1331
1469
  return { contains: true, diff: diff.isEmpty() ? undefined : diff };
1332
1470
  }
1333
-
1334
1471
  return { contains: false };
1335
1472
  }
1336
1473
 
@@ -1891,7 +2028,7 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
1891
2028
 
1892
2029
  abstract expandFragments(names: string[], updatedFragments: NamedFragments | undefined): TOwnType | readonly Selection[];
1893
2030
 
1894
- abstract trimUnsatisfiableBranches(parentType: CompositeType): TOwnType | SelectionSet | undefined;
2031
+ abstract trimUnsatisfiableBranches(parentType: CompositeType, options?: { recursive? : boolean }): TOwnType | SelectionSet | undefined;
1895
2032
 
1896
2033
  minus(that: Selection): TOwnType | undefined {
1897
2034
  // If there is a subset, then we compute the diff of the subset and add that (if not empty).
@@ -1905,6 +2042,10 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
1905
2042
  return undefined;
1906
2043
  }
1907
2044
 
2045
+ // Attempts to optimize the subselection of this field selection using named fragments `candidates` _assuming_ that
2046
+ // those candidates do apply at `parentType` (that is, `candidates.every((c) => c.canApplyAtType(parentType))` is true,
2047
+ // which is ensured by the fact that `tryOptimizeSubselectionWithFragments` calls this on a subset of the candidates
2048
+ // returned by `maybeApplyingAtType`).
1908
2049
  protected tryOptimizeSubselectionOnce(_: {
1909
2050
  parentType: CompositeType,
1910
2051
  subSelection: SelectionSet,
@@ -1978,7 +2119,7 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
1978
2119
  }
1979
2120
 
1980
2121
  optimize(fragments: NamedFragments): Selection {
1981
- let optimizedSelection = this.selectionSet ? this.selectionSet.optimize(fragments) : undefined;
2122
+ let optimizedSelection = this.selectionSet ? this.selectionSet.optimizeSelections(fragments) : undefined;
1982
2123
  const fieldBaseType = baseType(this.element.definition.type!);
1983
2124
  if (isCompositeType(fieldBaseType) && optimizedSelection) {
1984
2125
  const optimized = this.tryOptimizeSubselectionWithFragments({
@@ -2017,7 +2158,7 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
2017
2158
  }{
2018
2159
  let optimizedSelection = subSelection;
2019
2160
  for (const candidate of candidates) {
2020
- const { contains, diff } = optimizedSelection.diffIfContains(candidate.selectionSet);
2161
+ const { contains, diff } = optimizedSelection.diffWithNamedFragmentIfContained(candidate, parentType);
2021
2162
  if (contains) {
2022
2163
  // We can optimize the selection with this fragment. The replaced sub-selection will be
2023
2164
  // comprised of this new spread and the remaining `diff` if there is any.
@@ -2133,14 +2274,14 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
2133
2274
  return this.mapToSelectionSet((s) => s.expandAllFragments());
2134
2275
  }
2135
2276
 
2136
- trimUnsatisfiableBranches(_: CompositeType): FieldSelection {
2277
+ trimUnsatisfiableBranches(_: CompositeType, options?: { recursive? : boolean }): FieldSelection {
2137
2278
  if (!this.selectionSet) {
2138
2279
  return this;
2139
2280
  }
2140
2281
 
2141
2282
  const base = baseType(this.element.definition.type!)
2142
2283
  assert(isCompositeType(base), () => `Field ${this.element} should not have a sub-selection`);
2143
- const trimmed = this.mapToSelectionSet((s) => s.trimUnsatisfiableBranches(base));
2284
+ const trimmed = (options?.recursive ?? true) ? this.mapToSelectionSet((s) => s.trimUnsatisfiableBranches(base)) : this;
2144
2285
  // In rare caes, it's possible that everything in the sub-selection was trimmed away and so the
2145
2286
  // sub-selection is empty. Which suggest something may be wrong with this part of the query
2146
2287
  // intent, but the query was valid while keeping an empty sub-selection isn't. So in that
@@ -2327,7 +2468,7 @@ class InlineFragmentSelection extends FragmentSelection {
2327
2468
  }
2328
2469
 
2329
2470
  optimize(fragments: NamedFragments): FragmentSelection {
2330
- let optimizedSelection = this.selectionSet.optimize(fragments);
2471
+ let optimizedSelection = this.selectionSet.optimizeSelections(fragments);
2331
2472
  const typeCondition = this.element.typeCondition;
2332
2473
  if (typeCondition) {
2333
2474
  const optimized = this.tryOptimizeSubselectionWithFragments({
@@ -2363,7 +2504,7 @@ class InlineFragmentSelection extends FragmentSelection {
2363
2504
  }{
2364
2505
  let optimizedSelection = subSelection;
2365
2506
  for (const candidate of candidates) {
2366
- const { contains, diff } = optimizedSelection.diffIfContains(candidate.selectionSet);
2507
+ const { contains, diff } = optimizedSelection.diffWithNamedFragmentIfContained(candidate, parentType);
2367
2508
  if (contains) {
2368
2509
  // The candidate selection is included in our sub-selection. One remaining thing to take into account
2369
2510
  // is applied directives: if the candidate has directives, then we can only use it if 1) there is
@@ -2434,12 +2575,14 @@ class InlineFragmentSelection extends FragmentSelection {
2434
2575
  : this.withUpdatedComponents(newElement, newSelection);
2435
2576
  }
2436
2577
 
2437
- trimUnsatisfiableBranches(currentType: CompositeType): FragmentSelection | SelectionSet | undefined {
2578
+ trimUnsatisfiableBranches(currentType: CompositeType, options?: { recursive? : boolean }): FragmentSelection | SelectionSet | undefined {
2579
+ const recursive = options?.recursive ?? true;
2580
+
2438
2581
  const thisCondition = this.element.typeCondition;
2439
2582
  // Note that if the condition has directives, we preserve the fragment no matter what.
2440
2583
  if (this.element.appliedDirectives.length === 0) {
2441
2584
  if (!thisCondition || currentType === this.element.typeCondition) {
2442
- const trimmed = this.selectionSet.trimUnsatisfiableBranches(currentType);
2585
+ const trimmed = this.selectionSet.trimUnsatisfiableBranches(currentType, options);
2443
2586
  return trimmed.isEmpty() ? undefined : trimmed;
2444
2587
  }
2445
2588
 
@@ -2454,12 +2597,17 @@ class InlineFragmentSelection extends FragmentSelection {
2454
2597
  if (isObjectType(thisCondition) || !possibleRuntimeTypes(thisCondition).includes(currentType)) {
2455
2598
  return undefined;
2456
2599
  } else {
2457
- const trimmed = this.selectionSet.trimUnsatisfiableBranches(currentType);
2600
+ const trimmed =this.selectionSet.trimUnsatisfiableBranches(currentType, options);
2458
2601
  return trimmed.isEmpty() ? undefined : trimmed;
2459
2602
  }
2460
2603
  }
2461
2604
  }
2462
2605
 
2606
+ // As we preserve the current fragment, the rest is about recursing. If we don't recurse, we're done
2607
+ if (!recursive) {
2608
+ return this;
2609
+ }
2610
+
2463
2611
  // In all other cases, we first recurse on the sub-selection.
2464
2612
  const trimmedSelectionSet = this.selectionSet.trimUnsatisfiableBranches(this.element.typeCondition ?? this.parentType);
2465
2613
 
@@ -1 +0,0 @@
1
- //# sourceMappingURL=Subgraph.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Subgraph.d.ts","sourceRoot":"","sources":["../src/Subgraph.ts"],"names":[],"mappings":""}
package/dist/Subgraph.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- //# sourceMappingURL=Subgraph.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Subgraph.js","sourceRoot":"","sources":["../src/Subgraph.ts"],"names":[],"mappings":""}
@@ -1,34 +0,0 @@
1
- import { InputType, ListType, NonNullType, Schema } from "./definitions";
2
- export type ArgumentCompositionStrategy = {
3
- name: string;
4
- supportedTypes: (schema: Schema) => InputType[];
5
- mergeValues: (values: any[]) => any;
6
- };
7
- export declare const ARGUMENT_COMPOSITION_STRATEGIES: {
8
- MAX: {
9
- name: string;
10
- supportedTypes: (schema: Schema) => NonNullType<import("./definitions").ScalarType>[];
11
- mergeValues: (values: any[]) => number;
12
- };
13
- MIN: {
14
- name: string;
15
- supportedTypes: (schema: Schema) => NonNullType<import("./definitions").ScalarType>[];
16
- mergeValues: (values: any[]) => number;
17
- };
18
- SUM: {
19
- name: string;
20
- supportedTypes: (schema: Schema) => NonNullType<import("./definitions").ScalarType>[];
21
- mergeValues: (values: any[]) => any;
22
- };
23
- INTERSECTION: {
24
- name: string;
25
- supportedTypes: (schema: Schema) => NonNullType<ListType<NonNullType<import("./definitions").ScalarType>>>[];
26
- mergeValues: (values: any[]) => any;
27
- };
28
- UNION: {
29
- name: string;
30
- supportedTypes: (schema: Schema) => NonNullType<ListType<NonNullType<import("./definitions").ScalarType>>>[];
31
- mergeValues: (values: any[]) => any;
32
- };
33
- };
34
- //# sourceMappingURL=argumentCompositionStrategies.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"argumentCompositionStrategies.d.ts","sourceRoot":"","sources":["../src/argumentCompositionStrategies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAExE,MAAM,MAAM,2BAA2B,GAAG;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;IAChD,WAAW,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;CACrC,CAAA;AAED,eAAO,MAAM,+BAA+B;;;iCAGf,MAAM;8BACT,GAAG,EAAE;;;;iCAIF,MAAM;8BACT,GAAG,EAAE;;;;iCAIF,MAAM;8BACT,GAAG,EAAE;;;;iCAIF,MAAM;8BACT,GAAG,EAAE;;;;iCAIF,MAAM;8BACT,GAAG,EAAE;;CAM9B,CAAA"}