Skip to content

Commit 92d49a7

Browse files
committed
WIP
1 parent f531737 commit 92d49a7

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { expectJSON } from '../../__testUtils__/expectJSON.js';
5+
6+
import { parse } from '../../language/parser.js';
7+
8+
import { buildSchema } from '../../utilities/buildASTSchema.js';
9+
10+
import { execute } from '../execute.js';
11+
12+
const schema = buildSchema(/* GraphQL */ `
13+
type Todo {
14+
id: ID!
15+
text: String!
16+
completed: Boolean!
17+
author: User
18+
}
19+
20+
type User {
21+
id: ID!
22+
name: String!
23+
}
24+
25+
type Query {
26+
todo: Todo
27+
}
28+
29+
type Mutation {
30+
foo: String
31+
bar: String
32+
}
33+
`);
34+
35+
describe('Abort Signal', () => {
36+
it('should stop the execution when aborted in resolver', async () => {
37+
const abortController = new AbortController();
38+
const document = parse(/* GraphQL */ `
39+
query {
40+
todo {
41+
id
42+
author {
43+
id
44+
}
45+
}
46+
}
47+
`);
48+
const result = await execute({
49+
document,
50+
schema,
51+
abortSignal: abortController.signal,
52+
rootValue: {
53+
todo() {
54+
abortController.abort('Aborted');
55+
return {
56+
id: '1',
57+
text: 'Hello, World!',
58+
completed: false,
59+
author: () => {
60+
expect.fail('Should not be called');
61+
},
62+
};
63+
},
64+
},
65+
});
66+
67+
expectJSON(result).toDeepEqual({
68+
data: {
69+
todo: null,
70+
},
71+
errors: [
72+
{
73+
locations: [
74+
{
75+
column: 9,
76+
line: 3,
77+
},
78+
],
79+
message: 'Aborted',
80+
path: ['todo'],
81+
},
82+
],
83+
});
84+
});
85+
86+
it('should stop the for serial mutation execution', async () => {
87+
const abortController = new AbortController();
88+
const document = parse(/* GraphQL */ `
89+
mutation {
90+
foo
91+
bar
92+
}
93+
`);
94+
const result = await execute({
95+
document,
96+
schema,
97+
abortSignal: abortController.signal,
98+
rootValue: {
99+
foo() {
100+
abortController.abort('Aborted');
101+
return 'baz';
102+
},
103+
bar() {
104+
expect.fail('Should not be called');
105+
},
106+
},
107+
});
108+
109+
expectJSON(result).toDeepEqual({
110+
data: null,
111+
errors: [
112+
{
113+
message: 'Aborted',
114+
},
115+
],
116+
});
117+
});
118+
119+
it('should stop the execution when aborted pre-execute', async () => {
120+
const abortController = new AbortController();
121+
const document = parse(/* GraphQL */ `
122+
query {
123+
todo {
124+
id
125+
author {
126+
id
127+
}
128+
}
129+
}
130+
`);
131+
abortController.abort('Aborted');
132+
const result = await execute({
133+
document,
134+
schema,
135+
abortSignal: abortController.signal,
136+
rootValue: {
137+
todo() {
138+
abortController.abort('Aborted');
139+
return {
140+
id: '1',
141+
text: 'Hello, World!',
142+
completed: false,
143+
author: () => {
144+
expect.fail('Should not be called');
145+
},
146+
};
147+
},
148+
},
149+
});
150+
151+
expectJSON(result).toDeepEqual({
152+
data: null,
153+
errors: [
154+
{
155+
message: 'Aborted',
156+
},
157+
],
158+
});
159+
});
160+
});

src/execution/execute.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,14 @@ export interface ValidatedExecutionArgs {
157157
) => PromiseOrValue<ExecutionResult>;
158158
enableEarlyExecution: boolean;
159159
hideSuggestions: boolean;
160+
abortSignal: AbortSignal | undefined;
160161
}
161162

162163
export interface ExecutionContext {
163164
validatedExecutionArgs: ValidatedExecutionArgs;
164165
errors: Array<GraphQLError> | undefined;
165166
cancellableStreams: Set<CancellableStreamRecord> | undefined;
167+
abortSignal: AbortSignal | undefined;
166168
}
167169

168170
interface IncrementalContext {
@@ -187,6 +189,7 @@ export interface ExecutionArgs {
187189
>;
188190
enableEarlyExecution?: Maybe<boolean>;
189191
hideSuggestions?: Maybe<boolean>;
192+
abortSignal?: AbortSignal | undefined;
190193
}
191194

192195
export interface StreamUsage {
@@ -309,6 +312,7 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent(
309312
validatedExecutionArgs,
310313
errors: undefined,
311314
cancellableStreams: undefined,
315+
abortSignal: validatedExecutionArgs.abortSignal,
312316
};
313317
try {
314318
const {
@@ -318,7 +322,13 @@ export function experimentalExecuteQueryOrMutationOrSubscriptionEvent(
318322
operation,
319323
variableValues,
320324
hideSuggestions,
325+
abortSignal,
321326
} = validatedExecutionArgs;
327+
328+
if (abortSignal?.aborted) {
329+
throw new GraphQLError(abortSignal.reason);
330+
}
331+
322332
const rootType = schema.getRootType(operation.operation);
323333
if (rootType == null) {
324334
throw new GraphQLError(
@@ -592,6 +602,7 @@ export function validateExecutionArgs(
592602
perEventExecutor: perEventExecutor ?? executeSubscriptionEvent,
593603
enableEarlyExecution: enableEarlyExecution === true,
594604
hideSuggestions,
605+
abortSignal: args.abortSignal,
595606
};
596607
}
597608

@@ -656,6 +667,9 @@ function executeFieldsSerially(
656667
groupedFieldSet,
657668
(graphqlWrappedResult, [responseName, fieldDetailsList]) => {
658669
const fieldPath = addPath(path, responseName, parentType.name);
670+
if (exeContext.abortSignal?.aborted) {
671+
throw new GraphQLError(exeContext.abortSignal.reason);
672+
}
659673
const result = executeField(
660674
exeContext,
661675
parentType,
@@ -706,6 +720,12 @@ function executeFields(
706720
try {
707721
for (const [responseName, fieldDetailsList] of groupedFieldSet) {
708722
const fieldPath = addPath(path, responseName, parentType.name);
723+
724+
if (exeContext.abortSignal?.aborted) {
725+
// We might want to leverage a GraphQL error here
726+
throw new GraphQLError(exeContext.abortSignal.reason);
727+
}
728+
709729
const result = executeField(
710730
exeContext,
711731
parentType,
@@ -1069,6 +1089,11 @@ async function completePromisedValue(
10691089
if (isPromise(completed)) {
10701090
completed = await completed;
10711091
}
1092+
1093+
if (exeContext.abortSignal?.aborted) {
1094+
throw new GraphQLError(exeContext.abortSignal.reason);
1095+
}
1096+
10721097
return completed;
10731098
} catch (rawError) {
10741099
handleFieldError(

0 commit comments

Comments
 (0)