Skip to content

Commit 24708f0

Browse files
committed
refactor: subscribe: introduce buildPerEventExecutionContext (#3639)
= introduces `buildPerEventExecutionContext` that creates an `ExecutionContext` for each subscribe event from the original `ExecutionContext` used to create the event stream = `subscribe` now directly builds the `ExecutionContext` instead of relying on `createSourceEventStream` = introduces `createSourceEventStreamImpl` and `executeImpl` functions that operate on the built `ExecutionContext` rather the `ExecutionArgs` = `subscribe` calls the `createSourceEventStreamImpl` function on the original context and eventuallys calls `executeImpl` on the per event context created by `buildEventExecutionContext`. Motivation: 1. remove unnecessary `buildExecutionContext` call, reducing duplicate work 2. paves the way for easily enhancing the `buildPerEventExecutionContext` to add a new `perEventContextFactory` argument to augment the context argument passed to resolvers as need per event.
1 parent add21f6 commit 24708f0

2 files changed

Lines changed: 50 additions & 15 deletions

File tree

src/execution/__tests__/subscribe-test.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { GraphQLList, GraphQLObjectType } from '../../type/definition';
1414
import { GraphQLBoolean, GraphQLInt, GraphQLString } from '../../type/scalars';
1515
import { GraphQLSchema } from '../../type/schema';
1616

17-
import type { ExecutionResult } from '../execute';
17+
import type { ExecutionArgs, ExecutionResult } from '../execute';
1818
import { createSourceEventStream, subscribe } from '../execute';
1919

2020
import { SimplePubSub } from './simplePubSub';
@@ -189,9 +189,15 @@ function subscribeWithBadFn(
189189
});
190190
const document = parse('subscription { foo }');
191191

192+
return subscribeWithBadArgs({ schema, document });
193+
}
194+
195+
function subscribeWithBadArgs(
196+
args: ExecutionArgs,
197+
): PromiseOrValue<ExecutionResult | AsyncIterable<unknown>> {
192198
return expectEqualPromisesOrValues(
193-
subscribe({ schema, document }),
194-
createSourceEventStream({ schema, document }),
199+
subscribe(args),
200+
createSourceEventStream(args),
195201
);
196202
}
197203

@@ -394,7 +400,7 @@ describe('Subscription Initialization Phase', () => {
394400
const schema = new GraphQLSchema({ query: DummyQueryType });
395401
const document = parse('subscription { unknownField }');
396402

397-
const result = subscribe({ schema, document });
403+
const result = subscribeWithBadArgs({ schema, document });
398404
expectJSON(result).toDeepEqual({
399405
errors: [
400406
{
@@ -418,7 +424,7 @@ describe('Subscription Initialization Phase', () => {
418424
});
419425
const document = parse('subscription { unknownField }');
420426

421-
const result = subscribe({ schema, document });
427+
const result = subscribeWithBadArgs({ schema, document });
422428
expectJSON(result).toDeepEqual({
423429
errors: [
424430
{
@@ -441,7 +447,7 @@ describe('Subscription Initialization Phase', () => {
441447
});
442448

443449
// @ts-expect-error
444-
expect(() => subscribe({ schema, document: {} })).to.throw();
450+
expect(() => subscribeWithBadArgs({ schema, document: {} })).to.throw();
445451
});
446452

447453
it('throws an error if subscribe does not return an iterator', async () => {
@@ -526,7 +532,7 @@ describe('Subscription Initialization Phase', () => {
526532

527533
// If we receive variables that cannot be coerced correctly, subscribe() will
528534
// resolve to an ExecutionResult that contains an informative error description.
529-
const result = subscribe({ schema, document, variableValues });
535+
const result = subscribeWithBadArgs({ schema, document, variableValues });
530536
expectJSON(result).toDeepEqual({
531537
errors: [
532538
{

src/execution/execute.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
218218
return { errors: exeContext };
219219
}
220220

221+
return executeImpl(exeContext);
222+
}
223+
224+
function executeImpl(
225+
exeContext: ExecutionContext,
226+
): PromiseOrValue<ExecutionResult> {
221227
// Return a Promise that will eventually resolve to the data described by
222228
// The "Response" section of the GraphQL specification.
223229
//
@@ -364,6 +370,17 @@ export function buildExecutionContext(
364370
};
365371
}
366372

373+
function buildPerEventExecutionContext(
374+
exeContext: ExecutionContext,
375+
payload: unknown,
376+
): ExecutionContext {
377+
return {
378+
...exeContext,
379+
rootValue: payload,
380+
collectedErrors: new CollectedErrors(),
381+
};
382+
}
383+
367384
/**
368385
* Implements the "Executing operations" section of the spec.
369386
*/
@@ -1080,20 +1097,29 @@ export function subscribe(
10801097
): PromiseOrValue<
10811098
AsyncGenerator<ExecutionResult, void, void> | ExecutionResult
10821099
> {
1083-
const resultOrStream = createSourceEventStream(args);
1100+
// If a valid execution context cannot be created due to incorrect arguments,
1101+
// a "Response" with only errors is returned.
1102+
const exeContext = buildExecutionContext(args);
1103+
1104+
// Return early errors if execution context failed.
1105+
if (!('schema' in exeContext)) {
1106+
return { errors: exeContext };
1107+
}
1108+
1109+
const resultOrStream = createSourceEventStreamImpl(exeContext);
10841110

10851111
if (isPromise(resultOrStream)) {
10861112
return resultOrStream.then((resolvedResultOrStream) =>
1087-
mapSourceToResponse(resolvedResultOrStream, args),
1113+
mapSourceToResponse(exeContext, resolvedResultOrStream),
10881114
);
10891115
}
10901116

1091-
return mapSourceToResponse(resultOrStream, args);
1117+
return mapSourceToResponse(exeContext, resultOrStream);
10921118
}
10931119

10941120
function mapSourceToResponse(
1121+
exeContext: ExecutionContext,
10951122
resultOrStream: ExecutionResult | AsyncIterable<unknown>,
1096-
args: ExecutionArgs,
10971123
): PromiseOrValue<
10981124
AsyncGenerator<ExecutionResult, void, void> | ExecutionResult
10991125
> {
@@ -1108,10 +1134,7 @@ function mapSourceToResponse(
11081134
// "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
11091135
// "ExecuteQuery" algorithm, for which `execute` is also used.
11101136
return mapAsyncIterator(resultOrStream, (payload: unknown) =>
1111-
execute({
1112-
...args,
1113-
rootValue: payload,
1114-
}),
1137+
executeImpl(buildPerEventExecutionContext(exeContext, payload)),
11151138
);
11161139
}
11171140

@@ -1155,6 +1178,12 @@ export function createSourceEventStream(
11551178
return { errors: exeContext };
11561179
}
11571180

1181+
return createSourceEventStreamImpl(exeContext);
1182+
}
1183+
1184+
function createSourceEventStreamImpl(
1185+
exeContext: ExecutionContext,
1186+
): PromiseOrValue<AsyncIterable<unknown> | ExecutionResult> {
11581187
try {
11591188
const eventStream = executeSubscription(exeContext);
11601189
if (isPromise(eventStream)) {

0 commit comments

Comments
 (0)