Skip to content
This repository was archived by the owner on Mar 20, 2023. It is now read-only.

Commit 8dc8565

Browse files
committed
support for experimental defer-stream
1 parent 8436480 commit 8dc8565

File tree

4 files changed

+413
-22
lines changed

4 files changed

+413
-22
lines changed

package-lock.json

+2-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"express": "4.17.1",
7878
"flow-bin": "0.128.0",
7979
"graphiql": "0.17.5",
80-
"graphql": "15.2.0",
80+
"graphql": "https://registry.npmjs.org/graphql-experimental/-/graphql-experimental-3.0.3.tgz",
8181
"mocha": "8.0.1",
8282
"multer": "1.4.2",
8383
"nyc": "15.1.0",

src/__tests__/http-test.ts

+295-4
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ const QueryRootType = new GraphQLObjectType({
5656
});
5757

5858
const TestSchema = new GraphQLSchema({
59+
experimentalDefer: true,
60+
experimentalStream: true,
5961
query: QueryRootType,
6062
mutation: new GraphQLObjectType({
6163
name: 'MutationRoot',
@@ -1027,6 +1029,54 @@ function runTests(server: Server) {
10271029
errors: [{ message: 'Must provide query string.' }],
10281030
});
10291031
});
1032+
1033+
it('allows for streaming results with @defer', async () => {
1034+
const app = server();
1035+
1036+
app.post(
1037+
urlString(),
1038+
graphqlHTTP({
1039+
schema: TestSchema,
1040+
}),
1041+
);
1042+
1043+
const req = app
1044+
.request()
1045+
.post(urlString())
1046+
.send({
1047+
query:
1048+
'{ ...frag @defer(label: "deferLabel") } fragment frag on QueryRoot { test(who: "World") }',
1049+
})
1050+
.parse((res, cb) => {
1051+
res.on('data', (data) => {
1052+
res.text = `${res.text || ''}${data.toString('utf8') as string}`;
1053+
});
1054+
res.on('end', (err) => {
1055+
cb(err, null);
1056+
});
1057+
});
1058+
1059+
const response = await req;
1060+
expect(response.text).to.equal(
1061+
[
1062+
'',
1063+
'---',
1064+
'Content-Type: application/json; charset=utf-8',
1065+
'Content-Length: 26',
1066+
'',
1067+
'{"data":{},"hasNext":true}',
1068+
'',
1069+
'---',
1070+
'Content-Type: application/json; charset=utf-8',
1071+
'Content-Length: 78',
1072+
'',
1073+
'{"data":{"test":"Hello World"},"path":[],"label":"deferLabel","hasNext":false}',
1074+
'',
1075+
'-----',
1076+
'',
1077+
].join('\r\n'),
1078+
);
1079+
});
10301080
});
10311081

10321082
describe('Pretty printing', () => {
@@ -1109,6 +1159,62 @@ function runTests(server: Server) {
11091159

11101160
expect(unprettyResponse.text).to.equal('{"data":{"test":"Hello World"}}');
11111161
});
1162+
it('supports pretty printing async iterable requests', async () => {
1163+
const app = server();
1164+
1165+
app.post(
1166+
urlString(),
1167+
graphqlHTTP({
1168+
schema: TestSchema,
1169+
pretty: true,
1170+
}),
1171+
);
1172+
1173+
const req = app
1174+
.request()
1175+
.post(urlString())
1176+
.send({
1177+
query:
1178+
'{ ...frag @defer } fragment frag on QueryRoot { test(who: "World") }',
1179+
})
1180+
.parse((res, cb) => {
1181+
res.on('data', (data) => {
1182+
res.text = `${res.text || ''}${data.toString('utf8') as string}`;
1183+
});
1184+
res.on('end', (err) => {
1185+
cb(err, null);
1186+
});
1187+
});
1188+
1189+
const response = await req;
1190+
expect(response.text).to.equal(
1191+
[
1192+
'',
1193+
'---',
1194+
'Content-Type: application/json; charset=utf-8',
1195+
'Content-Length: 35',
1196+
'',
1197+
['{', ' "data": {},', ' "hasNext": true', '}'].join('\n'),
1198+
'',
1199+
'---',
1200+
'Content-Type: application/json; charset=utf-8',
1201+
'Content-Length: 79',
1202+
'',
1203+
[
1204+
'{',
1205+
' "data": {',
1206+
' "test": "Hello World"',
1207+
' },',
1208+
' "path": [],',
1209+
' "hasNext": false',
1210+
'}',
1211+
].join('\n'),
1212+
'',
1213+
'-----',
1214+
'',
1215+
].join('\r\n'),
1216+
);
1217+
});
11121218
});
11131219

11141220
it('will send request and response when using thunk', async () => {
@@ -1229,6 +1335,108 @@ function runTests(server: Server) {
12291335
});
12301336
});
12311337

1338+
it('allows for custom error formatting in initial payload of async iterator', async () => {
1339+
const app = server();
1340+
1341+
app.post(
1342+
urlString(),
1343+
graphqlHTTP({
1344+
schema: TestSchema,
1345+
customFormatErrorFn(error) {
1346+
return { message: 'Custom error format: ' + error.message };
1347+
},
1348+
}),
1349+
);
1350+
1351+
const req = app
1352+
.request()
1353+
.post(urlString())
1354+
.send({
1355+
query:
1356+
'{ thrower, ...frag @defer } fragment frag on QueryRoot { test(who: "World") }',
1357+
})
1358+
.parse((res, cb) => {
1359+
res.on('data', (data) => {
1360+
res.text = `${res.text || ''}${data.toString('utf8') as string}`;
1361+
});
1362+
res.on('end', (err) => {
1363+
cb(err, null);
1364+
});
1365+
});
1366+
1367+
const response = await req;
1368+
expect(response.text).to.equal(
1369+
[
1370+
'',
1371+
'---',
1372+
'Content-Type: application/json; charset=utf-8',
1373+
'Content-Length: 94',
1374+
'',
1375+
'{"errors":[{"message":"Custom error format: Throws!"}],"data":{"thrower":null},"hasNext":true}',
1376+
'',
1377+
'---',
1378+
'Content-Type: application/json; charset=utf-8',
1379+
'Content-Length: 57',
1380+
'',
1381+
'{"data":{"test":"Hello World"},"path":[],"hasNext":false}',
1382+
'',
1383+
'-----',
1384+
'',
1385+
].join('\r\n'),
1386+
);
1387+
});
1388+
1389+
it('allows for custom error formatting in subsequent payloads of async iterator', async () => {
1390+
const app = server();
1391+
1392+
app.post(
1393+
urlString(),
1394+
graphqlHTTP({
1395+
schema: TestSchema,
1396+
customFormatErrorFn(error) {
1397+
return { message: 'Custom error format: ' + error.message };
1398+
},
1399+
}),
1400+
);
1401+
1402+
const req = app
1403+
.request()
1404+
.post(urlString())
1405+
.send({
1406+
query:
1407+
'{ test(who: "World"), ...frag @defer } fragment frag on QueryRoot { thrower }',
1408+
})
1409+
.parse((res, cb) => {
1410+
res.on('data', (data) => {
1411+
res.text = `${res.text || ''}${data.toString('utf8') as string}`;
1412+
});
1413+
res.on('end', (err) => {
1414+
cb(err, null);
1415+
});
1416+
});
1417+
1418+
const response = await req;
1419+
expect(response.text).to.equal(
1420+
[
1421+
'',
1422+
'---',
1423+
'Content-Type: application/json; charset=utf-8',
1424+
'Content-Length: 46',
1425+
'',
1426+
'{"data":{"test":"Hello World"},"hasNext":true}',
1427+
'',
1428+
'---',
1429+
'Content-Type: application/json; charset=utf-8',
1430+
'Content-Length: 105',
1431+
'',
1432+
'{"data":{"thrower":null},"path":[],"errors":[{"message":"Custom error format: Throws!"}],"hasNext":false}',
1433+
'',
1434+
'-----',
1435+
'',
1436+
].join('\r\n'),
1437+
);
1438+
});
1439+
12321440
it('allows for custom error formatting to elaborate', async () => {
12331441
const app = server();
12341442

@@ -2220,6 +2428,57 @@ function runTests(server: Server) {
22202428
});
22212429
});
22222430

2431+
it('allows for custom extensions in initial and subsequent payloads of async iterator', async () => {
2432+
const app = server();
2433+
2434+
app.post(
2435+
urlString(),
2436+
graphqlHTTP({
2437+
schema: TestSchema,
2438+
extensions({ result }) {
2439+
return { preservedResult: { ...result } };
2440+
},
2441+
}),
2442+
);
2443+
2444+
const req = app
2445+
.request()
2446+
.post(urlString())
2447+
.send({
2448+
query:
2449+
'{ hello: test(who: "Rob"), ...frag @defer } fragment frag on QueryRoot { test(who: "World") }',
2450+
})
2451+
.parse((res, cb) => {
2452+
res.on('data', (data) => {
2453+
res.text = `${res.text || ''}${data.toString('utf8') as string}`;
2454+
});
2455+
res.on('end', (err) => {
2456+
cb(err, null);
2457+
});
2458+
});
2459+
2460+
const response = await req;
2461+
expect(response.text).to.equal(
2462+
[
2463+
'',
2464+
'---',
2465+
'Content-Type: application/json; charset=utf-8',
2466+
'Content-Length: 124',
2467+
'',
2468+
'{"data":{"hello":"Hello Rob"},"hasNext":true,"extensions":{"preservedResult":{"data":{"hello":"Hello Rob"},"hasNext":true}}}',
2469+
'',
2470+
'---',
2471+
'Content-Type: application/json; charset=utf-8',
2472+
'Content-Length: 148',
2473+
'',
2474+
'{"data":{"test":"Hello World"},"path":[],"hasNext":false,"extensions":{"preservedResult":{"data":{"test":"Hello World"},"path":[],"hasNext":false}}}',
2475+
'',
2476+
'-----',
2477+
'',
2478+
].join('\r\n'),
2479+
);
2480+
});
2481+
22232482
it('extension function may be async', async () => {
22242483
const app = server();
22252484

@@ -2260,12 +2519,44 @@ function runTests(server: Server) {
22602519

22612520
const response = await app
22622521
.request()
2263-
.get(urlString({ query: '{test}', raw: '' }))
2264-
.set('Accept', 'text/html');
2522+
.get(
2523+
urlString({
2524+
query:
2525+
'{ hello: test(who: "Rob"), ...frag @defer } fragment frag on QueryRoot { test(who: "World") }',
2526+
raw: '',
2527+
}),
2528+
)
2529+
.set('Accept', 'text/html')
2530+
.parse((res, cb) => {
2531+
res.on('data', (data) => {
2532+
res.text = `${res.text || ''}${data.toString('utf8') as string}`;
2533+
});
2534+
res.on('end', (err) => {
2535+
cb(err, null);
2536+
});
2537+
});
22652538

22662539
expect(response.status).to.equal(200);
2267-
expect(response.type).to.equal('application/json');
2268-
expect(response.text).to.equal('{"data":{"test":"Hello World"}}');
2540+
expect(response.type).to.equal('multipart/mixed');
2541+
expect(response.text).to.equal(
2542+
[
2543+
'',
2544+
'---',
2545+
'Content-Type: application/json; charset=utf-8',
2546+
'Content-Length: 45',
2547+
'',
2548+
'{"data":{"hello":"Hello Rob"},"hasNext":true}',
2549+
'',
2550+
'---',
2551+
'Content-Type: application/json; charset=utf-8',
2552+
'Content-Length: 57',
2553+
'',
2554+
'{"data":{"test":"Hello World"},"path":[],"hasNext":false}',
2555+
'',
2556+
'-----',
2557+
'',
2558+
].join('\r\n'),
2559+
);
22692560
});
22702561
});
22712562
}

0 commit comments

Comments
 (0)