Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

createSourceEventStream: introduce named arguments and deprecate positional arguments #3645

Merged
merged 1 commit into from
Jun 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion src/execution/__tests__/subscribe-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect } from 'chai';
import { assert, expect } from 'chai';
import { describe, it } from 'mocha';

import { expectJSON } from '../../__testUtils__/expectJSON';
Expand Down Expand Up @@ -377,6 +377,65 @@ describe('Subscription Initialization Phase', () => {
);
});

it('Deprecated: allows positional arguments to createSourceEventStream', async () => {
async function* fooGenerator() {
/* c8 ignore next 2 */
yield { foo: 'FooValue' };
}

const schema = new GraphQLSchema({
query: DummyQueryType,
subscription: new GraphQLObjectType({
name: 'Subscription',
fields: {
foo: { type: GraphQLString, subscribe: fooGenerator },
},
}),
});
const document = parse('subscription { foo }');

const eventStream = await createSourceEventStream(schema, document);
assert(isAsyncIterable(eventStream));
});

it('Deprecated: throws an error if document is missing when using positional arguments', async () => {
const document = parse('subscription { foo }');
const schema = new GraphQLSchema({
query: DummyQueryType,
subscription: new GraphQLObjectType({
name: 'Subscription',
fields: {
foo: { type: GraphQLString },
},
}),
});

// @ts-expect-error (schema must not be null)
(await expectPromise(createSourceEventStream(null, document))).toRejectWith(
'Expected null to be a GraphQL schema.',
);

(
await expectPromise(
createSourceEventStream(
// @ts-expect-error
undefined,
document,
),
)
).toRejectWith('Expected undefined to be a GraphQL schema.');

// @ts-expect-error (document must not be null)
(await expectPromise(createSourceEventStream(schema, null))).toRejectWith(
'Must provide document.',
);

// @ts-expect-error
(await expectPromise(createSourceEventStream(schema))).toRejectWith(
'Must provide document.',
);
});

it('resolves to an error if schema does not support subscriptions', async () => {
const schema = new GraphQLSchema({ query: DummyQueryType });
const document = parse('subscription { unknownField }');
Expand Down
81 changes: 45 additions & 36 deletions src/execution/subscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,7 @@ export async function subscribe(
'graphql@16 dropped long-deprecated support for positional arguments, please pass an object instead.',
);

const {
schema,
document,
rootValue,
contextValue,
variableValues,
operationName,
fieldResolver,
subscribeFieldResolver,
} = args;

const resultOrStream = await createSourceEventStream(
schema,
document,
rootValue,
contextValue,
variableValues,
operationName,
subscribeFieldResolver,
);
const resultOrStream = await createSourceEventStream(args);

if (!isAsyncIterable(resultOrStream)) {
return resultOrStream;
Expand All @@ -91,19 +72,44 @@ export async function subscribe(
// "ExecuteQuery" algorithm, for which `execute` is also used.
const mapSourceToResponse = (payload: unknown) =>
execute({
schema,
document,
...args,
rootValue: payload,
contextValue,
variableValues,
operationName,
fieldResolver,
});

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

type BackwardsCompatibleArgs =
| [options: ExecutionArgs]
| [
schema: ExecutionArgs['schema'],
document: ExecutionArgs['document'],
rootValue?: ExecutionArgs['rootValue'],
contextValue?: ExecutionArgs['contextValue'],
variableValues?: ExecutionArgs['variableValues'],
operationName?: ExecutionArgs['operationName'],
subscribeFieldResolver?: ExecutionArgs['subscribeFieldResolver'],
];

function toNormalizedArgs(args: BackwardsCompatibleArgs): ExecutionArgs {
const firstArg = args[0];
if (firstArg && 'document' in firstArg) {
return firstArg;
}

return {
schema: firstArg,
// FIXME: when underlying TS bug fixed, see https://github.com/microsoft/TypeScript/issues/31613
document: args[1] as DocumentNode,
rootValue: args[2],
contextValue: args[3],
variableValues: args[4],
operationName: args[5],
subscribeFieldResolver: args[6],
};
}

/**
* Implements the "CreateSourceEventStream" algorithm described in the
* GraphQL specification, resolving the subscription source event stream.
Expand Down Expand Up @@ -132,6 +138,10 @@ export async function subscribe(
* or otherwise separating these two steps. For more on this, see the
* "Supporting Subscriptions at Scale" information in the GraphQL specification.
*/
export async function createSourceEventStream(
args: ExecutionArgs,
): Promise<AsyncIterable<unknown> | ExecutionResult>;
/** @deprecated will be removed in next major version in favor of named arguments */
export async function createSourceEventStream(
schema: GraphQLSchema,
document: DocumentNode,
Expand All @@ -140,22 +150,21 @@ export async function createSourceEventStream(
variableValues?: Maybe<{ readonly [variable: string]: unknown }>,
operationName?: Maybe<string>,
subscribeFieldResolver?: Maybe<GraphQLFieldResolver<any, any>>,
): Promise<AsyncIterable<unknown> | ExecutionResult> {
): Promise<AsyncIterable<unknown> | ExecutionResult>;
export async function createSourceEventStream(
...rawArgs: BackwardsCompatibleArgs
) {
const args = toNormalizedArgs(rawArgs);

const { schema, document, variableValues } = args;

// If arguments are missing or incorrectly typed, this is an internal
// developer mistake which should throw an early error.
assertValidExecutionArguments(schema, document, variableValues);

// If a valid execution context cannot be created due to incorrect arguments,
// a "Response" with only errors is returned.
const exeContext = buildExecutionContext({
schema,
document,
rootValue,
contextValue,
variableValues,
operationName,
subscribeFieldResolver,
});
const exeContext = buildExecutionContext(args);

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