@apollo/federation-internals 2.5.0 → 2.5.1
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/operations.d.ts +43 -9
- package/dist/operations.d.ts.map +1 -1
- package/dist/operations.js +98 -42
- package/dist/operations.js.map +1 -1
- package/package.json +1 -1
- package/src/operations.ts +178 -54
package/src/operations.ts
CHANGED
|
@@ -85,7 +85,11 @@ abstract class AbstractOperationElement<T extends AbstractOperationElement<T>> e
|
|
|
85
85
|
|
|
86
86
|
abstract asPathElement(): string | undefined;
|
|
87
87
|
|
|
88
|
-
abstract rebaseOn(parentType: CompositeType): T;
|
|
88
|
+
abstract rebaseOn(args: { parentType: CompositeType, errorIfCannotRebase: boolean }): T | undefined;
|
|
89
|
+
|
|
90
|
+
rebaseOnOrError(parentType: CompositeType): T {
|
|
91
|
+
return this.rebaseOn({ parentType, errorIfCannotRebase: true })!;
|
|
92
|
+
}
|
|
89
93
|
|
|
90
94
|
abstract withUpdatedDirectives(newDirectives: readonly Directive<any>[]): T;
|
|
91
95
|
|
|
@@ -290,7 +294,7 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
|
|
|
290
294
|
}
|
|
291
295
|
}
|
|
292
296
|
|
|
293
|
-
rebaseOn(parentType: CompositeType): Field<TArgs> {
|
|
297
|
+
rebaseOn({ parentType, errorIfCannotRebase }: { parentType: CompositeType, errorIfCannotRebase: boolean }): Field<TArgs> | undefined {
|
|
294
298
|
const fieldParent = this.definition.parent;
|
|
295
299
|
if (parentType === fieldParent) {
|
|
296
300
|
return this;
|
|
@@ -300,12 +304,16 @@ export class Field<TArgs extends {[key: string]: any} = {[key: string]: any}> ex
|
|
|
300
304
|
return this.withUpdatedDefinition(parentType.typenameField()!);
|
|
301
305
|
}
|
|
302
306
|
|
|
303
|
-
validate(
|
|
304
|
-
this.canRebaseOn(parentType),
|
|
305
|
-
() => `Cannot add selection of field "${this.definition.coordinate}" to selection set of parent type "${parentType}"`
|
|
306
|
-
);
|
|
307
307
|
const fieldDef = parentType.field(this.name);
|
|
308
|
-
|
|
308
|
+
const canRebase = this.canRebaseOn(parentType) && fieldDef;
|
|
309
|
+
if (!canRebase) {
|
|
310
|
+
validate(
|
|
311
|
+
!errorIfCannotRebase,
|
|
312
|
+
() => `Cannot add selection of field "${this.definition.coordinate}" to selection set of parent type "${parentType}"`
|
|
313
|
+
);
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
|
|
309
317
|
return this.withUpdatedDefinition(fieldDef);
|
|
310
318
|
}
|
|
311
319
|
|
|
@@ -466,7 +474,7 @@ export class FragmentElement extends AbstractOperationElement<FragmentElement> {
|
|
|
466
474
|
return newFragment;
|
|
467
475
|
}
|
|
468
476
|
|
|
469
|
-
rebaseOn(parentType: CompositeType): FragmentElement {
|
|
477
|
+
rebaseOn({ parentType, errorIfCannotRebase }: { parentType: CompositeType, errorIfCannotRebase: boolean }): FragmentElement | undefined {
|
|
470
478
|
const fragmentParent = this.parentType;
|
|
471
479
|
const typeCondition = this.typeCondition;
|
|
472
480
|
if (parentType === fragmentParent) {
|
|
@@ -477,10 +485,13 @@ export class FragmentElement extends AbstractOperationElement<FragmentElement> {
|
|
|
477
485
|
// to update the source type of the fragment, but also "rebase" the condition to the selection set
|
|
478
486
|
// schema.
|
|
479
487
|
const { canRebase, rebasedCondition } = this.canRebaseOn(parentType);
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
488
|
+
if (!canRebase) {
|
|
489
|
+
validate(
|
|
490
|
+
!errorIfCannotRebase,
|
|
491
|
+
() => `Cannot add fragment of condition "${typeCondition}" (runtimes: [${possibleRuntimeTypes(typeCondition!)}]) to parent type "${parentType}" (runtimes: ${possibleRuntimeTypes(parentType)})`
|
|
492
|
+
);
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
484
495
|
return this.withUpdatedTypes(parentType, rebasedCondition);
|
|
485
496
|
}
|
|
486
497
|
|
|
@@ -1232,7 +1243,7 @@ export class NamedFragmentDefinition extends DirectiveTargetElement<NamedFragmen
|
|
|
1232
1243
|
}
|
|
1233
1244
|
|
|
1234
1245
|
toString(indent?: string): string {
|
|
1235
|
-
return
|
|
1246
|
+
return `fragment ${this.name} on ${this.typeCondition}${this.appliedDirectivesToString()} ${this.selectionSet.toString(false, true, indent)}`;
|
|
1236
1247
|
}
|
|
1237
1248
|
}
|
|
1238
1249
|
|
|
@@ -1363,21 +1374,39 @@ export class NamedFragments {
|
|
|
1363
1374
|
});
|
|
1364
1375
|
}
|
|
1365
1376
|
|
|
1377
|
+
// When we rebase named fragments on a subgraph schema, only a subset of what the fragment handles may belong
|
|
1378
|
+
// to that particular subgraph. And there are a few sub-cases where that subset is such that we basically need or
|
|
1379
|
+
// want to consider to ignore the fragment for that subgraph, and that is when:
|
|
1380
|
+
// 1. the subset that apply is actually empty. The fragment wouldn't be valid in this case anyway.
|
|
1381
|
+
// 2. the subset is a single leaf field: in that case, using the one field directly is just shorter than using
|
|
1382
|
+
// the fragment, so we consider the fragment don't really apply to that subgraph. Technically, using the
|
|
1383
|
+
// fragment could still be of value if the fragment name is a lot smaller than the one field name, but it's
|
|
1384
|
+
// enough of a niche case that we ignore it. Note in particular that one sub-case of this rule that is likely
|
|
1385
|
+
// to be common is when the subset ends up being just `__typename`: this would basically mean the fragment
|
|
1386
|
+
// don't really apply to the subgraph, and that this will ensure this is the case.
|
|
1387
|
+
private selectionSetIsWorthUsing(selectionSet: SelectionSet): boolean {
|
|
1388
|
+
const selections = selectionSet.selections();
|
|
1389
|
+
if (selections.length === 0) {
|
|
1390
|
+
return false;
|
|
1391
|
+
}
|
|
1392
|
+
if (selections.length === 1) {
|
|
1393
|
+
const s = selections[0];
|
|
1394
|
+
return !(s.kind === 'FieldSelection' && s.element.isLeafField());
|
|
1395
|
+
}
|
|
1396
|
+
return true;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1366
1399
|
rebaseOn(schema: Schema): NamedFragments | undefined {
|
|
1367
1400
|
return this.mapInDependencyOrder((fragment, newFragments) => {
|
|
1368
1401
|
const rebasedType = schema.type(fragment.selectionSet.parentType.name);
|
|
1369
|
-
|
|
1370
|
-
if (!rebasedType || !isCompositeType(rebasedType)) {
|
|
1371
|
-
return undefined;
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
const rebasedSelection = fragment.selectionSet.rebaseOn(rebasedType, newFragments);
|
|
1375
|
-
return new NamedFragmentDefinition(schema, fragment.name, rebasedType).setSelectionSet(rebasedSelection);
|
|
1376
|
-
} catch (e) {
|
|
1377
|
-
// This means we cannot rebase this selection on the schema and thus cannot reuse that fragment on that
|
|
1378
|
-
// particular schema.
|
|
1402
|
+
if (!rebasedType || !isCompositeType(rebasedType)) {
|
|
1379
1403
|
return undefined;
|
|
1380
1404
|
}
|
|
1405
|
+
|
|
1406
|
+
const rebasedSelection = fragment.selectionSet.rebaseOn({ parentType: rebasedType, fragments: newFragments, errorIfCannotRebase: false });
|
|
1407
|
+
return this.selectionSetIsWorthUsing(rebasedSelection)
|
|
1408
|
+
? new NamedFragmentDefinition(schema, fragment.name, rebasedType).setSelectionSet(rebasedSelection)
|
|
1409
|
+
: undefined;
|
|
1381
1410
|
});
|
|
1382
1411
|
}
|
|
1383
1412
|
|
|
@@ -1515,6 +1544,20 @@ export class SelectionSet {
|
|
|
1515
1544
|
return this._keyedSelections.has(typenameFieldName);
|
|
1516
1545
|
}
|
|
1517
1546
|
|
|
1547
|
+
withoutTopLevelTypenameField(): SelectionSet {
|
|
1548
|
+
if (!this.hasTopLevelTypenameField) {
|
|
1549
|
+
return this;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
const newKeyedSelections = new Map<string, Selection>();
|
|
1553
|
+
for (const [key, selection] of this._keyedSelections) {
|
|
1554
|
+
if (key !== typenameFieldName) {
|
|
1555
|
+
newKeyedSelections.set(key, selection);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return new SelectionSet(this.parentType, newKeyedSelections);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1518
1561
|
fieldsInSet(): CollectedFieldsInSet {
|
|
1519
1562
|
const fields = new Array<{ path: string[], field: FieldSelection }>();
|
|
1520
1563
|
for (const selection of this.selections()) {
|
|
@@ -1759,14 +1802,25 @@ export class SelectionSet {
|
|
|
1759
1802
|
return updated.isEmpty() ? undefined : updated;
|
|
1760
1803
|
}
|
|
1761
1804
|
|
|
1762
|
-
rebaseOn(
|
|
1805
|
+
rebaseOn({
|
|
1806
|
+
parentType,
|
|
1807
|
+
fragments,
|
|
1808
|
+
errorIfCannotRebase,
|
|
1809
|
+
}: {
|
|
1810
|
+
parentType: CompositeType,
|
|
1811
|
+
fragments: NamedFragments | undefined
|
|
1812
|
+
errorIfCannotRebase: boolean,
|
|
1813
|
+
}): SelectionSet {
|
|
1763
1814
|
if (this.parentType === parentType) {
|
|
1764
1815
|
return this;
|
|
1765
1816
|
}
|
|
1766
1817
|
|
|
1767
1818
|
const newSelections = new Map<string, Selection>();
|
|
1768
1819
|
for (const selection of this.selections()) {
|
|
1769
|
-
|
|
1820
|
+
const rebasedSelection = selection.rebaseOn({ parentType, fragments, errorIfCannotRebase });
|
|
1821
|
+
if (rebasedSelection) {
|
|
1822
|
+
newSelections.set(selection.key(), rebasedSelection);
|
|
1823
|
+
}
|
|
1770
1824
|
}
|
|
1771
1825
|
|
|
1772
1826
|
return new SelectionSet(parentType, newSelections);
|
|
@@ -1790,22 +1844,36 @@ export class SelectionSet {
|
|
|
1790
1844
|
return true;
|
|
1791
1845
|
}
|
|
1792
1846
|
|
|
1793
|
-
contains(that: SelectionSet): ContainsResult {
|
|
1847
|
+
contains(that: SelectionSet, options?: { ignoreMissingTypename?: boolean }): ContainsResult {
|
|
1848
|
+
const ignoreMissingTypename = options?.ignoreMissingTypename ?? false;
|
|
1794
1849
|
if (that._selections.length > this._selections.length) {
|
|
1795
|
-
|
|
1850
|
+
// If `that` has more selections but we're ignoring missing __typename, then in the case where
|
|
1851
|
+
// `that` has a __typename but `this` does not, then we need the length of `that` to be at
|
|
1852
|
+
// least 2 more than that of `this` to be able to conclude there is no contains.
|
|
1853
|
+
if (!ignoreMissingTypename || that._selections.length > this._selections.length + 1 || this.hasTopLevelTypenameField() || !that.hasTopLevelTypenameField()) {
|
|
1854
|
+
return ContainsResult.NOT_CONTAINED;
|
|
1855
|
+
}
|
|
1796
1856
|
}
|
|
1797
1857
|
|
|
1798
1858
|
let isEqual = true;
|
|
1859
|
+
let didIgnoreTypename = false;
|
|
1799
1860
|
for (const [key, thatSelection] of that._keyedSelections) {
|
|
1861
|
+
if (key === typenameFieldName && ignoreMissingTypename) {
|
|
1862
|
+
if (!this._keyedSelections.has(typenameFieldName)) {
|
|
1863
|
+
didIgnoreTypename = true;
|
|
1864
|
+
}
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1800
1868
|
const thisSelection = this._keyedSelections.get(key);
|
|
1801
|
-
const selectionResult = thisSelection?.contains(thatSelection);
|
|
1869
|
+
const selectionResult = thisSelection?.contains(thatSelection, options);
|
|
1802
1870
|
if (selectionResult === undefined || selectionResult === ContainsResult.NOT_CONTAINED) {
|
|
1803
1871
|
return ContainsResult.NOT_CONTAINED;
|
|
1804
1872
|
}
|
|
1805
1873
|
isEqual &&= selectionResult === ContainsResult.EQUAL;
|
|
1806
1874
|
}
|
|
1807
1875
|
|
|
1808
|
-
return isEqual && that._selections.length === this._selections.length
|
|
1876
|
+
return isEqual && that._selections.length === (this._selections.length + (didIgnoreTypename ? 1 : 0))
|
|
1809
1877
|
? ContainsResult.EQUAL
|
|
1810
1878
|
: ContainsResult.STRICTLY_CONTAINED;
|
|
1811
1879
|
}
|
|
@@ -2164,10 +2232,10 @@ function makeSelection(parentType: CompositeType, updates: SelectionUpdate[], fr
|
|
|
2164
2232
|
|
|
2165
2233
|
// Optimize for the simple case of a single selection, as we don't have to do anything complex to merge the sub-selections.
|
|
2166
2234
|
if (updates.length === 1 && first instanceof AbstractSelection) {
|
|
2167
|
-
return first.
|
|
2235
|
+
return first.rebaseOnOrError({ parentType, fragments });
|
|
2168
2236
|
}
|
|
2169
2237
|
|
|
2170
|
-
const element = updateElement(first).
|
|
2238
|
+
const element = updateElement(first).rebaseOnOrError(parentType);
|
|
2171
2239
|
const subSelectionParentType = element.kind === 'Field' ? element.baseType() : element.castedType();
|
|
2172
2240
|
if (!isCompositeType(subSelectionParentType)) {
|
|
2173
2241
|
// This is a leaf, so all updates should correspond ot the same field and we just use the first.
|
|
@@ -2356,7 +2424,11 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
|
|
|
2356
2424
|
|
|
2357
2425
|
abstract validate(variableDefinitions: VariableDefinitions): void;
|
|
2358
2426
|
|
|
2359
|
-
abstract rebaseOn(parentType: CompositeType, fragments: NamedFragments | undefined): TOwnType;
|
|
2427
|
+
abstract rebaseOn(args: { parentType: CompositeType, fragments: NamedFragments | undefined, errorIfCannotRebase: boolean}): TOwnType | undefined;
|
|
2428
|
+
|
|
2429
|
+
rebaseOnOrError({ parentType, fragments }: { parentType: CompositeType, fragments: NamedFragments | undefined }): TOwnType {
|
|
2430
|
+
return this.rebaseOn({ parentType, fragments, errorIfCannotRebase: true})!;
|
|
2431
|
+
}
|
|
2360
2432
|
|
|
2361
2433
|
get parentType(): CompositeType {
|
|
2362
2434
|
return this.element.parentType;
|
|
@@ -2468,7 +2540,6 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
|
|
|
2468
2540
|
const applyingFragments: { fragment: NamedFragmentDefinition, atType: FragmentRestrictionAtType }[] = [];
|
|
2469
2541
|
for (const candidate of candidates) {
|
|
2470
2542
|
const atType = candidate.expandedSelectionSetAtType(parentType);
|
|
2471
|
-
const selectionSetAtType = atType.selectionSet;
|
|
2472
2543
|
// It's possible that while the fragment technically applies at `parentType`, it's "rebasing" on
|
|
2473
2544
|
// `parentType` is empty, or contains only `__typename`. For instance, suppose we have
|
|
2474
2545
|
// a union `U = A | B | C`, and then a fragment:
|
|
@@ -2487,11 +2558,22 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
|
|
|
2487
2558
|
//
|
|
2488
2559
|
// Using `F` in those cases is, while not 100% incorrect, at least not productive, and so we
|
|
2489
2560
|
// skip it that case. This is essentially an optimisation.
|
|
2490
|
-
if (
|
|
2561
|
+
if (atType.selectionSet.isEmpty() || (atType.selectionSet.selections().length === 1 && atType.selectionSet.selections()[0].isTypenameField())) {
|
|
2491
2562
|
continue;
|
|
2492
2563
|
}
|
|
2493
2564
|
|
|
2494
|
-
|
|
2565
|
+
// As we check inclusion, we ignore the case where the fragment queries __typename but the subSelection does not.
|
|
2566
|
+
// The rational is that querying `__typename` unecessarily is mostly harmless (it always works and it's super cheap)
|
|
2567
|
+
// so we don't want to not use a fragment just to save querying a `__typename` in a few cases. But the underlying
|
|
2568
|
+
// context of why this matters is that the query planner always requests __typename for abstract type, and will do
|
|
2569
|
+
// so in fragments too, but we can have a field that _does_ return an abstract type within a fragment, but that
|
|
2570
|
+
// _does not_ end up returning an abstract type when applied in a "more specific" context (think a fragment on
|
|
2571
|
+
// an interface I1 where a inside field returns another interface I2, but applied in the context of a implementation
|
|
2572
|
+
// type of I1 where that particular field returns an implementation of I2 rather than I2 directly; we would have
|
|
2573
|
+
// added __typename to the fragment (because it's all interfaces), but the selection itself, which only deals
|
|
2574
|
+
// with object type, may not have __typename requested; using the fragment might still be a good idea, and
|
|
2575
|
+
// querying __typename needlessly is a very small price to pay for that).
|
|
2576
|
+
const res = subSelection.contains(atType.selectionSet, { ignoreMissingTypename: true });
|
|
2495
2577
|
|
|
2496
2578
|
if (res === ContainsResult.EQUAL) {
|
|
2497
2579
|
if (canUseFullMatchingFragment(candidate)) {
|
|
@@ -2568,7 +2650,7 @@ abstract class AbstractSelection<TElement extends OperationElement, TIsLeaf exte
|
|
|
2568
2650
|
|
|
2569
2651
|
let notCoveredByFragments = subSelection;
|
|
2570
2652
|
const optimized = new SelectionSetUpdates();
|
|
2571
|
-
for (const { fragment, atType} of filteredApplyingFragments) {
|
|
2653
|
+
for (const { fragment, atType } of filteredApplyingFragments) {
|
|
2572
2654
|
if (!validator.checkCanReuseFragmentAndTrackIt(atType)) {
|
|
2573
2655
|
continue;
|
|
2574
2656
|
}
|
|
@@ -2874,12 +2956,24 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
2874
2956
|
* Obviously, this operation will only succeed if this selection (both the field itself and its subselections)
|
|
2875
2957
|
* make sense from the provided parent type. If this is not the case, this method will throw.
|
|
2876
2958
|
*/
|
|
2877
|
-
rebaseOn(
|
|
2959
|
+
rebaseOn({
|
|
2960
|
+
parentType,
|
|
2961
|
+
fragments,
|
|
2962
|
+
errorIfCannotRebase,
|
|
2963
|
+
}: {
|
|
2964
|
+
parentType: CompositeType,
|
|
2965
|
+
fragments: NamedFragments | undefined,
|
|
2966
|
+
errorIfCannotRebase: boolean,
|
|
2967
|
+
}): FieldSelection | undefined {
|
|
2878
2968
|
if (this.element.parentType === parentType) {
|
|
2879
2969
|
return this;
|
|
2880
2970
|
}
|
|
2881
2971
|
|
|
2882
|
-
const rebasedElement = this.element.rebaseOn(parentType);
|
|
2972
|
+
const rebasedElement = this.element.rebaseOn({ parentType, errorIfCannotRebase });
|
|
2973
|
+
if (!rebasedElement) {
|
|
2974
|
+
return undefined;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2883
2977
|
if (!this.selectionSet) {
|
|
2884
2978
|
return this.withUpdatedElement(rebasedElement);
|
|
2885
2979
|
}
|
|
@@ -2890,7 +2984,8 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
2890
2984
|
}
|
|
2891
2985
|
|
|
2892
2986
|
validate(isCompositeType(rebasedBase), () => `Cannot rebase field selection ${this} on ${parentType}: rebased field base return type ${rebasedBase} is not composite`);
|
|
2893
|
-
|
|
2987
|
+
const rebasedSelectionSet = this.selectionSet.rebaseOn({ parentType: rebasedBase, fragments, errorIfCannotRebase });
|
|
2988
|
+
return rebasedSelectionSet.isEmpty() ? undefined : this.withUpdatedComponents(rebasedElement, rebasedSelectionSet);
|
|
2894
2989
|
}
|
|
2895
2990
|
|
|
2896
2991
|
/**
|
|
@@ -2997,7 +3092,7 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
2997
3092
|
return !!that.selectionSet && this.selectionSet.equals(that.selectionSet);
|
|
2998
3093
|
}
|
|
2999
3094
|
|
|
3000
|
-
contains(that: Selection): ContainsResult {
|
|
3095
|
+
contains(that: Selection, options?: { ignoreMissingTypename?: boolean }): ContainsResult {
|
|
3001
3096
|
if (!(that instanceof FieldSelection) || !this.element.equals(that.element)) {
|
|
3002
3097
|
return ContainsResult.NOT_CONTAINED;
|
|
3003
3098
|
}
|
|
@@ -3007,7 +3102,7 @@ export class FieldSelection extends AbstractSelection<Field<any>, undefined, Fie
|
|
|
3007
3102
|
return ContainsResult.EQUAL;
|
|
3008
3103
|
}
|
|
3009
3104
|
assert(that.selectionSet, '`this` and `that` have the same element, so if one has sub-selection, the other one should too')
|
|
3010
|
-
return this.selectionSet.contains(that.selectionSet);
|
|
3105
|
+
return this.selectionSet.contains(that.selectionSet, options);
|
|
3011
3106
|
}
|
|
3012
3107
|
|
|
3013
3108
|
toString(expandFragments: boolean = true, indent?: string): string {
|
|
@@ -3051,7 +3146,7 @@ export abstract class FragmentSelection extends AbstractSelection<FragmentElemen
|
|
|
3051
3146
|
|
|
3052
3147
|
abstract equals(that: Selection): boolean;
|
|
3053
3148
|
|
|
3054
|
-
abstract contains(that: Selection): ContainsResult;
|
|
3149
|
+
abstract contains(that: Selection, options?: { ignoreMissingTypename?: boolean }): ContainsResult;
|
|
3055
3150
|
|
|
3056
3151
|
normalize({ parentType, recursive }: { parentType: CompositeType, recursive? : boolean }): FragmentSelection | SelectionSet | undefined {
|
|
3057
3152
|
const thisCondition = this.element.typeCondition;
|
|
@@ -3108,18 +3203,31 @@ class InlineFragmentSelection extends FragmentSelection {
|
|
|
3108
3203
|
this.selectionSet.validate(variableDefinitions);
|
|
3109
3204
|
}
|
|
3110
3205
|
|
|
3111
|
-
rebaseOn(
|
|
3206
|
+
rebaseOn({
|
|
3207
|
+
parentType,
|
|
3208
|
+
fragments,
|
|
3209
|
+
errorIfCannotRebase,
|
|
3210
|
+
}: {
|
|
3211
|
+
parentType: CompositeType,
|
|
3212
|
+
fragments: NamedFragments | undefined,
|
|
3213
|
+
errorIfCannotRebase: boolean,
|
|
3214
|
+
}): FragmentSelection | undefined {
|
|
3112
3215
|
if (this.parentType === parentType) {
|
|
3113
3216
|
return this;
|
|
3114
3217
|
}
|
|
3115
3218
|
|
|
3116
|
-
const rebasedFragment = this.element.rebaseOn(parentType);
|
|
3219
|
+
const rebasedFragment = this.element.rebaseOn({ parentType, errorIfCannotRebase });
|
|
3220
|
+
if (!rebasedFragment) {
|
|
3221
|
+
return undefined;
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3117
3224
|
const rebasedCastedType = rebasedFragment.castedType();
|
|
3118
3225
|
if (rebasedCastedType === this.selectionSet.parentType) {
|
|
3119
3226
|
return this.withUpdatedElement(rebasedFragment);
|
|
3120
3227
|
}
|
|
3121
3228
|
|
|
3122
|
-
|
|
3229
|
+
const rebasedSelectionSet = this.selectionSet.rebaseOn({ parentType: rebasedCastedType, fragments, errorIfCannotRebase });
|
|
3230
|
+
return rebasedSelectionSet.isEmpty() ? undefined : this.withUpdatedComponents(rebasedFragment, rebasedSelectionSet);
|
|
3123
3231
|
}
|
|
3124
3232
|
|
|
3125
3233
|
canAddTo(parentType: CompositeType): boolean {
|
|
@@ -3268,7 +3376,8 @@ class InlineFragmentSelection extends FragmentSelection {
|
|
|
3268
3376
|
return undefined;
|
|
3269
3377
|
} else {
|
|
3270
3378
|
return this.withUpdatedComponents(
|
|
3271
|
-
|
|
3379
|
+
// We should be able to rebase, or there is a bug, so error if that is the case.
|
|
3380
|
+
this.element.rebaseOnOrError(parentType),
|
|
3272
3381
|
selectionSetOfElement(
|
|
3273
3382
|
new Field(
|
|
3274
3383
|
(this.element.typeCondition ?? parentType).typenameField()!,
|
|
@@ -3320,7 +3429,7 @@ class InlineFragmentSelection extends FragmentSelection {
|
|
|
3320
3429
|
|
|
3321
3430
|
return this.parentType === parentType && this.selectionSet === normalizedSelectionSet
|
|
3322
3431
|
? this
|
|
3323
|
-
: this.withUpdatedComponents(this.element.
|
|
3432
|
+
: this.withUpdatedComponents(this.element.rebaseOnOrError(parentType), normalizedSelectionSet);
|
|
3324
3433
|
}
|
|
3325
3434
|
|
|
3326
3435
|
expandFragments(updatedFragments: NamedFragments | undefined): FragmentSelection {
|
|
@@ -3337,12 +3446,12 @@ class InlineFragmentSelection extends FragmentSelection {
|
|
|
3337
3446
|
&& this.selectionSet.equals(that.selectionSet);
|
|
3338
3447
|
}
|
|
3339
3448
|
|
|
3340
|
-
contains(that: Selection): ContainsResult {
|
|
3449
|
+
contains(that: Selection, options?: { ignoreMissingTypename?: boolean }): ContainsResult {
|
|
3341
3450
|
if (!(that instanceof FragmentSelection) || !this.element.equals(that.element)) {
|
|
3342
3451
|
return ContainsResult.NOT_CONTAINED;
|
|
3343
3452
|
}
|
|
3344
3453
|
|
|
3345
|
-
return this.selectionSet.contains(that.selectionSet);
|
|
3454
|
+
return this.selectionSet.contains(that.selectionSet, options);
|
|
3346
3455
|
}
|
|
3347
3456
|
|
|
3348
3457
|
toString(expandFragments: boolean = true, indent?: string): string {
|
|
@@ -3385,7 +3494,7 @@ class FragmentSpreadSelection extends FragmentSelection {
|
|
|
3385
3494
|
// We must update the spread parent type if necessary since we're not going deeper,
|
|
3386
3495
|
// or we'll be fundamentally losing context.
|
|
3387
3496
|
assert(parentType.schema() === this.parentType.schema(), 'Should not try to normalize using a type from another schema');
|
|
3388
|
-
return this.
|
|
3497
|
+
return this.rebaseOnOrError({ parentType, fragments: this.fragments });
|
|
3389
3498
|
}
|
|
3390
3499
|
|
|
3391
3500
|
validate(): void {
|
|
@@ -3421,7 +3530,15 @@ class FragmentSpreadSelection extends FragmentSelection {
|
|
|
3421
3530
|
return this;
|
|
3422
3531
|
}
|
|
3423
3532
|
|
|
3424
|
-
rebaseOn(
|
|
3533
|
+
rebaseOn({
|
|
3534
|
+
parentType,
|
|
3535
|
+
fragments,
|
|
3536
|
+
errorIfCannotRebase,
|
|
3537
|
+
}: {
|
|
3538
|
+
parentType: CompositeType,
|
|
3539
|
+
fragments: NamedFragments | undefined,
|
|
3540
|
+
errorIfCannotRebase: boolean,
|
|
3541
|
+
}): FragmentSelection | undefined {
|
|
3425
3542
|
// We preserve the parent type here, to make sure we don't lose context, but we actually don't
|
|
3426
3543
|
// want to expand the spread as that would compromise the code that optimize subgraph fetches to re-use named
|
|
3427
3544
|
// fragments.
|
|
@@ -3441,7 +3558,14 @@ class FragmentSpreadSelection extends FragmentSelection {
|
|
|
3441
3558
|
assert(fragments || this.parentType.schema() === parentType.schema(), `Must provide fragments is rebasing on other schema`);
|
|
3442
3559
|
const newFragments = fragments ?? this.fragments;
|
|
3443
3560
|
const namedFragment = newFragments.get(this.namedFragment.name);
|
|
3444
|
-
|
|
3561
|
+
// If we're rebasing on another schema (think a subgraph), then named fragments will have been rebased on that, and some
|
|
3562
|
+
// of them may not contain anything that is on that subgraph, in which case they will not have been included at all.
|
|
3563
|
+
// If so, then as long as we're not ask to error if we cannot rebase, then we're happy to skip that spread (since again,
|
|
3564
|
+
// it expands to nothing that apply on the schema).
|
|
3565
|
+
if (!namedFragment) {
|
|
3566
|
+
validate(!errorIfCannotRebase, () => `Cannot rebase ${this.toString(false)} if it isn't part of the provided fragments`);
|
|
3567
|
+
return undefined;
|
|
3568
|
+
}
|
|
3445
3569
|
return new FragmentSpreadSelection(
|
|
3446
3570
|
parentType,
|
|
3447
3571
|
newFragments,
|
|
@@ -3497,7 +3621,7 @@ class FragmentSpreadSelection extends FragmentSelection {
|
|
|
3497
3621
|
&& sameDirectiveApplications(this.spreadDirectives, that.spreadDirectives);
|
|
3498
3622
|
}
|
|
3499
3623
|
|
|
3500
|
-
contains(that: Selection): ContainsResult {
|
|
3624
|
+
contains(that: Selection, options?: { ignoreMissingTypename?: boolean }): ContainsResult {
|
|
3501
3625
|
if (this.equals(that)) {
|
|
3502
3626
|
return ContainsResult.EQUAL;
|
|
3503
3627
|
}
|
|
@@ -3506,7 +3630,7 @@ class FragmentSpreadSelection extends FragmentSelection {
|
|
|
3506
3630
|
return ContainsResult.NOT_CONTAINED;
|
|
3507
3631
|
}
|
|
3508
3632
|
|
|
3509
|
-
return
|
|
3633
|
+
return this.selectionSet.contains(that.selectionSet, options);
|
|
3510
3634
|
}
|
|
3511
3635
|
|
|
3512
3636
|
toString(expandFragments: boolean = true, indent?: string): string {
|