From 8fd02e7940af5805918ece36e04ac7730bc0411a Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Sun, 10 Oct 2021 22:09:04 +0300 Subject: [PATCH] Deprecate SubscriptionArgs and broaden ExecutionArgs (#3295) --- src/execution/__tests__/subscribe-test.ts | 32 ++++++++++++++++ src/execution/execute.ts | 46 +++++++++-------------- src/execution/subscribe.ts | 36 ++++++++++-------- 3 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/execution/__tests__/subscribe-test.ts b/src/execution/__tests__/subscribe-test.ts index 004429630ef..097535259dc 100644 --- a/src/execution/__tests__/subscribe-test.ts +++ b/src/execution/__tests__/subscribe-test.ts @@ -256,6 +256,38 @@ describe('Subscription Initialization Phase', () => { await subscription.return(); }); + it('uses a custom default subscribeFieldResolver', async () => { + const schema = new GraphQLSchema({ + query: DummyQueryType, + subscription: new GraphQLObjectType({ + name: 'Subscription', + fields: { + foo: { type: GraphQLString }, + }, + }), + }); + + async function* fooGenerator() { + yield { foo: 'FooValue' }; + } + + const subscription = await subscribe({ + schema, + document: parse('subscription { foo }'), + rootValue: { customFoo: fooGenerator }, + subscribeFieldResolver: (root) => root.customFoo(), + }); + invariant(isAsyncIterable(subscription)); + + expect(await subscription.next()).to.deep.equal({ + done: false, + value: { data: { foo: 'FooValue' } }, + }); + + // Close subscription + await subscription.return(); + }); + it('should only resolve the first field of invalid multi-field', async () => { async function* fooGenerator() { yield { foo: 'FooValue' }; diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 26342ed8ef2..4fe4cfbfe76 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -114,6 +114,7 @@ export interface ExecutionContext { variableValues: { [variable: string]: unknown }; fieldResolver: GraphQLFieldResolver; typeResolver: GraphQLTypeResolver; + subscribeFieldResolver: GraphQLFieldResolver; errors: Array; } @@ -151,6 +152,7 @@ export interface ExecutionArgs { operationName?: Maybe; fieldResolver?: Maybe>; typeResolver?: Maybe>; + subscribeFieldResolver?: Maybe>; } /** @@ -164,32 +166,14 @@ export interface ExecutionArgs { * a GraphQLError will be thrown immediately explaining the invalid input. */ export function execute(args: ExecutionArgs): PromiseOrValue { - const { - schema, - document, - rootValue, - contextValue, - variableValues, - operationName, - fieldResolver, - typeResolver, - } = args; + const { schema, document, variableValues } = args; // If arguments are missing or incorrect, throw an 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, - fieldResolver, - typeResolver, - ); + const exeContext = buildExecutionContext(args); // Return early errors if execution context failed. if (!('schema' in exeContext)) { @@ -271,15 +255,20 @@ export function assertValidExecutionArguments( * @internal */ export function buildExecutionContext( - schema: GraphQLSchema, - document: DocumentNode, - rootValue: unknown, - contextValue: unknown, - rawVariableValues: Maybe<{ readonly [variable: string]: unknown }>, - operationName: Maybe, - fieldResolver: Maybe>, - typeResolver?: Maybe>, + args: ExecutionArgs, ): ReadonlyArray | ExecutionContext { + const { + schema, + document, + rootValue, + contextValue, + variableValues: rawVariableValues, + operationName, + fieldResolver, + typeResolver, + subscribeFieldResolver, + } = args; + let operation: OperationDefinitionNode | undefined; const fragments: ObjMap = Object.create(null); for (const definition of document.definitions) { @@ -334,6 +323,7 @@ export function buildExecutionContext( variableValues: coercedVariableValues.coerced, fieldResolver: fieldResolver ?? defaultFieldResolver, typeResolver: typeResolver ?? defaultTypeResolver, + subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver, errors: [], }; } diff --git a/src/execution/subscribe.ts b/src/execution/subscribe.ts index 957724eb7fe..fb1c7cc1db6 100644 --- a/src/execution/subscribe.ts +++ b/src/execution/subscribe.ts @@ -13,7 +13,11 @@ import type { GraphQLFieldResolver } from '../type/definition'; import { getOperationRootType } from '../utilities/getOperationRootType'; -import type { ExecutionResult, ExecutionContext } from './execute'; +import type { + ExecutionArgs, + ExecutionResult, + ExecutionContext, +} from './execute'; import { collectFields } from './collectFields'; import { getArgumentValues } from './values'; import { @@ -25,16 +29,16 @@ import { } from './execute'; import { mapAsyncIterator } from './mapAsyncIterator'; -export interface SubscriptionArgs { - schema: GraphQLSchema; - document: DocumentNode; - rootValue?: unknown; - contextValue?: unknown; - variableValues?: Maybe<{ readonly [variable: string]: unknown }>; - operationName?: Maybe; - fieldResolver?: Maybe>; - subscribeFieldResolver?: Maybe>; -} +/** + * @deprecated use ExecutionArgs instead. + * + * ExecutionArgs has been broadened to include all properties + * within SubscriptionArgs. The SubscriptionArgs type is retained + * for backwards compatibility and will be removed in the next major + * version. + */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SubscriptionArgs extends ExecutionArgs {} /** * Implements the "Subscribe" algorithm described in the GraphQL specification. @@ -141,7 +145,7 @@ export async function createSourceEventStream( contextValue?: unknown, variableValues?: Maybe<{ readonly [variable: string]: unknown }>, operationName?: Maybe, - fieldResolver?: Maybe>, + subscribeFieldResolver?: Maybe>, ): Promise | ExecutionResult> { // If arguments are missing or incorrectly typed, this is an internal // developer mistake which should throw an early error. @@ -149,15 +153,15 @@ export async function createSourceEventStream( // If a valid execution context cannot be created due to incorrect arguments, // a "Response" with only errors is returned. - const exeContext = buildExecutionContext( + const exeContext = buildExecutionContext({ schema, document, rootValue, contextValue, variableValues, operationName, - fieldResolver, - ); + subscribeFieldResolver, + }); // Return early errors if execution context failed. if (!('schema' in exeContext)) { @@ -228,7 +232,7 @@ async function executeSubscription( // Call the `subscribe()` resolver or the default resolver to produce an // AsyncIterable yielding raw payloads. - const resolveFn = fieldDef.subscribe ?? exeContext.fieldResolver; + const resolveFn = fieldDef.subscribe ?? exeContext.subscribeFieldResolver; const eventStream = await resolveFn(rootValue, args, contextValue, info); if (eventStream instanceof Error) {