Skip to content

Commit d5c7db5

Browse files
committed
WIP
1 parent f531737 commit d5c7db5

File tree

5 files changed

+345
-6
lines changed

5 files changed

+345
-6
lines changed

package-lock.json

Lines changed: 4 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@svgr/webpack": "8.1.0",
6363
"@types/chai": "4.3.19",
6464
"@types/mocha": "10.0.7",
65-
"@types/node": "22.5.4",
65+
"@types/node": "22.7.7",
6666
"@typescript-eslint/eslint-plugin": "8.4.0",
6767
"@typescript-eslint/parser": "8.4.0",
6868
"c8": "10.1.2",
Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { expectJSON } from '../../__testUtils__/expectJSON.js';
5+
import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js';
6+
7+
import type { DocumentNode } from '../../language/ast.js';
8+
import { parse } from '../../language/parser.js';
9+
10+
import {
11+
GraphQLObjectType,
12+
GraphQLSchema,
13+
GraphQLString,
14+
} from '../../type/index.js';
15+
16+
import { buildSchema } from '../../utilities/buildASTSchema.js';
17+
18+
import { execute, experimentalExecuteIncrementally } from '../execute.js';
19+
import type {
20+
InitialIncrementalExecutionResult,
21+
SubsequentIncrementalExecutionResult,
22+
} from '../types.js';
23+
24+
async function complete(
25+
document: DocumentNode,
26+
rootValue: unknown,
27+
abortSignal: AbortSignal,
28+
) {
29+
const result = await experimentalExecuteIncrementally({
30+
schema,
31+
document,
32+
rootValue,
33+
abortSignal,
34+
});
35+
36+
if ('initialResult' in result) {
37+
const results: Array<
38+
InitialIncrementalExecutionResult | SubsequentIncrementalExecutionResult
39+
> = [result.initialResult];
40+
for await (const patch of result.subsequentResults) {
41+
results.push(patch);
42+
}
43+
return results;
44+
}
45+
}
46+
47+
const schema = buildSchema(/* GraphQL */ `
48+
type Todo {
49+
id: ID!
50+
text: String!
51+
completed: Boolean!
52+
author: User
53+
}
54+
55+
type User {
56+
id: ID!
57+
name: String!
58+
}
59+
60+
type Query {
61+
todo: Todo
62+
}
63+
64+
type Mutation {
65+
foo: String
66+
bar: String
67+
}
68+
`);
69+
70+
describe('Abort Signal', () => {
71+
it('should stop the execution when aborted in resolver', async () => {
72+
const abortController = new AbortController();
73+
const document = parse(/* GraphQL */ `
74+
query {
75+
todo {
76+
id
77+
author {
78+
id
79+
}
80+
}
81+
}
82+
`);
83+
const result = await execute({
84+
document,
85+
schema,
86+
abortSignal: abortController.signal,
87+
rootValue: {
88+
todo() {
89+
abortController.abort('Aborted');
90+
return {
91+
id: '1',
92+
text: 'Hello, World!',
93+
completed: false,
94+
/* c8 ignore next 3 */
95+
author: () => {
96+
expect.fail('Should not be called');
97+
},
98+
};
99+
},
100+
},
101+
});
102+
103+
expectJSON(result).toDeepEqual({
104+
data: {
105+
todo: null,
106+
},
107+
errors: [
108+
{
109+
locations: [
110+
{
111+
column: 9,
112+
line: 3,
113+
},
114+
],
115+
message: 'Aborted',
116+
path: ['todo'],
117+
},
118+
],
119+
});
120+
});
121+
122+
it('should stop the execution when aborted in deferred resolver', async () => {
123+
const abortController = new AbortController();
124+
const document = parse(/* GraphQL */ `
125+
query {
126+
todo {
127+
id
128+
... on Todo @defer {
129+
text
130+
author {
131+
... on Author @defer {
132+
id
133+
}
134+
}
135+
}
136+
}
137+
}
138+
`);
139+
const result = complete(
140+
document,
141+
{
142+
todo() {
143+
return {
144+
id: '1',
145+
text: () => {
146+
abortController.abort('Aborted');
147+
return 'hello world';
148+
},
149+
/* c8 ignore next 3 */
150+
author: async () => {
151+
await resolveOnNextTick();
152+
return { id: '2' };
153+
},
154+
};
155+
},
156+
},
157+
abortController.signal,
158+
);
159+
160+
expectJSON(await result).toDeepEqual([
161+
{
162+
data: {
163+
todo: {
164+
id: '1',
165+
},
166+
},
167+
pending: [{ id: '0', path: ['todo'] }],
168+
hasNext: true,
169+
},
170+
{
171+
completed: [
172+
{
173+
errors: [
174+
{
175+
message: 'Aborted',
176+
},
177+
],
178+
id: '0',
179+
},
180+
],
181+
hasNext: false,
182+
},
183+
]);
184+
});
185+
186+
it('should stop the for serial mutation execution', async () => {
187+
const abortController = new AbortController();
188+
const document = parse(/* GraphQL */ `
189+
mutation {
190+
foo
191+
bar
192+
}
193+
`);
194+
const result = await execute({
195+
document,
196+
schema,
197+
abortSignal: abortController.signal,
198+
rootValue: {
199+
foo() {
200+
abortController.abort('Aborted');
201+
return 'baz';
202+
},
203+
/* c8 ignore next 3 */
204+
bar() {
205+
expect.fail('Should not be called');
206+
},
207+
},
208+
});
209+
210+
expectJSON(result).toDeepEqual({
211+
data: null,
212+
errors: [
213+
{
214+
message: 'Aborted',
215+
},
216+
],
217+
});
218+
});
219+
220+
it('should stop the execution when aborted pre-execute', async () => {
221+
const abortController = new AbortController();
222+
const document = parse(/* GraphQL */ `
223+
query {
224+
todo {
225+
id
226+
author {
227+
id
228+
}
229+
}
230+
}
231+
`);
232+
abortController.abort('Aborted');
233+
const result = await execute({
234+
document,
235+
schema,
236+
abortSignal: abortController.signal,
237+
rootValue: {
238+
/* c8 ignore next 3 */
239+
todo() {
240+
return {};
241+
},
242+
},
243+
});
244+
245+
expectJSON(result).toDeepEqual({
246+
data: null,
247+
errors: [
248+
{
249+
message: 'Aborted',
250+
},
251+
],
252+
});
253+
});
254+
255+
it('exits early on abort mid-execution', async () => {
256+
const asyncObjectType = new GraphQLObjectType({
257+
name: 'AsyncObject',
258+
fields: {
259+
field: {
260+
type: GraphQLString,
261+
/* c8 ignore next 3 */
262+
resolve() {
263+
expect.fail('Should not be called');
264+
},
265+
},
266+
},
267+
});
268+
269+
const newSchema = new GraphQLSchema({
270+
query: new GraphQLObjectType({
271+
name: 'Query',
272+
fields: {
273+
asyncObject: {
274+
type: asyncObjectType,
275+
async resolve() {
276+
await resolveOnNextTick();
277+
return {};
278+
},
279+
},
280+
},
281+
}),
282+
});
283+
284+
const document = parse(`
285+
{
286+
asyncObject {
287+
field
288+
}
289+
}
290+
`);
291+
292+
const abortController = new AbortController();
293+
294+
const result = execute({
295+
schema: newSchema,
296+
document,
297+
abortSignal: abortController.signal,
298+
});
299+
300+
abortController.abort('This operation was aborted');
301+
302+
expectJSON(await result).toDeepEqual({
303+
data: { asyncObject: null },
304+
errors: [
305+
{
306+
message: 'This operation was aborted',
307+
locations: [{ line: 3, column: 9 }],
308+
path: ['asyncObject'],
309+
},
310+
],
311+
});
312+
});
313+
});

0 commit comments

Comments
 (0)