Skip to content

Commit 535e210

Browse files
committed
introduce executeRequest function
...capable of processing requests of all operation types. ExecutionArgs now augmented with all relevant arguments for subscriptions as well.
1 parent f4c4835 commit 535e210

File tree

5 files changed

+112
-87
lines changed

5 files changed

+112
-87
lines changed

src/execution/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
The `graphql/execution` module is responsible for the execution phase of
44
fulfilling a GraphQL request.
55

6+
For queries and mutations:
7+
68
```js
7-
import { execute } from 'graphql/execution'; // ES6
9+
import { executeRequest } from 'graphql/execution'; // ES6
810
var GraphQLExecution = require('graphql/execution'); // CommonJS
911
```
1012

13+
For subscriptions:
14+
1115
```js
12-
import { subscribe, createSourceEventStream } from 'graphql/execution'; // ES6
13-
var GraphQLSubscription = require('graphql/execution'); // CommonJS
16+
import { executeRequest, createSourceEventStream } from 'graphql/execution'; // ES6
17+
var GraphQLExecution = require('graphql/execution'); // CommonJS
1418
```

src/execution/__tests__/subscribe-test.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ import { GraphQLList, GraphQLObjectType } from '../../type/definition';
1414
import { GraphQLInt, GraphQLString, GraphQLBoolean } from '../../type/scalars';
1515

1616
import type { ExecutionContext } from '../execute';
17-
import { buildExecutionContext, createSourceEventStream } from '../execute';
18-
import { subscribe } from '../subscribe';
17+
import {
18+
buildExecutionContext,
19+
createSourceEventStream,
20+
subscribe,
21+
} from '../execute';
1922

2023
import { SimplePubSub } from './simplePubSub';
2124

@@ -316,6 +319,9 @@ describe('Subscription Initialization Phase', () => {
316319
}),
317320
});
318321

322+
// NOTE: in contrast to the below tests, executeRequest() will directly throw the
323+
// error rather than return a promise that rejects.
324+
319325
// @ts-expect-error (schema must not be null)
320326
(await expectPromise(subscribe({ schema: null, document }))).toRejectWith(
321327
'Expected null to be a GraphQL schema.',
@@ -371,6 +377,9 @@ describe('Subscription Initialization Phase', () => {
371377
}),
372378
});
373379

380+
// NOTE: in contrast to the below test, executeRequest() will directly throw the
381+
// error rather than return a promise that rejects.
382+
374383
// @ts-expect-error
375384
(await expectPromise(subscribe({ schema, document: {} }))).toReject();
376385
});
@@ -391,7 +400,8 @@ describe('Subscription Initialization Phase', () => {
391400

392401
const document = parse('subscription { foo }');
393402

394-
(await expectPromise(subscribe({ schema, document }))).toRejectWith(
403+
const result = subscribe({ schema, document }) as Promise<unknown>;
404+
(await expectPromise(result)).toRejectWith(
395405
'Subscription field must return Async Iterable. Received: "test".',
396406
);
397407
});
@@ -474,6 +484,9 @@ describe('Subscription Initialization Phase', () => {
474484

475485
// If we receive variables that cannot be coerced correctly, subscribe() will
476486
// resolve to an ExecutionResult that contains an informative error description.
487+
488+
// NOTE: in contrast, executeRequest() will directly return the error as an
489+
// ExecutionResult rather than a promise.
477490
const result = await subscribe({ schema, document, variableValues });
478491
expectJSON(result).to.deep.equal({
479492
errors: [

src/execution/execute.ts

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -153,33 +153,94 @@ export interface ExecutionArgs {
153153
contextValue?: unknown;
154154
variableValues?: Maybe<{ readonly [variable: string]: unknown }>;
155155
operationName?: Maybe<string>;
156+
disableSubscription?: Maybe<boolean>;
156157
fieldResolver?: Maybe<GraphQLFieldResolver<any, any>>;
157158
typeResolver?: Maybe<GraphQLTypeResolver<any, any>>;
159+
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<unknown, unknown>>;
158160
}
159161

162+
// Exported for backwards compatibility, see below.
163+
// eslint-disable-next-line @typescript-eslint/no-empty-interface
164+
export interface SubscriptionArgs extends ExecutionArgs {}
165+
160166
/**
161167
* Implements the "Executing requests" section of the GraphQL specification.
162168
*
169+
* For queries and mutations:
163170
* Returns either a synchronous ExecutionResult (if all encountered resolvers
164171
* are synchronous), or a Promise of an ExecutionResult that will eventually be
165172
* resolved and never rejected.
166173
*
167-
* If the arguments to this function do not result in a legal execution context,
168-
* a GraphQLError will be thrown immediately explaining the invalid input.
174+
* For subscriptions:
175+
* Returns a Promise which resolves to either an AsyncIterator (if successful)
176+
* or an ExecutionResult (error). The promise will be rejected if the resolved
177+
* event stream is not an async iterable.
178+
*
179+
* If the source stream could not be created due to faulty subscription
180+
* resolver logic or underlying systems, the promise will resolve to a single
181+
* ExecutionResult containing `errors` and no `data`.
182+
*
183+
* If the operation succeeded, the promise resolves to an AsyncIterator, which
184+
* yields a stream of ExecutionResults representing the response stream.
185+
*
186+
* For queries, mutations and subscriptions:
187+
* If a valid execution context cannot be created due to incorrect arguments
188+
* an ExecutionResult containing descriptive errors will be returned.
189+
*
190+
* NOTE: the below always async `subscribe` function will return a Promise
191+
* that resolves to the ExecutionResult containing the errors, rather than the
192+
* ExecutionResult itself.
193+
*
194+
* The `executeRequest` function, in contrast, aligns the return type in the
195+
* case of incorrect arguments between all operation types. `executeRequest`
196+
* always returns an ExecutionResult with the errors, even for subscriptions,
197+
* rather than a promise that resolves to the ExecutionResult with the errors.
198+
*
169199
*/
170-
export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
171-
// If a valid execution context cannot be created due to incorrect arguments,
172-
// a "Response" with only errors is returned.
200+
export function executeRequest(
201+
args: ExecutionArgs,
202+
):
203+
| ExecutionResult
204+
| Promise<ExecutionResult | AsyncGenerator<ExecutionResult, void, void>> {
173205
const exeContext = buildExecutionContext(args);
174206

175207
// Return early errors if execution context failed.
176208
if (!('schema' in exeContext)) {
177209
return { errors: exeContext };
178210
}
179211

212+
if (
213+
!args.disableSubscription &&
214+
exeContext.operation.operation === 'subscription'
215+
) {
216+
return executeSubscription(exeContext);
217+
}
218+
180219
return executeQueryOrMutation(exeContext);
181220
}
182221

222+
/**
223+
* Also implements the "Executing requests" section of the GraphQL specification.
224+
* The `execute` function presumes the request contains a query or mutation
225+
* and has a narrower return type than `executeRequest`.
226+
*
227+
* The below use of the new `disableSubscription` argument preserves the
228+
* previous default behavior of executing documents containing subscription
229+
* operations as queries.
230+
*
231+
* Note: In a future version the `execute` function may be entirely replaced
232+
* with the `executeRequest` function, and the `executeRequest` function may
233+
* be renamed to `execute`. The `disableSubscription` option may be replaced
234+
* by an `operationType` option that changes that overrides the operation
235+
* type stored within the document.
236+
*/
237+
export function execute(args: ExecutionArgs): PromiseOrValue<ExecutionResult> {
238+
return executeRequest({
239+
...args,
240+
disableSubscription: true,
241+
}) as PromiseOrValue<ExecutionResult>;
242+
}
243+
183244
/**
184245
* Also implements the "Executing requests" section of the GraphQL specification.
185246
* However, it guarantees to complete synchronously (or throw an error) assuming
@@ -196,6 +257,22 @@ export function executeSync(args: ExecutionArgs): ExecutionResult {
196257
return result;
197258
}
198259

260+
/**
261+
* Also implements the "Executing requests" section of the GraphQL specification.
262+
* The `subscribe` function presumes the request contains a subscription
263+
* and has a narrower return type than `executeRequest`.
264+
*
265+
* Note: In a future version the `subscribe` function may be entirely replaced
266+
* with the `executeRequest` function as above.
267+
*/
268+
export async function subscribe(
269+
args: SubscriptionArgs,
270+
): Promise<AsyncGenerator<ExecutionResult, void, void> | ExecutionResult> {
271+
return executeRequest(args) as Promise<
272+
AsyncGenerator<ExecutionResult, void, void> | ExecutionResult
273+
>;
274+
}
275+
199276
/**
200277
* Implements the "Executing operations" section of the spec for queries and
201278
* mutations.
@@ -250,23 +327,15 @@ export function assertValidExecutionArguments(
250327

251328
/**
252329
* Constructs a ExecutionContext object from the arguments passed to
253-
* execute, which we will pass throughout the other execution methods.
330+
* executeRequest, which we will pass throughout the other execution methods.
254331
*
255332
* Throws a GraphQLError if a valid execution context cannot be created.
256333
*
257334
* @internal
258335
*/
259-
export function buildExecutionContext(args: {
260-
schema: GraphQLSchema;
261-
document: DocumentNode;
262-
rootValue?: Maybe<unknown>;
263-
contextValue?: Maybe<unknown>;
264-
variableValues?: Maybe<{ readonly [variable: string]: unknown }>;
265-
operationName?: Maybe<string>;
266-
fieldResolver?: Maybe<GraphQLFieldResolver<unknown, unknown>>;
267-
typeResolver?: Maybe<GraphQLTypeResolver<unknown, unknown>>;
268-
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<unknown, unknown>>;
269-
}): ReadonlyArray<GraphQLError> | ExecutionContext {
336+
export function buildExecutionContext(
337+
args: ExecutionArgs,
338+
): ReadonlyArray<GraphQLError> | ExecutionContext {
270339
const {
271340
schema,
272341
document,

src/execution/index.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,18 @@ export { pathToArray as responsePathAsArray } from '../jsutils/Path';
33
export {
44
createSourceEventStream,
55
execute,
6+
executeRequest,
67
executeSync,
78
defaultFieldResolver,
89
defaultTypeResolver,
10+
subscribe,
911
} from './execute';
1012

1113
export type {
1214
ExecutionArgs,
1315
ExecutionResult,
1416
FormattedExecutionResult,
17+
SubscriptionArgs,
1518
} from './execute';
1619

1720
export { getDirectiveValues } from './values';
18-
19-
export { subscribe } from './subscribe';
20-
21-
export type { SubscriptionArgs } from './subscribe';

src/execution/subscribe.ts

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

0 commit comments

Comments
 (0)