Skip to content

Commit be87bf4

Browse files
committed
Experiment with simplifying new defer/stream execute types
In this experiment, either execute returns a single value (with no hasNext/incremental), or it returns an async iterable of values where most of the data (including for the first payload!) is on an `incremental` field. This does affect the form of subscriptions in a perhaps surprising way. I did not make tests pass.
1 parent 55ac8df commit be87bf4

File tree

5 files changed

+30
-205
lines changed

5 files changed

+30
-205
lines changed

src/execution/__tests__/flattenAsyncIterable-test.ts

Lines changed: 0 additions & 145 deletions
This file was deleted.

src/execution/execute.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,7 @@ export interface ExecutionResult<
139139
> {
140140
errors?: ReadonlyArray<GraphQLError>;
141141
data?: TData | null;
142-
hasNext?: boolean;
143142
extensions?: TExtensions;
144-
incremental?: ReadonlyArray<IncrementalResult>;
145143
}
146144

147145
export interface FormattedExecutionResult<
@@ -155,23 +153,17 @@ export interface FormattedExecutionResult<
155153
incremental?: ReadonlyArray<IncrementalResult>;
156154
}
157155

158-
export interface SubsequentExecutionResult<TExtensions = ObjMap<unknown>> {
156+
export interface AsyncExecutionResult {
159157
hasNext?: boolean;
160-
extensions?: TExtensions;
161158
incremental?: ReadonlyArray<IncrementalResult>;
162159
}
163160

164-
export type AsyncExecutionResult = ExecutionResult | SubsequentExecutionResult;
165-
166161
export interface IncrementalDeferResult<
167162
TData = ObjMap<unknown>,
168163
TExtensions = ObjMap<unknown>,
169-
> {
170-
errors?: ReadonlyArray<GraphQLError>;
171-
data?: TData | null;
164+
> extends ExecutionResult<TData, TExtensions> {
172165
path?: ReadonlyArray<string | number>;
173166
label?: string;
174-
extensions?: TExtensions;
175167
}
176168

177169
export interface IncrementalStreamResult<
@@ -1362,7 +1354,8 @@ export const defaultFieldResolver: GraphQLFieldResolver<unknown, unknown> =
13621354
export function subscribe(
13631355
args: ExecutionArgs,
13641356
): PromiseOrValue<
1365-
AsyncGenerator<ExecutionResult, void, void> | ExecutionResult
1357+
| AsyncGenerator<ExecutionResult | AsyncExecutionResult, void, void>
1358+
| ExecutionResult
13661359
> {
13671360
// If a valid execution context cannot be created due to incorrect arguments,
13681361
// a "Response" with only errors is returned.
@@ -1384,11 +1377,17 @@ export function subscribe(
13841377
return mapSourceToResponse(exeContext, resultOrStream);
13851378
}
13861379

1380+
// eslint-disable-next-line @typescript-eslint/require-await
1381+
async function* singleElementAsyncIterable<T>(element: T) {
1382+
yield element;
1383+
}
1384+
13871385
function mapSourceToResponse(
13881386
exeContext: ExecutionContext,
13891387
resultOrStream: ExecutionResult | AsyncIterable<unknown>,
13901388
): PromiseOrValue<
1391-
AsyncGenerator<ExecutionResult, void, void> | ExecutionResult
1389+
| AsyncGenerator<ExecutionResult | AsyncExecutionResult, void, void>
1390+
| ExecutionResult
13921391
> {
13931392
if (!isAsyncIterable(resultOrStream)) {
13941393
return resultOrStream;
@@ -1400,10 +1399,16 @@ function mapSourceToResponse(
14001399
// the GraphQL specification. The `execute` function provides the
14011400
// "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
14021401
// "ExecuteQuery" algorithm, for which `execute` is also used.
1403-
return flattenAsyncIterable<ExecutionResult, AsyncExecutionResult>(
1404-
mapAsyncIterable(resultOrStream, (payload: unknown) =>
1405-
executeImpl(buildPerEventExecutionContext(exeContext, payload)),
1406-
),
1402+
return flattenAsyncIterable<ExecutionResult | AsyncExecutionResult>(
1403+
mapAsyncIterable(resultOrStream, async (payload: unknown) => {
1404+
const result = await executeImpl(
1405+
buildPerEventExecutionContext(exeContext, payload),
1406+
);
1407+
if (isAsyncIterable(result)) {
1408+
return result;
1409+
}
1410+
return singleElementAsyncIterable(result);
1411+
}),
14071412
);
14081413
}
14091414

@@ -1874,8 +1879,8 @@ function yieldSubsequentPayloads(
18741879
_hasReturnedInitialResult = true;
18751880
return Promise.resolve({
18761881
value: {
1877-
...initialResult,
18781882
hasNext: true,
1883+
incremental: [initialResult],
18791884
},
18801885
done: false,
18811886
});

src/execution/flattenAsyncIterable.ts

Lines changed: 8 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,17 @@
1-
import { isAsyncIterable } from '../jsutils/isAsyncIterable';
2-
31
type AsyncIterableOrGenerator<T> =
42
| AsyncGenerator<T, void, void>
53
| AsyncIterable<T>;
64

75
/**
8-
* Given an AsyncIterable that could potentially yield other async iterators,
9-
* flatten all yielded results into a single AsyncIterable
6+
* Given an AsyncIterable of async iterators, flatten all yielded results into a
7+
* single AsyncIterable
108
*/
11-
export function flattenAsyncIterable<T, AT>(
12-
iterable: AsyncIterableOrGenerator<T | AsyncIterableOrGenerator<AT>>,
13-
): AsyncGenerator<T | AT, void, void> {
14-
const iteratorMethod = iterable[Symbol.asyncIterator];
15-
const iterator: any = iteratorMethod.call(iterable);
16-
let iteratorStack: Array<AsyncIterator<T>> = [iterator];
17-
18-
async function next(): Promise<IteratorResult<T | AT, void>> {
19-
const currentIterator = iteratorStack[0];
20-
if (!currentIterator) {
21-
return { value: undefined, done: true };
22-
}
23-
const result = await currentIterator.next();
24-
if (result.done) {
25-
iteratorStack.shift();
26-
return next();
27-
} else if (isAsyncIterable(result.value)) {
28-
const childIterator = result.value[
29-
Symbol.asyncIterator
30-
]() as AsyncIterator<T>;
31-
iteratorStack.unshift(childIterator);
32-
return next();
9+
export async function* flattenAsyncIterable<T>(
10+
iterable: AsyncIterableOrGenerator<AsyncIterableOrGenerator<T>>,
11+
): AsyncGenerator<T> {
12+
for await (const subIterator of iterable) {
13+
for await (const value of subIterator) {
14+
yield value;
3315
}
34-
return result;
3516
}
36-
return {
37-
next,
38-
return() {
39-
iteratorStack = [];
40-
return iterator.return();
41-
},
42-
throw(error?: unknown): Promise<IteratorResult<T | AT>> {
43-
iteratorStack = [];
44-
return iterator.throw(error);
45-
},
46-
[Symbol.asyncIterator]() {
47-
return this;
48-
},
49-
};
5017
}

src/execution/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export type {
1313
ExecutionArgs,
1414
ExecutionResult,
1515
FormattedExecutionResult,
16-
SubsequentExecutionResult,
1716
IncrementalDeferResult,
1817
IncrementalStreamResult,
1918
IncrementalResult,

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,6 @@ export type {
334334
ExecutionArgs,
335335
ExecutionResult,
336336
FormattedExecutionResult,
337-
SubsequentExecutionResult,
338337
IncrementalDeferResult,
339338
IncrementalStreamResult,
340339
IncrementalResult,

0 commit comments

Comments
 (0)