Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bb3d536

Browse files
committedJun 14, 2022
createSourceEventStream: introduce named arguments and deprecate positional arguments
BACKPORT OF graphql#3634 Deprecates the positional arguments to createSourceEventStream, to be removed in the next major version, in favor of named arguments. Motivation: 1. aligns createSourceEventStream with the other exported entrypoints graphql, execute, and subscribe 2. allows simplification of mapSourceToResponse suggested by @IvanGoncharov
1 parent 1f8ba95 commit bb3d536

File tree

2 files changed

+106
-15
lines changed

2 files changed

+106
-15
lines changed
 

‎src/execution/__tests__/subscribe-test.ts

+60-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect } from 'chai';
1+
import { assert, expect } from 'chai';
22
import { describe, it } from 'mocha';
33

44
import { expectJSON } from '../../__testUtils__/expectJSON';
@@ -377,6 +377,65 @@ describe('Subscription Initialization Phase', () => {
377377
);
378378
});
379379

380+
it('Deprecated: allows positional arguments to createSourceEventStream', async () => {
381+
async function* fooGenerator() {
382+
/* c8 ignore next 2 */
383+
yield { foo: 'FooValue' };
384+
}
385+
386+
const schema = new GraphQLSchema({
387+
query: DummyQueryType,
388+
subscription: new GraphQLObjectType({
389+
name: 'Subscription',
390+
fields: {
391+
foo: { type: GraphQLString, subscribe: fooGenerator },
392+
},
393+
}),
394+
});
395+
const document = parse('subscription { foo }');
396+
397+
const eventStream = await createSourceEventStream(schema, document);
398+
assert(isAsyncIterable(eventStream));
399+
});
400+
401+
it('Deprecated: throws an error if document is missing when using positional arguments', async () => {
402+
const document = parse('subscription { foo }');
403+
const schema = new GraphQLSchema({
404+
query: DummyQueryType,
405+
subscription: new GraphQLObjectType({
406+
name: 'Subscription',
407+
fields: {
408+
foo: { type: GraphQLString },
409+
},
410+
}),
411+
});
412+
413+
// @ts-expect-error (schema must not be null)
414+
(await expectPromise(createSourceEventStream(null, document))).toRejectWith(
415+
'Expected null to be a GraphQL schema.',
416+
);
417+
418+
(
419+
await expectPromise(
420+
createSourceEventStream(
421+
// @ts-expect-error
422+
undefined,
423+
document,
424+
),
425+
)
426+
).toRejectWith('Expected undefined to be a GraphQL schema.');
427+
428+
// @ts-expect-error (document must not be null)
429+
(await expectPromise(createSourceEventStream(schema, null))).toRejectWith(
430+
'Must provide document.',
431+
);
432+
433+
// @ts-expect-error
434+
(await expectPromise(createSourceEventStream(schema))).toRejectWith(
435+
'Must provide document.',
436+
);
437+
});
438+
380439
it('resolves to an error if schema does not support subscriptions', async () => {
381440
const schema = new GraphQLSchema({ query: DummyQueryType });
382441
const document = parse('subscription { unknownField }');

‎src/execution/subscribe.ts

+46-14
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@ export async function subscribe(
6969
subscribeFieldResolver,
7070
} = args;
7171

72-
const resultOrStream = await createSourceEventStream(
72+
const resultOrStream = await createSourceEventStream({
7373
schema,
7474
document,
7575
rootValue,
7676
contextValue,
7777
variableValues,
7878
operationName,
7979
subscribeFieldResolver,
80-
);
80+
});
8181

8282
if (!isAsyncIterable(resultOrStream)) {
8383
return resultOrStream;
@@ -91,8 +91,7 @@ export async function subscribe(
9191
// "ExecuteQuery" algorithm, for which `execute` is also used.
9292
const mapSourceToResponse = (payload: unknown) =>
9393
execute({
94-
schema,
95-
document,
94+
...args,
9695
rootValue: payload,
9796
contextValue,
9897
variableValues,
@@ -104,6 +103,36 @@ export async function subscribe(
104103
return mapAsyncIterator(resultOrStream, mapSourceToResponse);
105104
}
106105

106+
type BackwardsCompatibleArgs =
107+
| [options: ExecutionArgs]
108+
| [
109+
schema: ExecutionArgs['schema'],
110+
document: ExecutionArgs['document'],
111+
rootValue?: ExecutionArgs['rootValue'],
112+
contextValue?: ExecutionArgs['contextValue'],
113+
variableValues?: ExecutionArgs['variableValues'],
114+
operationName?: ExecutionArgs['operationName'],
115+
subscribeFieldResolver?: ExecutionArgs['subscribeFieldResolver'],
116+
];
117+
118+
function toNormalizedArgs(args: BackwardsCompatibleArgs): ExecutionArgs {
119+
const firstArg = args[0];
120+
if (firstArg && 'document' in firstArg) {
121+
return firstArg;
122+
}
123+
124+
return {
125+
schema: firstArg,
126+
// FIXME: when underlying TS bug fixed, see https://github.com/microsoft/TypeScript/issues/31613
127+
document: args[1] as DocumentNode,
128+
rootValue: args[2],
129+
contextValue: args[3],
130+
variableValues: args[4],
131+
operationName: args[5],
132+
subscribeFieldResolver: args[6],
133+
};
134+
}
135+
107136
/**
108137
* Implements the "CreateSourceEventStream" algorithm described in the
109138
* GraphQL specification, resolving the subscription source event stream.
@@ -132,6 +161,10 @@ export async function subscribe(
132161
* or otherwise separating these two steps. For more on this, see the
133162
* "Supporting Subscriptions at Scale" information in the GraphQL specification.
134163
*/
164+
export async function createSourceEventStream(
165+
args: ExecutionArgs,
166+
): Promise<AsyncIterable<unknown> | ExecutionResult>;
167+
/** @deprecated will be removed in next major version in favor of named arguments */
135168
export async function createSourceEventStream(
136169
schema: GraphQLSchema,
137170
document: DocumentNode,
@@ -140,22 +173,21 @@ export async function createSourceEventStream(
140173
variableValues?: Maybe<{ readonly [variable: string]: unknown }>,
141174
operationName?: Maybe<string>,
142175
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>,
143-
): Promise<AsyncIterable<unknown> | ExecutionResult> {
176+
): Promise<AsyncIterable<unknown> | ExecutionResult>;
177+
export async function createSourceEventStream(
178+
...rawArgs: BackwardsCompatibleArgs
179+
) {
180+
const args = toNormalizedArgs(rawArgs);
181+
182+
const { schema, document, variableValues } = args;
183+
144184
// If arguments are missing or incorrectly typed, this is an internal
145185
// developer mistake which should throw an early error.
146186
assertValidExecutionArguments(schema, document, variableValues);
147187

148188
// If a valid execution context cannot be created due to incorrect arguments,
149189
// a "Response" with only errors is returned.
150-
const exeContext = buildExecutionContext({
151-
schema,
152-
document,
153-
rootValue,
154-
contextValue,
155-
variableValues,
156-
operationName,
157-
subscribeFieldResolver,
158-
});
190+
const exeContext = buildExecutionContext(args);
159191

160192
// Return early errors if execution context failed.
161193
if (!('schema' in exeContext)) {

0 commit comments

Comments
 (0)
Please sign in to comment.