Skip to content

Commit 54b215f

Browse files
committed
add legacy incremental delivery entrypoints
1 parent d6e6ac1 commit 54b215f

File tree

2 files changed

+304
-0
lines changed

2 files changed

+304
-0
lines changed

src/execution/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ export {
1313
subscribe,
1414
} from './execute.js';
1515

16+
export {
17+
legacyExecuteIncrementally,
18+
legacyExecuteQueryOrMutationOrSubscriptionEvent,
19+
} from './legacyExecuteIncrementally.js';
20+
1621
export type { ExecutionArgs } from './execute.js';
1722

1823
export type { ValidatedExecutionArgs } from './Executor.js';
Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
import { AccumulatorMap } from '../jsutils/AccumulatorMap.js';
2+
import { getBySet } from '../jsutils/getBySet.js';
3+
import { invariant } from '../jsutils/invariant.js';
4+
import { isSameSet } from '../jsutils/isSameSet.js';
5+
import type { ObjMap } from '../jsutils/ObjMap.js';
6+
import { addPath, pathToArray } from '../jsutils/Path.js';
7+
import type { PromiseOrValue } from '../jsutils/PromiseOrValue.js';
8+
9+
import type { GraphQLError } from '../error/GraphQLError.js';
10+
11+
import type { DeferUsageSet, ExecutionPlan } from './buildExecutionPlan.js';
12+
import type {
13+
DeferUsage,
14+
FieldDetails,
15+
GroupedFieldSet,
16+
} from './collectFields.js';
17+
import type { ExecutionArgs } from './execute.js';
18+
import { validateExecutionArgs } from './execute.js';
19+
import type { ValidatedExecutionArgs } from './Executor.js';
20+
import { Executor } from './Executor.js';
21+
import type { ExperimentalIncrementalExecutionResults } from './IncrementalPublisher.js';
22+
import type {
23+
PayloadPublisher,
24+
SubsequentPayloadPublisher,
25+
} from './PayloadPublisher.js';
26+
import type {
27+
DeferredFragmentRecord,
28+
DeliveryGroup,
29+
ExecutionResult,
30+
FailedExecutionGroup,
31+
StreamItemsRecordResult,
32+
StreamRecord,
33+
SuccessfulExecutionGroup,
34+
} from './types.js';
35+
import { isDeferredFragmentRecord } from './types.js';
36+
37+
interface InitialIncrementalExecutionResult<
38+
TData = ObjMap<unknown>,
39+
TExtensions = ObjMap<unknown>,
40+
> extends ExecutionResult<TData, TExtensions> {
41+
data: TData;
42+
hasNext: true;
43+
extensions?: TExtensions;
44+
}
45+
46+
interface SubsequentIncrementalExecutionResult<
47+
TData = unknown,
48+
TExtensions = ObjMap<unknown>,
49+
> {
50+
incremental?: ReadonlyArray<IncrementalResult<TData, TExtensions>>;
51+
hasNext: boolean;
52+
extensions?: TExtensions;
53+
}
54+
55+
type IncrementalResult<TData = unknown, TExtensions = ObjMap<unknown>> =
56+
| IncrementalDeferResult<TData, TExtensions>
57+
| IncrementalStreamResult<TData, TExtensions>;
58+
59+
interface IncrementalDeferResult<
60+
TData = ObjMap<unknown>,
61+
TExtensions = ObjMap<unknown>,
62+
> extends ExecutionResult<TData, TExtensions> {
63+
path: ReadonlyArray<string | number>;
64+
label?: string;
65+
}
66+
67+
interface IncrementalStreamResult<
68+
TData = ReadonlyArray<unknown>,
69+
TExtensions = ObjMap<unknown>,
70+
> {
71+
errors?: ReadonlyArray<GraphQLError>;
72+
items: TData | null;
73+
path: ReadonlyArray<string | number>;
74+
label?: string;
75+
extensions?: TExtensions;
76+
}
77+
78+
export function legacyExecuteIncrementally(
79+
args: ExecutionArgs,
80+
): PromiseOrValue<
81+
| ExecutionResult
82+
| ExperimentalIncrementalExecutionResults<
83+
InitialIncrementalExecutionResult,
84+
SubsequentIncrementalExecutionResult
85+
>
86+
> {
87+
// If a valid execution context cannot be created due to incorrect arguments,
88+
// a "Response" with only errors is returned.
89+
const validatedExecutionArgs = validateExecutionArgs(args);
90+
91+
// Return early errors if execution context failed.
92+
if (!('schema' in validatedExecutionArgs)) {
93+
return { errors: validatedExecutionArgs };
94+
}
95+
96+
return legacyExecuteQueryOrMutationOrSubscriptionEvent(
97+
validatedExecutionArgs,
98+
);
99+
}
100+
101+
export function legacyExecuteQueryOrMutationOrSubscriptionEvent(
102+
validatedExecutionArgs: ValidatedExecutionArgs,
103+
): PromiseOrValue<
104+
| ExecutionResult
105+
| ExperimentalIncrementalExecutionResults<
106+
InitialIncrementalExecutionResult,
107+
SubsequentIncrementalExecutionResult
108+
>
109+
> {
110+
const executor = new Executor(
111+
validatedExecutionArgs,
112+
buildBranchingExecutionPlan,
113+
getBranchingPayloadPublisher,
114+
);
115+
return executor.executeQueryOrMutationOrSubscriptionEvent();
116+
}
117+
118+
function buildBranchingExecutionPlan(
119+
originalGroupedFieldSet: GroupedFieldSet,
120+
parentDeferUsages: DeferUsageSet = new Set<DeferUsage>(),
121+
): ExecutionPlan {
122+
const groupedFieldSet = new AccumulatorMap<string, FieldDetails>();
123+
124+
const newGroupedFieldSets = new Map<
125+
DeferUsageSet,
126+
AccumulatorMap<string, FieldDetails>
127+
>();
128+
129+
for (const [responseKey, fieldGroup] of originalGroupedFieldSet) {
130+
for (const fieldDetails of fieldGroup) {
131+
const deferUsage = fieldDetails.deferUsage;
132+
const deferUsageSet =
133+
deferUsage === undefined
134+
? new Set<DeferUsage>()
135+
: new Set([deferUsage]);
136+
if (isSameSet(parentDeferUsages, deferUsageSet)) {
137+
groupedFieldSet.add(responseKey, fieldDetails);
138+
} else {
139+
let newGroupedFieldSet = getBySet(newGroupedFieldSets, deferUsageSet);
140+
if (newGroupedFieldSet === undefined) {
141+
newGroupedFieldSet = new AccumulatorMap();
142+
newGroupedFieldSets.set(deferUsageSet, newGroupedFieldSet);
143+
}
144+
newGroupedFieldSet.add(responseKey, fieldDetails);
145+
}
146+
}
147+
}
148+
149+
return {
150+
groupedFieldSet,
151+
newGroupedFieldSets,
152+
};
153+
}
154+
155+
function getBranchingPayloadPublisher(): PayloadPublisher<
156+
InitialIncrementalExecutionResult,
157+
SubsequentIncrementalExecutionResult
158+
> {
159+
const indices = new Map<StreamRecord, number>();
160+
161+
return {
162+
getInitialPayload,
163+
getSubsequentPayloadPublisher,
164+
};
165+
166+
function getInitialPayload(
167+
data: ObjMap<unknown>,
168+
errors: ReadonlyArray<GraphQLError> | undefined,
169+
newRootNodes: ReadonlyArray<DeliveryGroup>,
170+
): InitialIncrementalExecutionResult {
171+
for (const node of newRootNodes) {
172+
if (!isDeferredFragmentRecord(node)) {
173+
indices.set(node, 0);
174+
}
175+
}
176+
177+
return errors === undefined
178+
? { data, hasNext: true }
179+
: { errors, data, hasNext: true };
180+
}
181+
182+
function getSubsequentPayloadPublisher(): SubsequentPayloadPublisher<SubsequentIncrementalExecutionResult> {
183+
const incremental: Array<IncrementalResult> = [];
184+
185+
return {
186+
addFailedDeferredFragmentRecord,
187+
addSuccessfulDeferredFragmentRecord,
188+
addFailedStreamRecord,
189+
addSuccessfulStreamRecord,
190+
addStreamItems,
191+
getSubsequentPayload,
192+
};
193+
194+
function addFailedDeferredFragmentRecord(
195+
deferredFragmentRecord: DeferredFragmentRecord,
196+
failedExecutionGroup: FailedExecutionGroup,
197+
): void {
198+
const { path, label } = deferredFragmentRecord;
199+
const incrementalEntry: IncrementalDeferResult = {
200+
errors: failedExecutionGroup.errors,
201+
data: null,
202+
path: pathToArray(path),
203+
};
204+
incrementalEntry.path = pathToArray(path);
205+
if (label !== undefined) {
206+
incrementalEntry.label = label;
207+
}
208+
incremental.push(incrementalEntry);
209+
}
210+
211+
function addSuccessfulDeferredFragmentRecord(
212+
deferredFragmentRecord: DeferredFragmentRecord,
213+
newRootNodes: ReadonlyArray<DeliveryGroup>,
214+
successfulExecutionGroups: ReadonlyArray<SuccessfulExecutionGroup>,
215+
): void {
216+
for (const node of newRootNodes) {
217+
if (!isDeferredFragmentRecord(node)) {
218+
indices.set(node, 0);
219+
}
220+
}
221+
222+
for (const successfulExecutionGroup of successfulExecutionGroups) {
223+
const { path, label } = deferredFragmentRecord;
224+
const incrementalEntry: IncrementalDeferResult = {
225+
...successfulExecutionGroup.result,
226+
path: pathToArray(path),
227+
};
228+
if (label !== undefined) {
229+
incrementalEntry.label = label;
230+
}
231+
incremental.push(incrementalEntry);
232+
}
233+
}
234+
235+
function addFailedStreamRecord(
236+
streamRecord: StreamRecord,
237+
errors: ReadonlyArray<GraphQLError>,
238+
): void {
239+
const { path, label } = streamRecord;
240+
const index = indices.get(streamRecord);
241+
invariant(index !== undefined);
242+
const incrementalEntry: IncrementalStreamResult = {
243+
errors,
244+
items: null,
245+
path: pathToArray(addPath(path, index, undefined)),
246+
};
247+
if (label !== undefined) {
248+
incrementalEntry.label = label;
249+
}
250+
incremental.push(incrementalEntry);
251+
indices.delete(streamRecord);
252+
}
253+
254+
function addSuccessfulStreamRecord(streamRecord: StreamRecord): void {
255+
indices.delete(streamRecord);
256+
}
257+
258+
function addStreamItems(
259+
streamRecord: StreamRecord,
260+
newRootNodes: ReadonlyArray<DeliveryGroup> | undefined,
261+
result: StreamItemsRecordResult,
262+
): void {
263+
if (newRootNodes !== undefined) {
264+
for (const node of newRootNodes) {
265+
if (!isDeferredFragmentRecord(node)) {
266+
indices.set(node, 0);
267+
}
268+
}
269+
}
270+
271+
const { path, label } = streamRecord;
272+
const index = indices.get(streamRecord);
273+
invariant(index !== undefined);
274+
const incrementalEntry: IncrementalStreamResult = {
275+
...result,
276+
path: pathToArray(addPath(path, index, undefined)),
277+
};
278+
if (label !== undefined) {
279+
incrementalEntry.label = label;
280+
}
281+
incremental.push(incrementalEntry);
282+
}
283+
284+
function getSubsequentPayload(
285+
hasNext: boolean,
286+
): SubsequentIncrementalExecutionResult | undefined {
287+
if (incremental.length > 0) {
288+
const subsequentIncrementalExecutionResult: SubsequentIncrementalExecutionResult =
289+
{ hasNext };
290+
291+
if (incremental.length > 0) {
292+
subsequentIncrementalExecutionResult.incremental = incremental;
293+
}
294+
295+
return subsequentIncrementalExecutionResult;
296+
}
297+
}
298+
}
299+
}

0 commit comments

Comments
 (0)