Skip to content

Commit 59a73d6

Browse files
authored
createSourceEventStream: introduce named arguments and deprecate positional arguments (#3645)
BACKPORT OF #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 59a73d6

File tree

2 files changed

+105
-37
lines changed

2 files changed

+105
-37
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

+45-36
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,7 @@ export async function subscribe(
5858
'graphql@16 dropped long-deprecated support for positional arguments, please pass an object instead.',
5959
);
6060

61-
const {
62-
schema,
63-
document,
64-
rootValue,
65-
contextValue,
66-
variableValues,
67-
operationName,
68-
fieldResolver,
69-
subscribeFieldResolver,
70-
} = args;
71-
72-
const resultOrStream = await createSourceEventStream(
73-
schema,
74-
document,
75-
rootValue,
76-
contextValue,
77-
variableValues,
78-
operationName,
79-
subscribeFieldResolver,
80-
);
61+
const resultOrStream = await createSourceEventStream(args);
8162

8263
if (!isAsyncIterable(resultOrStream)) {
8364
return resultOrStream;
@@ -91,19 +72,44 @@ export async function subscribe(
9172
// "ExecuteQuery" algorithm, for which `execute` is also used.
9273
const mapSourceToResponse = (payload: unknown) =>
9374
execute({
94-
schema,
95-
document,
75+
...args,
9676
rootValue: payload,
97-
contextValue,
98-
variableValues,
99-
operationName,
100-
fieldResolver,
10177
});
10278

10379
// Map every source value to a ExecutionResult value as described above.
10480
return mapAsyncIterator(resultOrStream, mapSourceToResponse);
10581
}
10682

83+
type BackwardsCompatibleArgs =
84+
| [options: ExecutionArgs]
85+
| [
86+
schema: ExecutionArgs['schema'],
87+
document: ExecutionArgs['document'],
88+
rootValue?: ExecutionArgs['rootValue'],
89+
contextValue?: ExecutionArgs['contextValue'],
90+
variableValues?: ExecutionArgs['variableValues'],
91+
operationName?: ExecutionArgs['operationName'],
92+
subscribeFieldResolver?: ExecutionArgs['subscribeFieldResolver'],
93+
];
94+
95+
function toNormalizedArgs(args: BackwardsCompatibleArgs): ExecutionArgs {
96+
const firstArg = args[0];
97+
if (firstArg && 'document' in firstArg) {
98+
return firstArg;
99+
}
100+
101+
return {
102+
schema: firstArg,
103+
// FIXME: when underlying TS bug fixed, see https://github.com/microsoft/TypeScript/issues/31613
104+
document: args[1] as DocumentNode,
105+
rootValue: args[2],
106+
contextValue: args[3],
107+
variableValues: args[4],
108+
operationName: args[5],
109+
subscribeFieldResolver: args[6],
110+
};
111+
}
112+
107113
/**
108114
* Implements the "CreateSourceEventStream" algorithm described in the
109115
* GraphQL specification, resolving the subscription source event stream.
@@ -132,6 +138,10 @@ export async function subscribe(
132138
* or otherwise separating these two steps. For more on this, see the
133139
* "Supporting Subscriptions at Scale" information in the GraphQL specification.
134140
*/
141+
export async function createSourceEventStream(
142+
args: ExecutionArgs,
143+
): Promise<AsyncIterable<unknown> | ExecutionResult>;
144+
/** @deprecated will be removed in next major version in favor of named arguments */
135145
export async function createSourceEventStream(
136146
schema: GraphQLSchema,
137147
document: DocumentNode,
@@ -140,22 +150,21 @@ export async function createSourceEventStream(
140150
variableValues?: Maybe<{ readonly [variable: string]: unknown }>,
141151
operationName?: Maybe<string>,
142152
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>,
143-
): Promise<AsyncIterable<unknown> | ExecutionResult> {
153+
): Promise<AsyncIterable<unknown> | ExecutionResult>;
154+
export async function createSourceEventStream(
155+
...rawArgs: BackwardsCompatibleArgs
156+
) {
157+
const args = toNormalizedArgs(rawArgs);
158+
159+
const { schema, document, variableValues } = args;
160+
144161
// If arguments are missing or incorrectly typed, this is an internal
145162
// developer mistake which should throw an early error.
146163
assertValidExecutionArguments(schema, document, variableValues);
147164

148165
// If a valid execution context cannot be created due to incorrect arguments,
149166
// 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-
});
167+
const exeContext = buildExecutionContext(args);
159168

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

0 commit comments

Comments
 (0)