@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.
- package/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/src/__tests__/operations.test.ts +175 -10
- package/src/operations.ts +184 -36
- package/dist/Subgraph.d.ts +0 -1
- package/dist/Subgraph.d.ts.map +0 -1
- package/dist/Subgraph.js +0 -2
- package/dist/Subgraph.js.map +0 -1
- package/dist/argumentCompositionStrategies.d.ts +0 -34
- package/dist/argumentCompositionStrategies.d.ts.map +0 -1
- package/dist/argumentCompositionStrategies.js +0 -35
- package/dist/argumentCompositionStrategies.js.map +0 -1
- package/dist/buildSchema.d.ts +0 -10
- package/dist/buildSchema.d.ts.map +0 -1
- package/dist/buildSchema.js +0 -362
- package/dist/buildSchema.js.map +0 -1
- package/dist/coreSpec.d.ts +0 -127
- package/dist/coreSpec.d.ts.map +0 -1
- package/dist/coreSpec.js +0 -590
- package/dist/coreSpec.js.map +0 -1
- package/dist/debug.d.ts +0 -15
- package/dist/debug.d.ts.map +0 -1
- package/dist/debug.js +0 -122
- package/dist/debug.js.map +0 -1
- package/dist/definitions.d.ts +0 -663
- package/dist/definitions.d.ts.map +0 -1
- package/dist/definitions.js +0 -2841
- package/dist/definitions.js.map +0 -1
- package/dist/directiveAndTypeSpecification.d.ts +0 -67
- package/dist/directiveAndTypeSpecification.d.ts.map +0 -1
- package/dist/directiveAndTypeSpecification.js +0 -271
- package/dist/directiveAndTypeSpecification.js.map +0 -1
- package/dist/error.d.ts +0 -128
- package/dist/error.d.ts.map +0 -1
- package/dist/error.js +0 -315
- package/dist/error.js.map +0 -1
- package/dist/extractSubgraphsFromSupergraph.d.ts +0 -8
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +0 -1
- package/dist/extractSubgraphsFromSupergraph.js +0 -576
- package/dist/extractSubgraphsFromSupergraph.js.map +0 -1
- package/dist/federation.d.ts +0 -175
- package/dist/federation.d.ts.map +0 -1
- package/dist/federation.js +0 -1414
- package/dist/federation.js.map +0 -1
- package/dist/federationSpec.d.ts +0 -25
- package/dist/federationSpec.d.ts.map +0 -1
- package/dist/federationSpec.js +0 -125
- package/dist/federationSpec.js.map +0 -1
- package/dist/genErrorCodeDoc.d.ts +0 -2
- package/dist/genErrorCodeDoc.d.ts.map +0 -1
- package/dist/genErrorCodeDoc.js +0 -61
- package/dist/genErrorCodeDoc.js.map +0 -1
- package/dist/graphQLJSSchemaToAST.d.ts +0 -8
- package/dist/graphQLJSSchemaToAST.d.ts.map +0 -1
- package/dist/graphQLJSSchemaToAST.js +0 -96
- package/dist/graphQLJSSchemaToAST.js.map +0 -1
- package/dist/inaccessibleSpec.d.ts +0 -18
- package/dist/inaccessibleSpec.d.ts.map +0 -1
- package/dist/inaccessibleSpec.js +0 -655
- package/dist/inaccessibleSpec.js.map +0 -1
- package/dist/index.d.ts +0 -24
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -42
- package/dist/index.js.map +0 -1
- package/dist/introspection.d.ts +0 -6
- package/dist/introspection.d.ts.map +0 -1
- package/dist/introspection.js +0 -96
- package/dist/introspection.js.map +0 -1
- package/dist/joinSpec.d.ts +0 -51
- package/dist/joinSpec.d.ts.map +0 -1
- package/dist/joinSpec.js +0 -160
- package/dist/joinSpec.js.map +0 -1
- package/dist/knownCoreFeatures.d.ts +0 -5
- package/dist/knownCoreFeatures.d.ts.map +0 -1
- package/dist/knownCoreFeatures.js +0 -20
- package/dist/knownCoreFeatures.js.map +0 -1
- package/dist/operations.d.ts +0 -403
- package/dist/operations.d.ts.map +0 -1
- package/dist/operations.js +0 -1983
- package/dist/operations.js.map +0 -1
- package/dist/precompute.d.ts +0 -3
- package/dist/precompute.d.ts.map +0 -1
- package/dist/precompute.js +0 -54
- package/dist/precompute.js.map +0 -1
- package/dist/print.d.ts +0 -28
- package/dist/print.d.ts.map +0 -1
- package/dist/print.js +0 -299
- package/dist/print.js.map +0 -1
- package/dist/schemaUpgrader.d.ts +0 -121
- package/dist/schemaUpgrader.d.ts.map +0 -1
- package/dist/schemaUpgrader.js +0 -570
- package/dist/schemaUpgrader.js.map +0 -1
- package/dist/suggestions.d.ts +0 -3
- package/dist/suggestions.d.ts.map +0 -1
- package/dist/suggestions.js +0 -44
- package/dist/suggestions.js.map +0 -1
- package/dist/supergraphs.d.ts +0 -10
- package/dist/supergraphs.d.ts.map +0 -1
- package/dist/supergraphs.js +0 -76
- package/dist/supergraphs.js.map +0 -1
- package/dist/tagSpec.d.ts +0 -19
- package/dist/tagSpec.d.ts.map +0 -1
- package/dist/tagSpec.js +0 -66
- package/dist/tagSpec.js.map +0 -1
- package/dist/types.d.ts +0 -9
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -64
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -64
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -326
- package/dist/utils.js.map +0 -1
- package/dist/validate.d.ts +0 -4
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js +0 -239
- package/dist/validate.js.map +0 -1
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts +0 -4
- package/dist/validation/KnownTypeNamesInFederationRule.d.ts.map +0 -1
- package/dist/validation/KnownTypeNamesInFederationRule.js +0 -41
- package/dist/validation/KnownTypeNamesInFederationRule.js.map +0 -1
- package/dist/values.d.ts +0 -23
- package/dist/values.d.ts.map +0 -1
- package/dist/values.js +0 -580
- package/dist/values.js.map +0 -1
- 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
|
@@ -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
|
-
...
|
|
174
|
+
...OnI
|
|
175
|
+
...OnT1
|
|
176
|
+
...OnT2
|
|
183
177
|
u {
|
|
184
|
-
...
|
|
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
|
-
|
|
736
|
-
|
|
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
|
-
|
|
898
|
-
return applyAtType
|
|
899
|
-
&& this.validForSchema(type.schema());
|
|
907
|
+
return sameType(type, this.typeCondition) || runtimeTypesIntersects(type, this.typeCondition);
|
|
900
908
|
}
|
|
901
909
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
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
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
//
|
|
918
|
-
|
|
919
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
package/dist/Subgraph.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=Subgraph.d.ts.map
|
package/dist/Subgraph.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Subgraph.d.ts","sourceRoot":"","sources":["../src/Subgraph.ts"],"names":[],"mappings":""}
|
package/dist/Subgraph.js
DELETED
package/dist/Subgraph.js.map
DELETED
|
@@ -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"}
|