Skip to content

Commit 2aedf25

Browse files
authored
simplify CollectFields for @defer and @stream (#3994)
minimizes the changes to `CollectFields` required for incremental delivery (inspired by #3982) -- but retains a single memoized incremental field plan per list item.
1 parent 688ee2f commit 2aedf25

File tree

5 files changed

+286
-309
lines changed

5 files changed

+286
-309
lines changed

src/execution/IncrementalPublisher.ts

+1-1
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

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { getBySet } from '../jsutils/getBySet.js';
2+
import { isSameSet } from '../jsutils/isSameSet.js';
3+
4+
import type { DeferUsage, FieldDetails } from './collectFields.js';
5+
6+
export type DeferUsageSet = ReadonlySet<DeferUsage>;
7+
8+
export interface FieldGroup {
9+
fields: ReadonlyArray<FieldDetails>;
10+
deferUsages?: DeferUsageSet | undefined;
11+
knownDeferUsages?: DeferUsageSet | undefined;
12+
}
13+
14+
export type GroupedFieldSet = Map<string, FieldGroup>;
15+
16+
export interface NewGroupedFieldSetDetails {
17+
groupedFieldSet: GroupedFieldSet;
18+
shouldInitiateDefer: boolean;
19+
}
20+
21+
export function buildFieldPlan(
22+
fields: Map<string, ReadonlyArray<FieldDetails>>,
23+
parentDeferUsages: DeferUsageSet = new Set<DeferUsage>(),
24+
knownDeferUsages: DeferUsageSet = new Set<DeferUsage>(),
25+
): {
26+
groupedFieldSet: GroupedFieldSet;
27+
newGroupedFieldSetDetailsMap: Map<DeferUsageSet, NewGroupedFieldSetDetails>;
28+
newDeferUsages: ReadonlyArray<DeferUsage>;
29+
} {
30+
const newDeferUsages: Set<DeferUsage> = new Set<DeferUsage>();
31+
const newKnownDeferUsages = new Set<DeferUsage>(knownDeferUsages);
32+
33+
const groupedFieldSet = new Map<
34+
string,
35+
{
36+
fields: Array<FieldDetails>;
37+
deferUsages: DeferUsageSet;
38+
knownDeferUsages: DeferUsageSet;
39+
}
40+
>();
41+
42+
const newGroupedFieldSetDetailsMap = new Map<
43+
DeferUsageSet,
44+
{
45+
groupedFieldSet: Map<
46+
string,
47+
{
48+
fields: Array<FieldDetails>;
49+
deferUsages: DeferUsageSet;
50+
knownDeferUsages: DeferUsageSet;
51+
}
52+
>;
53+
shouldInitiateDefer: boolean;
54+
}
55+
>();
56+
57+
const map = new Map<
58+
string,
59+
{
60+
deferUsageSet: DeferUsageSet;
61+
fieldDetailsList: ReadonlyArray<FieldDetails>;
62+
}
63+
>();
64+
65+
for (const [responseKey, fieldDetailsList] of fields) {
66+
const deferUsageSet = new Set<DeferUsage>();
67+
let inOriginalResult = false;
68+
for (const fieldDetails of fieldDetailsList) {
69+
const deferUsage = fieldDetails.deferUsage;
70+
if (deferUsage === undefined) {
71+
inOriginalResult = true;
72+
continue;
73+
}
74+
deferUsageSet.add(deferUsage);
75+
if (!knownDeferUsages.has(deferUsage)) {
76+
newDeferUsages.add(deferUsage);
77+
newKnownDeferUsages.add(deferUsage);
78+
}
79+
}
80+
if (inOriginalResult) {
81+
deferUsageSet.clear();
82+
} else {
83+
deferUsageSet.forEach((deferUsage) => {
84+
const ancestors = getAncestors(deferUsage);
85+
for (const ancestor of ancestors) {
86+
if (deferUsageSet.has(ancestor)) {
87+
deferUsageSet.delete(deferUsage);
88+
}
89+
}
90+
});
91+
}
92+
map.set(responseKey, { deferUsageSet, fieldDetailsList });
93+
}
94+
95+
for (const [responseKey, { deferUsageSet, fieldDetailsList }] of map) {
96+
if (isSameSet(deferUsageSet, parentDeferUsages)) {
97+
let fieldGroup = groupedFieldSet.get(responseKey);
98+
if (fieldGroup === undefined) {
99+
fieldGroup = {
100+
fields: [],
101+
deferUsages: deferUsageSet,
102+
knownDeferUsages: newKnownDeferUsages,
103+
};
104+
groupedFieldSet.set(responseKey, fieldGroup);
105+
}
106+
fieldGroup.fields.push(...fieldDetailsList);
107+
continue;
108+
}
109+
110+
let newGroupedFieldSetDetails = getBySet(
111+
newGroupedFieldSetDetailsMap,
112+
deferUsageSet,
113+
);
114+
let newGroupedFieldSet;
115+
if (newGroupedFieldSetDetails === undefined) {
116+
newGroupedFieldSet = new Map<
117+
string,
118+
{
119+
fields: Array<FieldDetails>;
120+
deferUsages: DeferUsageSet;
121+
knownDeferUsages: DeferUsageSet;
122+
}
123+
>();
124+
125+
newGroupedFieldSetDetails = {
126+
groupedFieldSet: newGroupedFieldSet,
127+
shouldInitiateDefer: Array.from(deferUsageSet).some(
128+
(deferUsage) => !parentDeferUsages.has(deferUsage),
129+
),
130+
};
131+
newGroupedFieldSetDetailsMap.set(
132+
deferUsageSet,
133+
newGroupedFieldSetDetails,
134+
);
135+
} else {
136+
newGroupedFieldSet = newGroupedFieldSetDetails.groupedFieldSet;
137+
}
138+
let fieldGroup = newGroupedFieldSet.get(responseKey);
139+
if (fieldGroup === undefined) {
140+
fieldGroup = {
141+
fields: [],
142+
deferUsages: deferUsageSet,
143+
knownDeferUsages: newKnownDeferUsages,
144+
};
145+
newGroupedFieldSet.set(responseKey, fieldGroup);
146+
}
147+
fieldGroup.fields.push(...fieldDetailsList);
148+
}
149+
150+
return {
151+
groupedFieldSet,
152+
newGroupedFieldSetDetailsMap,
153+
newDeferUsages: Array.from(newDeferUsages),
154+
};
155+
}
156+
157+
function getAncestors(deferUsage: DeferUsage): ReadonlyArray<DeferUsage> {
158+
const ancestors: Array<DeferUsage> = [];
159+
let parentDeferUsage: DeferUsage | undefined = deferUsage.parentDeferUsage;
160+
while (parentDeferUsage !== undefined) {
161+
ancestors.unshift(parentDeferUsage);
162+
parentDeferUsage = parentDeferUsage.parentDeferUsage;
163+
}
164+
return ancestors;
165+
}

0 commit comments

Comments
 (0)