diff --git a/src/execution/__tests__/subscribe-test.ts b/src/execution/__tests__/subscribe-test.ts index 03e3da2839..b1ca701185 100644 --- a/src/execution/__tests__/subscribe-test.ts +++ b/src/execution/__tests__/subscribe-test.ts @@ -191,7 +191,7 @@ function subscribeWithBadFn( return expectEqualPromisesOrValues( subscribe({ schema, document }), - createSourceEventStream(schema, document), + createSourceEventStream({ schema, document }), ); } @@ -421,6 +421,49 @@ describe('Subscription Initialization Phase', () => { expect(() => subscribe({ schema })).to.throw('Must provide document.'); }); + it('Deprecated: allows positional arguments to createSourceEventStream', () => { + 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 = createSourceEventStream(schema, document); + assert(isAsyncIterable(eventStream)); + }); + + it('Deprecated: throws an error if document is missing when using positional arguments', async () => { + const schema = new GraphQLSchema({ + query: DummyQueryType, + subscription: new GraphQLObjectType({ + name: 'Subscription', + fields: { + foo: { type: GraphQLString }, + }, + }), + }); + + // @ts-expect-error + expect(() => createSourceEventStream(schema, null)).to.throw( + 'Must provide document.', + ); + + // @ts-expect-error + expect(() => createSourceEventStream(schema)).to.throw( + '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 }'); diff --git a/src/execution/subscribe.ts b/src/execution/subscribe.ts index 9ff7cd2112..74608d7d25 100644 --- a/src/execution/subscribe.ts +++ b/src/execution/subscribe.ts @@ -54,60 +54,20 @@ export function subscribe( ): PromiseOrValue< AsyncGenerator | ExecutionResult > { - const { - schema, - document, - rootValue, - contextValue, - variableValues, - operationName, - fieldResolver, - subscribeFieldResolver, - } = args; - - const resultOrStream = createSourceEventStream( - schema, - document, - rootValue, - contextValue, - variableValues, - operationName, - subscribeFieldResolver, - ); + const resultOrStream = createSourceEventStream(args); if (isPromise(resultOrStream)) { return resultOrStream.then((resolvedResultOrStream) => - mapSourceToResponse( - schema, - document, - resolvedResultOrStream, - contextValue, - variableValues, - operationName, - fieldResolver, - ), + mapSourceToResponse(resolvedResultOrStream, args), ); } - return mapSourceToResponse( - schema, - document, - resultOrStream, - contextValue, - variableValues, - operationName, - fieldResolver, - ); + return mapSourceToResponse(resultOrStream, args); } function mapSourceToResponse( - schema: GraphQLSchema, - document: DocumentNode, resultOrStream: ExecutionResult | AsyncIterable, - contextValue?: unknown, - variableValues?: Maybe<{ readonly [variable: string]: unknown }>, - operationName?: Maybe, - fieldResolver?: Maybe>, + args: ExecutionArgs, ): PromiseOrValue< AsyncGenerator | ExecutionResult > { @@ -123,17 +83,42 @@ function mapSourceToResponse( // "ExecuteQuery" algorithm, for which `execute` is also used. return mapAsyncIterator(resultOrStream, (payload: unknown) => execute({ - schema, - document, + ...args, rootValue: payload, - contextValue, - variableValues, - operationName, - fieldResolver, }), ); } +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 ('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. @@ -162,6 +147,10 @@ function mapSourceToResponse( * or otherwise separating these two steps. For more on this, see the * "Supporting Subscriptions at Scale" information in the GraphQL specification. */ +export function createSourceEventStream( + args: ExecutionArgs, +): PromiseOrValue | ExecutionResult>; +/** @deprecated will be removed in next major version in favor of named arguments */ export function createSourceEventStream( schema: GraphQLSchema, document: DocumentNode, @@ -170,7 +159,18 @@ export function createSourceEventStream( variableValues?: Maybe<{ readonly [variable: string]: unknown }>, operationName?: Maybe, subscribeFieldResolver?: Maybe>, -): PromiseOrValue | ExecutionResult> { +): PromiseOrValue | ExecutionResult>; +export function createSourceEventStream(...rawArgs: BackwardsCompatibleArgs) { + const { + schema, + document, + rootValue, + contextValue, + variableValues, + operationName, + subscribeFieldResolver, + } = toNormalizedArgs(rawArgs); + // If arguments are missing or incorrectly typed, this is an internal // developer mistake which should throw an early error. assertValidExecutionArguments(schema, document, variableValues);