@apollo/federation-internals 2.4.8 → 2.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/extractSubgraphsFromSupergraph.d.ts.map +1 -1
- package/dist/extractSubgraphsFromSupergraph.js +25 -7
- package/dist/extractSubgraphsFromSupergraph.js.map +1 -1
- package/dist/operations.d.ts +23 -7
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +82 -52
- package/dist/operations.js.map +1 -1
- package/package.json +1 -1
- package/src/extractSubgraphsFromSupergraph.ts +36 -10
- package/src/operations.ts +157 -103
package/src/operations.ts
CHANGED
|
@@ -478,7 +478,7 @@ export class FragmentElement extends AbstractOperationElement<FragmentElement> {
|
|
|
478
478
|
// schema.
|
|
479
479
|
const { canRebase, rebasedCondition } = this.canRebaseOn(parentType);
|
|
480
480
|
validate(
|
|
481
|
-
canRebase,
|
|
481
|
+
canRebase,
|
|
482
482
|
() => `Cannot add fragment of condition "${typeCondition}" (runtimes: [${possibleRuntimeTypes(typeCondition!)}]) to parent type "${parentType}" (runtimes: ${possibleRuntimeTypes(parentType)})`
|
|
483
483
|
);
|
|
484
484
|
return this.withUpdatedTypes(parentType, rebasedCondition);
|
|
@@ -697,7 +697,7 @@ export type RootOperationPath = {
|
|
|
697
697
|
path: OperationPath
|
|
698
698
|
}
|
|
699
699
|
|
|
700
|
-
// Computes for every fragment, which other fragments use it (so the reverse of it's dependencies, the other fragment it uses).
|
|
700
|
+
// Computes for every fragment, which other fragments use it (so the reverse of it's dependencies, the other fragment it uses).
|
|
701
701
|
function computeFragmentsDependents(fragments: NamedFragments): SetMultiMap<string, string> {
|
|
702
702
|
const reverseDeps = new SetMultiMap<string, string>();
|
|
703
703
|
for (const fragment of fragments.definitions()) {
|
|
@@ -934,9 +934,17 @@ export class Operation {
|
|
|
934
934
|
// leaving this is not a huge deal and it's not worth the complexity, but it could be that we can
|
|
935
935
|
// refactor all this later to avoid this case without additional complexity.
|
|
936
936
|
if (finalFragments) {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
937
|
+
// Note that removing a fragment might lead to another fragment being unused, so we need to iterate
|
|
938
|
+
// until there is nothing more to remove, or we're out of fragments.
|
|
939
|
+
let beforeRemoval: NamedFragments;
|
|
940
|
+
do {
|
|
941
|
+
beforeRemoval = finalFragments;
|
|
942
|
+
const usages = new Map<string, number>();
|
|
943
|
+
// Collecting all usages, both in the selection and within other fragments.
|
|
944
|
+
optimizedSelection.collectUsedFragmentNames(usages);
|
|
945
|
+
finalFragments.collectUsedFragmentNames(usages);
|
|
946
|
+
finalFragments = finalFragments.filter((f) => (usages.get(f.name) ?? 0) > 0);
|
|
947
|
+
} while (finalFragments && finalFragments.size < beforeRemoval.size);
|
|
940
948
|
}
|
|
941
949
|
}
|
|
942
950
|
|
|
@@ -1098,27 +1106,26 @@ export class NamedFragmentDefinition extends DirectiveTargetElement<NamedFragmen
|
|
|
1098
1106
|
|
|
1099
1107
|
/**
|
|
1100
1108
|
* Whether this fragment may apply _directly_ at the provided type, meaning that the fragment sub-selection
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1109
|
+
* (_without_ the fragment condition, hence the "directly") can be normalized at `type` and this without
|
|
1110
|
+
* "widening" the runtime types to types that do not intersect the fragment condition.
|
|
1103
1111
|
*
|
|
1104
1112
|
* For that to be true, we need one of this to be true:
|
|
1105
1113
|
* 1. the runtime types of the fragment condition must be at least as general as those of the provided `type`.
|
|
1106
|
-
* Otherwise, putting it at `type` without its condition would "generalize" more than fragment meant to (and
|
|
1107
|
-
* so we'd "
|
|
1114
|
+
* Otherwise, putting it at `type` without its condition would "generalize" more than the fragment meant to (and
|
|
1115
|
+
* so we'd "widen" the runtime types more than what the query meant to.
|
|
1108
1116
|
* 2. either `type` and `this.typeCondition` are equal, or `type` is an object or `this.typeCondition` is a union
|
|
1109
1117
|
* The idea is that, assuming our 1st point, then:
|
|
1110
1118
|
* - if both are equal, things works trivially.
|
|
1111
1119
|
* - if `type` is an object, `this.typeCondition` is either the same object, or a union/interface for which
|
|
1112
1120
|
* type is a valid runtime. In all case, anything valid on `this.typeCondition` would apply to `type` too.
|
|
1113
|
-
* - if `this.typeCondition` is a union, then it's selection can only have fragments
|
|
1114
|
-
* (save for `__typename`), and
|
|
1121
|
+
* - if `this.typeCondition` is a union, then it's selection can only have fragments at top-level
|
|
1122
|
+
* (no fields save for `__typename`), and normalising is always fine with top-level fragments.
|
|
1115
1123
|
* But in any other case, both types must be abstract (if `this.typeCondition` is an object, the 1st condition
|
|
1116
1124
|
* imply `type` can only be the same type) and we're in one of:
|
|
1117
1125
|
* - `type` and `this.typeCondition` are both different interfaces (that intersect but are different).
|
|
1118
1126
|
* - `type` is aunion and `this.typeCondition` an interface.
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
* not have that particular field).
|
|
1127
|
+
* And in both cases, since `this.typeCondition` is an interface, the fragment selection set may have field selections
|
|
1128
|
+
* on that interface, and those fields may not be valid for `type`.
|
|
1122
1129
|
*
|
|
1123
1130
|
* @param type - the type at which we're looking at applying the fragment
|
|
1124
1131
|
*/
|
|
@@ -1274,6 +1281,15 @@ export class NamedFragments {
|
|
|
1274
1281
|
return this.fragments.values();
|
|
1275
1282
|
}
|
|
1276
1283
|
|
|
1284
|
+
/**
|
|
1285
|
+
* Collect the usages of fragments that are used within the selection of other fragments.
|
|
1286
|
+
*/
|
|
1287
|
+
collectUsedFragmentNames(collector: Map<string, number>) {
|
|
1288
|
+
for (const fragment of this.definitions()) {
|
|
1289
|
+
fragment.collectUsedFragmentNames(collector);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1277
1293
|
map(mapper: (def: NamedFragmentDefinition) => NamedFragmentDefinition): NamedFragments {
|
|
1278
1294
|
const mapped = new NamedFragments();
|
|
1279
1295
|
for (const def of this.fragments.values()) {
|
|
@@ -1460,7 +1476,7 @@ class DeferNormalizer {
|
|
|
1460
1476
|
}
|
|
1461
1477
|
|
|
1462
1478
|
export enum ContainsResult {
|
|
1463
|
-
// Note: enum values are numbers in the end, and 0 means false in JS, so we should keep `NOT_CONTAINED` first
|
|
1479
|
+
// Note: enum values are numbers in the end, and 0 means false in JS, so we should keep `NOT_CONTAINED` first
|
|
1464
1480
|
// so that using the result of `contains` as a boolean works.
|
|
1465
1481
|
NOT_CONTAINED,
|
|
1466
1482
|
STRICTLY_CONTAINED,
|
|
@@ -1570,7 +1586,7 @@ export class SelectionSet {
|
|
|
1570
1586
|
// With that, `optimizeSelections` will correctly match on the `on Query` fragment; after which
|
|
1571
1587
|
// we can unpack the final result.
|
|
1572
1588
|
const wrapped = new InlineFragmentSelection(new FragmentElement(this.parentType, this.parentType), this);
|
|
1573
|
-
const validator = FieldsConflictValidator.build(this);
|
|
1589
|
+
const validator = FieldsConflictMultiBranchValidator.ofInitial(FieldsConflictValidator.build(this));
|
|
1574
1590
|
const optimized = wrapped.optimize(fragments, validator);
|
|
1575
1591
|
|
|
1576
1592
|
// Now, it's possible we matched a full fragment, in which case `optimized` will be just the named fragment,
|
|
@@ -1584,7 +1600,7 @@ export class SelectionSet {
|
|
|
1584
1600
|
// Tries to match fragments inside each selections of this selection set, and this recursively. However, note that this
|
|
1585
1601
|
// may not match fragments that would apply at top-level, so you should usually use `optimize` instead (this exists mostly
|
|
1586
1602
|
// for the recursion).
|
|
1587
|
-
optimizeSelections(fragments: NamedFragments, validator:
|
|
1603
|
+
optimizeSelections(fragments: NamedFragments, validator: FieldsConflictMultiBranchValidator): SelectionSet {
|
|
1588
1604
|
return this.lazyMap((selection) => selection.optimize(fragments, validator));
|
|
1589
1605
|
}
|
|
1590
1606
|
|
|
@@ -1593,7 +1609,7 @@ export class SelectionSet {
|
|
|
1593
1609
|
}
|
|
1594
1610
|
|
|
1595
1611
|
/**
|
|
1596
|
-
* Applies some normalization rules to this selection set in the context of the provided `parentType`.
|
|
1612
|
+
* Applies some normalization rules to this selection set in the context of the provided `parentType`.
|
|
1597
1613
|
*
|
|
1598
1614
|
* Normalization mostly removes unecessary/redundant inline fragments, so that for instance, with
|
|
1599
1615
|
* schema:
|
|
@@ -1657,10 +1673,12 @@ export class SelectionSet {
|
|
|
1657
1673
|
* }
|
|
1658
1674
|
* ```
|
|
1659
1675
|
*
|
|
1660
|
-
* For this operation to be valid (to not throw), `parentType` must be such
|
|
1661
|
-
*
|
|
1662
|
-
*
|
|
1663
|
-
* that is
|
|
1676
|
+
* For this operation to be valid (to not throw), `parentType` must be such that every field selection in
|
|
1677
|
+
* this selection set is such that the field parent type intersects `parentType` (there is no limitation
|
|
1678
|
+
* on the fragment selections, though any fragment selections whose condition do not intersects `parentType`
|
|
1679
|
+
* will be discarded). Note that `this.normalize(this.parentType)` is always valid and useful, but it is
|
|
1680
|
+
* also possible to pass a `parentType` that is more "restrictive" than the selection current parent type
|
|
1681
|
+
* (as long as the top-level fields of this selection set can be rebased on that type).
|
|
1664
1682
|
*
|
|
1665
1683
|
* Passing the option `recursive == false` makes the normalization only apply at the top-level, removing
|
|
1666
1684
|
* any unecessary top-level inline fragments, possibly multiple layers of them, but we never recurse
|
|
@@ -1792,6 +1810,11 @@ export class SelectionSet {
|
|
|
1792
1810
|
: ContainsResult.STRICTLY_CONTAINED;
|
|
1793
1811
|
}
|
|
1794
1812
|
|
|
1813
|
+
containsTopLevelField(field: Field): boolean {
|
|
1814
|
+
const selection = this._keyedSelections.get(field.key());
|
|
1815
|
+
return !!selection && selection.element.equals(field);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1795
1818
|
/**
|
|
1796
1819
|
* Returns a selection set that correspond to this selection set but where any of the selections in the
|
|
1797
1820
|
* provided selection set have been remove.
|
|
@@ -2192,7 +2215,7 @@ function makeSelectionSet(parentType: CompositeType, keyedUpdates: MultiMap<stri
|
|
|
2192
2215
|
}
|
|
2193
2216
|
|
|
2194
2217
|
/**
|
|
2195
|
-
* A simple wrapper over a `SelectionSetUpdates` that allows to conveniently build a selection set, then add some more updates and build it again, etc...
|
|
2218
|
+
* A simple wrapper over a `SelectionSetUpdates` that allows to conveniently build a selection set, then add some more updates and build it again, etc...
|
|
2196
2219
|
*/
|
|
2197
2220
|
export class MutableSelectionSet<TMemoizedValue extends { [key: string]: any } = {}> {
|
|
2198
2221
|
private computed: SelectionSet | undefined;
|
|
@@ -2327,7 +2350,7 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
|
|
|
2327
2350
|
|
|
2328
2351
|
abstract key(): string;
|
|
2329
2352
|
|
|
2330
|
-
abstract optimize(fragments: NamedFragments, validator:
|
|
2353
|
+
abstract optimize(fragments: NamedFragments, validator: FieldsConflictMultiBranchValidator): Selection;
|
|
2331
2354
|
|
|
2332
2355
|
abstract toSelectionNode(): SelectionNode;
|
|
2333
2356
|
|
|
@@ -2425,7 +2448,7 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
|
|
|
2425
2448
|
parentType: CompositeType,
|
|
2426
2449
|
subSelection: SelectionSet,
|
|
2427
2450
|
fragments: NamedFragments,
|
|
2428
|
-
validator:
|
|
2451
|
+
validator: FieldsConflictMultiBranchValidator,
|
|
2429
2452
|
canUseFullMatchingFragment: (match: NamedFragmentDefinition) => boolean,
|
|
2430
2453
|
}): SelectionSet | NamedFragmentDefinition {
|
|
2431
2454
|
// We limit to fragments whose selection could be applied "directly" at `parentType`, meaning without taking the fragment condition
|
|
@@ -2433,7 +2456,7 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
|
|
|
2433
2456
|
// have been "normalized away" and so we want for this very call to be called on the fragment whose type _is_ the fragment condition (at
|
|
2434
2457
|
// which point, this `maybeApplyingDirectlyAtType` method will apply.
|
|
2435
2458
|
// Also note that this is because we have this restriction that calling `expandedSelectionSetAtType` is ok.
|
|
2436
|
-
|
|
2459
|
+
const candidates = fragments.maybeApplyingDirectlyAtType(parentType);
|
|
2437
2460
|
if (candidates.length === 0) {
|
|
2438
2461
|
return subSelection;
|
|
2439
2462
|
}
|
|
@@ -2558,54 +2581,24 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
|
|
|
2558
2581
|
}
|
|
2559
2582
|
}
|
|
2560
2583
|
|
|
2561
|
-
class
|
|
2584
|
+
class FieldsConflictMultiBranchValidator {
|
|
2562
2585
|
private usedSpreadTrimmedPartAtLevel?: FieldsConflictValidator[];
|
|
2563
2586
|
|
|
2564
|
-
|
|
2565
|
-
private readonly
|
|
2587
|
+
constructor(
|
|
2588
|
+
private readonly validators: FieldsConflictValidator[],
|
|
2566
2589
|
) {
|
|
2567
2590
|
}
|
|
2568
2591
|
|
|
2569
|
-
static
|
|
2570
|
-
return
|
|
2592
|
+
static ofInitial(validator: FieldsConflictValidator): FieldsConflictMultiBranchValidator {
|
|
2593
|
+
return new FieldsConflictMultiBranchValidator([validator]);
|
|
2571
2594
|
}
|
|
2572
2595
|
|
|
2573
|
-
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
if (!atResponseName) {
|
|
2580
|
-
atResponseName = new Map<Field, CollectedFieldsInSet>();
|
|
2581
|
-
atLevel.set(responseName, atResponseName);
|
|
2582
|
-
}
|
|
2583
|
-
if (field.selectionSet) {
|
|
2584
|
-
let forField = atResponseName.get(field.element) ?? [];
|
|
2585
|
-
atResponseName.set(field.element, forField.concat(field.selectionSet.fieldsInSet()));
|
|
2586
|
-
} else {
|
|
2587
|
-
atResponseName.set(field.element, null);
|
|
2588
|
-
}
|
|
2589
|
-
}
|
|
2590
|
-
|
|
2591
|
-
const byResponseName = new Map<string, Map<Field, FieldsConflictValidator | null>>();
|
|
2592
|
-
for (const [name, level] of atLevel.entries()) {
|
|
2593
|
-
const atResponseName = new Map<Field, FieldsConflictValidator | null>();
|
|
2594
|
-
for (const [field, collectedFields] of level) {
|
|
2595
|
-
const validator = collectedFields ? FieldsConflictValidator.forLevel(collectedFields) : null;
|
|
2596
|
-
atResponseName.set(field, validator);
|
|
2597
|
-
}
|
|
2598
|
-
byResponseName.set(name, atResponseName);
|
|
2599
|
-
}
|
|
2600
|
-
return new FieldsConflictValidator(byResponseName);
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
forField(field: Field): FieldsConflictValidator {
|
|
2604
|
-
const validator = this.byResponseName.get(field.responseName())?.get(field);
|
|
2605
|
-
// This should be called on validator built on the exact selection set from field this `field` is coming, so
|
|
2606
|
-
// we should find it or the code is buggy.
|
|
2607
|
-
assert(validator, () => `Should have found validator for ${field}`);
|
|
2608
|
-
return validator;
|
|
2596
|
+
forField(field: Field): FieldsConflictMultiBranchValidator {
|
|
2597
|
+
const forAllBranches = this.validators.flatMap((vs) => vs.forField(field));
|
|
2598
|
+
// As this is called on (non-leaf) field from the same query on which we have build the initial validators, we
|
|
2599
|
+
// should find at least one validator.
|
|
2600
|
+
assert(forAllBranches.length > 0, `Shoud have found at least one validator for ${field}`);
|
|
2601
|
+
return new FieldsConflictMultiBranchValidator(forAllBranches);
|
|
2609
2602
|
}
|
|
2610
2603
|
|
|
2611
2604
|
// At this point, we known that the fragment, restricted to the current parent type, matches a subset of the
|
|
@@ -2627,7 +2620,7 @@ class FieldsConflictValidator {
|
|
|
2627
2620
|
return true;
|
|
2628
2621
|
}
|
|
2629
2622
|
|
|
2630
|
-
if (!this.doMergeWith(validator)) {
|
|
2623
|
+
if (!this.validators.every((v) => v.doMergeWith(validator))) {
|
|
2631
2624
|
return false;
|
|
2632
2625
|
}
|
|
2633
2626
|
|
|
@@ -2654,6 +2647,61 @@ class FieldsConflictValidator {
|
|
|
2654
2647
|
this.usedSpreadTrimmedPartAtLevel.push(validator);
|
|
2655
2648
|
return true;
|
|
2656
2649
|
}
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
class FieldsConflictValidator {
|
|
2653
|
+
private constructor(
|
|
2654
|
+
private readonly byResponseName: Map<string, Map<Field, FieldsConflictValidator | null>>,
|
|
2655
|
+
) {
|
|
2656
|
+
}
|
|
2657
|
+
|
|
2658
|
+
static build(s: SelectionSet): FieldsConflictValidator {
|
|
2659
|
+
return FieldsConflictValidator.forLevel(s.fieldsInSet());
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
private static forLevel(level: CollectedFieldsInSet): FieldsConflictValidator {
|
|
2663
|
+
const atLevel = new Map<string, Map<Field, CollectedFieldsInSet | null>>();
|
|
2664
|
+
|
|
2665
|
+
for (const { field } of level) {
|
|
2666
|
+
const responseName = field.element.responseName();
|
|
2667
|
+
let atResponseName = atLevel.get(responseName);
|
|
2668
|
+
if (!atResponseName) {
|
|
2669
|
+
atResponseName = new Map<Field, CollectedFieldsInSet>();
|
|
2670
|
+
atLevel.set(responseName, atResponseName);
|
|
2671
|
+
}
|
|
2672
|
+
if (field.selectionSet) {
|
|
2673
|
+
// It's unlikely that we've seen the same `field.element` as we don't particularly "intern" `Field` object (so even if the exact same field
|
|
2674
|
+
// is used in 2 parts of a selection set, it will probably be a different `Field` object), so the `get` below will probably mostly return `undefined`,
|
|
2675
|
+
// but it wouldn't be incorrect to re-use a `Field` object multiple side, so no reason not to handle that correctly.
|
|
2676
|
+
const forField = atResponseName.get(field.element) ?? [];
|
|
2677
|
+
atResponseName.set(field.element, forField.concat(field.selectionSet.fieldsInSet()));
|
|
2678
|
+
} else {
|
|
2679
|
+
// Note that whether a `FieldSelection` has `selectionSet` or not is entirely determined by whether the field type is a composite type
|
|
2680
|
+
// or not, so even if we've seen a previous version of `field.element` before, we know it's guarantee to have had no `selectionSet`.
|
|
2681
|
+
// So the `set` below may overwrite a previous entry, but it would be a `null` so no harm done.
|
|
2682
|
+
atResponseName.set(field.element, null);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
const byResponseName = new Map<string, Map<Field, FieldsConflictValidator | null>>();
|
|
2687
|
+
for (const [name, level] of atLevel.entries()) {
|
|
2688
|
+
const atResponseName = new Map<Field, FieldsConflictValidator | null>();
|
|
2689
|
+
for (const [field, collectedFields] of level) {
|
|
2690
|
+
const validator = collectedFields ? FieldsConflictValidator.forLevel(collectedFields) : null;
|
|
2691
|
+
atResponseName.set(field, validator);
|
|
2692
|
+
}
|
|
2693
|
+
byResponseName.set(name, atResponseName);
|
|
2694
|
+
}
|
|
2695
|
+
return new FieldsConflictValidator(byResponseName);
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
forField(field: Field): FieldsConflictValidator[] {
|
|
2699
|
+
const byResponseName = this.byResponseName.get(field.responseName());
|
|
2700
|
+
if (!byResponseName) {
|
|
2701
|
+
return [];
|
|
2702
|
+
}
|
|
2703
|
+
return mapValues(byResponseName).filter((v): v is FieldsConflictValidator => !!v);
|
|
2704
|
+
}
|
|
2657
2705
|
|
|
2658
2706
|
doMergeWith(that: FieldsConflictValidator): boolean {
|
|
2659
2707
|
for (const [responseName, thisFields] of this.byResponseName.entries()) {
|
|
@@ -2761,7 +2809,7 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
2761
2809
|
return this.element.key();
|
|
2762
2810
|
}
|
|
2763
2811
|
|
|
2764
|
-
optimize(fragments: NamedFragments, validator:
|
|
2812
|
+
optimize(fragments: NamedFragments, validator: FieldsConflictMultiBranchValidator): Selection {
|
|
2765
2813
|
const fieldBaseType = baseType(this.element.definition.type!);
|
|
2766
2814
|
if (!isCompositeType(fieldBaseType) || !this.selectionSet) {
|
|
2767
2815
|
return this;
|
|
@@ -2770,22 +2818,20 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
2770
2818
|
const fieldValidator = validator.forField(this.element);
|
|
2771
2819
|
|
|
2772
2820
|
// First, see if we can reuse fragments for the selection of this field.
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
canUseFullMatchingFragment: (fragment) => fragment.appliedDirectives.length === 0,
|
|
2782
|
-
});
|
|
2821
|
+
const optimized = this.tryOptimizeSubselectionWithFragments({
|
|
2822
|
+
parentType: fieldBaseType,
|
|
2823
|
+
subSelection: this.selectionSet,
|
|
2824
|
+
fragments,
|
|
2825
|
+
validator: fieldValidator,
|
|
2826
|
+
// We can never apply a fragments that has directives on it at the field level.
|
|
2827
|
+
canUseFullMatchingFragment: (fragment) => fragment.appliedDirectives.length === 0,
|
|
2828
|
+
});
|
|
2783
2829
|
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2830
|
+
let optimizedSelection;
|
|
2831
|
+
if (optimized instanceof NamedFragmentDefinition) {
|
|
2832
|
+
optimizedSelection = selectionSetOf(fieldBaseType, new FragmentSpreadSelection(fieldBaseType, fragments, optimized, []));
|
|
2833
|
+
} else {
|
|
2834
|
+
optimizedSelection = optimized;
|
|
2789
2835
|
}
|
|
2790
2836
|
|
|
2791
2837
|
// Then, recurse inside the field sub-selection (note that if we matched some fragments above,
|
|
@@ -2822,7 +2868,7 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
2822
2868
|
}
|
|
2823
2869
|
|
|
2824
2870
|
/**
|
|
2825
|
-
* Returns a field selection "equivalent" to the one represented by this object, but such that its parent type
|
|
2871
|
+
* Returns a field selection "equivalent" to the one represented by this object, but such that its parent type
|
|
2826
2872
|
* is the one provided as argument.
|
|
2827
2873
|
*
|
|
2828
2874
|
* Obviously, this operation will only succeed if this selection (both the field itself and its subselections)
|
|
@@ -2988,7 +3034,7 @@ export abstract class FragmentSelection extends AbstractSelection<FragmentElemen
|
|
|
2988
3034
|
);
|
|
2989
3035
|
}
|
|
2990
3036
|
}
|
|
2991
|
-
|
|
3037
|
+
|
|
2992
3038
|
filterRecursiveDepthFirst(predicate: (selection: Selection) => boolean): FragmentSelection | undefined {
|
|
2993
3039
|
// Note that we essentially expand all fragments as part of this.
|
|
2994
3040
|
const updatedSelectionSet = this.selectionSet.filterRecursiveDepthFirst(predicate);
|
|
@@ -2998,7 +3044,7 @@ export abstract class FragmentSelection extends AbstractSelection<FragmentElemen
|
|
|
2998
3044
|
|
|
2999
3045
|
return predicate(thisWithFilteredSelectionSet) ? thisWithFilteredSelectionSet : undefined;
|
|
3000
3046
|
}
|
|
3001
|
-
|
|
3047
|
+
|
|
3002
3048
|
hasDefer(): boolean {
|
|
3003
3049
|
return this.element.hasDefer() || this.selectionSet.hasDefer();
|
|
3004
3050
|
}
|
|
@@ -3006,6 +3052,26 @@ export abstract class FragmentSelection extends AbstractSelection<FragmentElemen
|
|
|
3006
3052
|
abstract equals(that: Selection): boolean;
|
|
3007
3053
|
|
|
3008
3054
|
abstract contains(that: Selection): ContainsResult;
|
|
3055
|
+
|
|
3056
|
+
normalize({ parentType, recursive }: { parentType: CompositeType, recursive? : boolean }): FragmentSelection | SelectionSet | undefined {
|
|
3057
|
+
const thisCondition = this.element.typeCondition;
|
|
3058
|
+
|
|
3059
|
+
// This method assumes by contract that `parentType` runtimes intersects `this.parentType`'s, but `parentType`
|
|
3060
|
+
// runtimes may be a subset. So first check if the selection should not be discarded on that account (that
|
|
3061
|
+
// is, we should not keep the selection if its condition runtimes don't intersect at all with those of
|
|
3062
|
+
// `parentType` as that would ultimately make an invalid selection set).
|
|
3063
|
+
if (thisCondition && parentType !== this.parentType) {
|
|
3064
|
+
const conditionRuntimes = possibleRuntimeTypes(thisCondition);
|
|
3065
|
+
const typeRuntimes = possibleRuntimeTypes(parentType);
|
|
3066
|
+
if (!conditionRuntimes.some((t) => typeRuntimes.includes(t))) {
|
|
3067
|
+
return undefined;
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
return this.normalizeKnowingItIntersects({ parentType, recursive });
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
protected abstract normalizeKnowingItIntersects({ parentType, recursive }: { parentType: CompositeType, recursive? : boolean }): FragmentSelection | SelectionSet | undefined;
|
|
3009
3075
|
}
|
|
3010
3076
|
|
|
3011
3077
|
class InlineFragmentSelection extends FragmentSelection {
|
|
@@ -3090,7 +3156,7 @@ class InlineFragmentSelection extends FragmentSelection {
|
|
|
3090
3156
|
};
|
|
3091
3157
|
}
|
|
3092
3158
|
|
|
3093
|
-
optimize(fragments: NamedFragments, validator:
|
|
3159
|
+
optimize(fragments: NamedFragments, validator: FieldsConflictMultiBranchValidator): FragmentSelection {
|
|
3094
3160
|
let optimizedSelection = this.selectionSet;
|
|
3095
3161
|
|
|
3096
3162
|
// First, see if we can reuse fragments for the selection of this field.
|
|
@@ -3173,21 +3239,9 @@ class InlineFragmentSelection extends FragmentSelection {
|
|
|
3173
3239
|
: this.withUpdatedComponents(newElement, newSelection);
|
|
3174
3240
|
}
|
|
3175
3241
|
|
|
3176
|
-
|
|
3242
|
+
protected normalizeKnowingItIntersects({ parentType, recursive }: { parentType: CompositeType, recursive? : boolean }): FragmentSelection | SelectionSet | undefined {
|
|
3177
3243
|
const thisCondition = this.element.typeCondition;
|
|
3178
3244
|
|
|
3179
|
-
// This method assumes by contract that `parentType` runtimes intersects `this.parentType`'s, but `parentType`
|
|
3180
|
-
// runtimes may be a subset. So first check if the selection should not be discarded on that account (that
|
|
3181
|
-
// is, we should not keep the selection if its condition runtimes don't intersect at all with those of
|
|
3182
|
-
// `parentType` as that would ultimately make an invalid selection set).
|
|
3183
|
-
if (thisCondition && parentType !== this.parentType) {
|
|
3184
|
-
const conditionRuntimes = possibleRuntimeTypes(thisCondition);
|
|
3185
|
-
const typeRuntimes = possibleRuntimeTypes(parentType);
|
|
3186
|
-
if (!conditionRuntimes.some((t) => typeRuntimes.includes(t))) {
|
|
3187
|
-
return undefined;
|
|
3188
|
-
}
|
|
3189
|
-
}
|
|
3190
|
-
|
|
3191
3245
|
// We know the condition is "valid", but it may not be useful. That said, if the condition has directives,
|
|
3192
3246
|
// we preserve the fragment no matter what.
|
|
3193
3247
|
if (this.element.appliedDirectives.length === 0) {
|
|
@@ -3327,7 +3381,7 @@ class FragmentSpreadSelection extends FragmentSelection {
|
|
|
3327
3381
|
assert(false, `Unsupported`);
|
|
3328
3382
|
}
|
|
3329
3383
|
|
|
3330
|
-
|
|
3384
|
+
normalizeKnowingItIntersects({ parentType }: { parentType: CompositeType }): FragmentSelection {
|
|
3331
3385
|
// We must update the spread parent type if necessary since we're not going deeper,
|
|
3332
3386
|
// or we'll be fundamentally losing context.
|
|
3333
3387
|
assert(parentType.schema() === this.parentType.schema(), 'Should not try to normalize using a type from another schema');
|
|
@@ -3363,7 +3417,7 @@ class FragmentSpreadSelection extends FragmentSelection {
|
|
|
3363
3417
|
};
|
|
3364
3418
|
}
|
|
3365
3419
|
|
|
3366
|
-
optimize(_1: NamedFragments, _2:
|
|
3420
|
+
optimize(_1: NamedFragments, _2: FieldsConflictMultiBranchValidator): FragmentSelection {
|
|
3367
3421
|
return this;
|
|
3368
3422
|
}
|
|
3369
3423
|
|