Skip to content

Commit ce95244

Browse files
committed
fix mutations and subscriptions
1 parent 2ef31ba commit ce95244

File tree

2 files changed

+74
-27
lines changed

2 files changed

+74
-27
lines changed

src/execution/__tests__/abort-signal-test.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import { parse } from '../../language/parser.js';
99

1010
import { buildSchema } from '../../utilities/buildASTSchema.js';
1111

12-
import { execute, experimentalExecuteIncrementally } from '../execute.js';
12+
import {
13+
execute,
14+
experimentalExecuteIncrementally,
15+
subscribe,
16+
} from '../execute.js';
1317
import type {
1418
InitialIncrementalExecutionResult,
1519
SubsequentIncrementalExecutionResult,
@@ -59,6 +63,10 @@ const schema = buildSchema(`
5963
foo: String
6064
bar: String
6165
}
66+
67+
type Subscription {
68+
foo: String
69+
}
6270
`);
6371

6472
describe('Execute: Cancellation', () => {
@@ -583,4 +591,39 @@ describe('Execute: Cancellation', () => {
583591
],
584592
});
585593
});
594+
595+
it('should stop the execution when aborted during subscription', async () => {
596+
const abortController = new AbortController();
597+
const document = parse(`
598+
subscription {
599+
foo
600+
}
601+
`);
602+
603+
const resultPromise = subscribe({
604+
document,
605+
schema,
606+
abortSignal: abortController.signal,
607+
rootValue: {
608+
foo: async () =>
609+
new Promise(() => {
610+
/* will never resolve */
611+
}),
612+
},
613+
});
614+
615+
abortController.abort();
616+
617+
const result = await resultPromise;
618+
619+
expectJSON(result).toDeepEqual({
620+
errors: [
621+
{
622+
message: 'This operation was aborted',
623+
path: ['foo'],
624+
locations: [{ line: 3, column: 9 }],
625+
},
626+
],
627+
});
628+
});
586629
});

src/execution/execute.ts

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -673,20 +673,6 @@ function executeFieldsSerially(
673673
groupedFieldSet,
674674
(graphqlWrappedResult, [responseName, fieldDetailsList]) => {
675675
const fieldPath = addPath(path, responseName, parentType.name);
676-
const abortSignal = exeContext.validatedExecutionArgs.abortSignal;
677-
if (abortSignal?.aborted) {
678-
addErrors(graphqlWrappedResult, [
679-
buildFieldError(
680-
abortSignal.reason,
681-
parentType,
682-
fieldDetailsList,
683-
fieldPath,
684-
),
685-
]);
686-
graphqlWrappedResult.rawResult[responseName] = null;
687-
return graphqlWrappedResult;
688-
}
689-
690676
const result = executeField(
691677
exeContext,
692678
parentType,
@@ -837,6 +823,22 @@ function executeField(
837823
}
838824

839825
const returnType = fieldDef.type;
826+
827+
if (abortSignal?.aborted) {
828+
return {
829+
rawResult: null,
830+
incrementalDataRecords: undefined,
831+
errors: [
832+
buildFieldError(
833+
abortSignal?.reason,
834+
returnType,
835+
fieldDetailsList,
836+
path,
837+
),
838+
],
839+
};
840+
}
841+
840842
const resolveFn = fieldDef.resolve ?? validatedExecutionArgs.fieldResolver;
841843

842844
const info = buildResolveInfo(
@@ -1758,23 +1760,13 @@ function completeObjectValue(
17581760
incrementalContext: IncrementalContext | undefined,
17591761
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
17601762
): PromiseOrValue<GraphQLWrappedResult<ObjMap<unknown>>> {
1761-
const validatedExecutionArgs = exeContext.validatedExecutionArgs;
1762-
const abortSignal = validatedExecutionArgs.abortSignal;
1763-
if (abortSignal?.aborted) {
1764-
throw locatedError(
1765-
abortSignal.reason,
1766-
toNodes(fieldDetailsList),
1767-
pathToArray(path),
1768-
);
1769-
}
1770-
17711763
// If there is an isTypeOf predicate function, call it with the
17721764
// current result. If isTypeOf returns false, then raise an error rather
17731765
// than continuing execution.
17741766
if (returnType.isTypeOf) {
17751767
const isTypeOf = returnType.isTypeOf(
17761768
result,
1777-
validatedExecutionArgs.contextValue,
1769+
exeContext.validatedExecutionArgs.contextValue,
17781770
info,
17791771
);
17801772

@@ -2214,7 +2206,19 @@ function executeSubscription(
22142206
const result = resolveFn(rootValue, args, contextValue, info, abortSignal);
22152207

22162208
if (isPromise(result)) {
2217-
return result
2209+
const { promise, resolve, reject } = promiseWithResolvers<unknown>();
2210+
abortSignal?.addEventListener(
2211+
'abort',
2212+
() =>
2213+
reject(
2214+
locatedError(abortSignal.reason, fieldNodes, pathToArray(path)),
2215+
),
2216+
{ once: true },
2217+
);
2218+
// eslint-disable-next-line @typescript-eslint/use-unknown-in-catch-callback-variable
2219+
result.then(resolve, reject);
2220+
2221+
return promise
22182222
.then(assertEventStream)
22192223
.then(undefined, (error: unknown) => {
22202224
throw locatedError(error, fieldNodes, pathToArray(path));

0 commit comments

Comments
 (0)