Skip to content

Commit 6cfc042

Browse files
committed
alternative to simplify CollectFIelds for @defer @stream
minimizes changes to CollectFields a la #3982 but still creates a single memoized incremental field plan per list item.
1 parent 688ee2f commit 6cfc042

File tree

5 files changed

+273
-301
lines changed

5 files changed

+273
-301
lines changed

src/execution/IncrementalPublisher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
GraphQLFormattedError,
99
} from '../error/GraphQLError.js';
1010

11-
import type { GroupedFieldSet } from './collectFields.js';
11+
import type { GroupedFieldSet } from './buildFieldPlan.js';
1212

1313
interface IncrementalUpdate<TData = unknown, TExtensions = ObjMap<unknown>> {
1414
pending: ReadonlyArray<PendingResult>;

src/execution/buildFieldPlan.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { getBySet } from '../jsutils/getBySet.js';
2+
import { isSameSet } from '../jsutils/isSameSet.js';
3+
4+
import type { FieldDetails } from './collectFields.js';
5+
6+
export interface DeferUsage {
7+
label: string | undefined;
8+
ancestors: ReadonlyArray<Target>;
9+
}
10+
11+
export const NON_DEFERRED_TARGET_SET: TargetSet = new Set<Target>([undefined]);
12+
13+
export type Target = DeferUsage | undefined;
14+
export type TargetSet = ReadonlySet<Target>;
15+
export type DeferUsageSet = ReadonlySet<DeferUsage>;
16+
17+
export interface FieldGroup {
18+
fields: ReadonlyArray<FieldDetails>;
19+
targets: TargetSet;
20+
knownTargets: TargetSet;
21+
}
22+
23+
export type GroupedFieldSet = Map<string, FieldGroup>;
24+
25+
export interface NewGroupedFieldSetDetails {
26+
groupedFieldSet: GroupedFieldSet;
27+
shouldInitiateDefer: boolean;
28+
}
29+
30+
export function buildFieldPlan(
31+
fields: Map<string, ReadonlyArray<FieldDetails>>,
32+
parentTargets = NON_DEFERRED_TARGET_SET,
33+
knownTargets = NON_DEFERRED_TARGET_SET,
34+
): {
35+
groupedFieldSet: GroupedFieldSet;
36+
newGroupedFieldSetDetailsMap: Map<DeferUsageSet, NewGroupedFieldSetDetails>;
37+
newDeferUsages: ReadonlyArray<DeferUsage>;
38+
} {
39+
const newDeferUsages: Set<DeferUsage> = new Set<DeferUsage>();
40+
const newKnownTargets = new Set<Target>(knownTargets);
41+
42+
const groupedFieldSet = new Map<
43+
string,
44+
{ fields: Array<FieldDetails>; targets: TargetSet; knownTargets: TargetSet }
45+
>();
46+
47+
const newGroupedFieldSetDetailsMap = new Map<
48+
DeferUsageSet,
49+
{
50+
groupedFieldSet: Map<
51+
string,
52+
{
53+
fields: Array<FieldDetails>;
54+
targets: TargetSet;
55+
knownTargets: TargetSet;
56+
}
57+
>;
58+
shouldInitiateDefer: boolean;
59+
}
60+
>();
61+
62+
const map = new Map<
63+
string,
64+
{ targetSet: TargetSet; fieldDetailsList: ReadonlyArray<FieldDetails> }
65+
>();
66+
for (const [responseKey, fieldDetailsList] of fields) {
67+
const targetSet = new Set<Target>();
68+
for (const fieldDetails of fieldDetailsList) {
69+
const target = fieldDetails.deferUsage;
70+
targetSet.add(target);
71+
if (!knownTargets.has(target)) {
72+
// all targets that are not known must be defined
73+
newDeferUsages.add(target as DeferUsage);
74+
}
75+
newKnownTargets.add(target);
76+
}
77+
map.set(responseKey, { targetSet, fieldDetailsList });
78+
}
79+
80+
for (const [responseKey, { targetSet, fieldDetailsList }] of map) {
81+
const maskingTargetList: Array<Target> = [];
82+
for (const target of targetSet) {
83+
if (
84+
target === undefined ||
85+
target.ancestors.every((ancestor) => !targetSet.has(ancestor))
86+
) {
87+
maskingTargetList.push(target);
88+
}
89+
}
90+
91+
const maskingTargets: TargetSet = new Set<Target>(maskingTargetList);
92+
if (isSameSet(maskingTargets, parentTargets)) {
93+
let fieldGroup = groupedFieldSet.get(responseKey);
94+
if (fieldGroup === undefined) {
95+
fieldGroup = {
96+
fields: [],
97+
targets: maskingTargets,
98+
knownTargets: newKnownTargets,
99+
};
100+
groupedFieldSet.set(responseKey, fieldGroup);
101+
}
102+
fieldGroup.fields.push(...fieldDetailsList);
103+
continue;
104+
}
105+
106+
let newGroupedFieldSetDetails = getBySet(
107+
newGroupedFieldSetDetailsMap,
108+
maskingTargets,
109+
);
110+
let newGroupedFieldSet;
111+
if (newGroupedFieldSetDetails === undefined) {
112+
newGroupedFieldSet = new Map<
113+
string,
114+
{
115+
fields: Array<FieldDetails>;
116+
targets: TargetSet;
117+
knownTargets: TargetSet;
118+
}
119+
>();
120+
121+
newGroupedFieldSetDetails = {
122+
groupedFieldSet: newGroupedFieldSet,
123+
shouldInitiateDefer: maskingTargetList.some(
124+
(deferUsage) => !parentTargets.has(deferUsage),
125+
),
126+
};
127+
newGroupedFieldSetDetailsMap.set(
128+
// all new grouped field sets must not contain the initial result as a target
129+
maskingTargets as DeferUsageSet,
130+
newGroupedFieldSetDetails,
131+
);
132+
} else {
133+
newGroupedFieldSet = newGroupedFieldSetDetails.groupedFieldSet;
134+
}
135+
let fieldGroup = newGroupedFieldSet.get(responseKey);
136+
if (fieldGroup === undefined) {
137+
fieldGroup = {
138+
fields: [],
139+
targets: maskingTargets,
140+
knownTargets: newKnownTargets,
141+
};
142+
newGroupedFieldSet.set(responseKey, fieldGroup);
143+
}
144+
fieldGroup.fields.push(...fieldDetailsList);
145+
}
146+
147+
return {
148+
groupedFieldSet,
149+
newGroupedFieldSetDetailsMap,
150+
newDeferUsages: Array.from(newDeferUsages),
151+
};
152+
}

0 commit comments

Comments
 (0)