Skip to content

Commit ed5420a

Browse files
committed
introduce experimental parallel streaming
extracted from #154 relies on bundlers that can be used for batched streaming as well
1 parent 3083c1d commit ed5420a

10 files changed

Lines changed: 1339 additions & 227 deletions

File tree

.changeset/giant-llamas-peel.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'graphql-executor': patch
3+
---
4+
5+
introduce experimental parallel streaming
6+
7+
Experimental `inParallel` boolean argument to the stream directive may now be used to stream list items as they are ready instead of in sequential list order.

src/execution/__tests__/stream-test.ts

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ const query = new GraphQLObjectType({
100100
type: new GraphQLList(new GraphQLNonNull(friendType)),
101101
async *resolve() {
102102
yield await Promise.resolve(friends[0]);
103-
yield await Promise.resolve(null); /* c8 ignore start */
104-
// Not reachable, error from resolving null
103+
yield await Promise.resolve(null);
104+
yield await Promise.resolve(friends[1]);
105105
},
106106
/* c8 ignore stop */
107107
},
@@ -149,7 +149,7 @@ const query = new GraphQLObjectType({
149149
for (const friend of friends) {
150150
yield friend;
151151
}
152-
await new Promise((r) => setTimeout(r, 1));
152+
await new Promise((r) => setTimeout(r, 10));
153153
},
154154
},
155155
nestedObject: {
@@ -442,6 +442,49 @@ describe('Execute: stream directive', () => {
442442
},
443443
]);
444444
});
445+
it('Can stream in parallel', async () => {
446+
const document = parse(`
447+
query {
448+
asyncSlowList @stream(initialCount: 0, inParallel: true) {
449+
name
450+
id
451+
}
452+
}
453+
`);
454+
const result = await complete(document);
455+
expect(result).to.deep.equal([
456+
{
457+
data: {
458+
asyncSlowList: [],
459+
},
460+
hasNext: true,
461+
},
462+
{
463+
data: {
464+
name: 'Han',
465+
id: '2',
466+
},
467+
path: ['asyncSlowList', 1],
468+
hasNext: true,
469+
},
470+
{
471+
data: {
472+
name: 'Leia',
473+
id: '3',
474+
},
475+
path: ['asyncSlowList', 2],
476+
hasNext: true,
477+
},
478+
{
479+
data: {
480+
name: 'Luke',
481+
id: '1',
482+
},
483+
path: ['asyncSlowList', 0],
484+
hasNext: false,
485+
},
486+
]);
487+
});
445488
it('Handles rejections in a field that returns a list of promises before initialCount is reached', async () => {
446489
const document = parse(`
447490
query {
@@ -876,6 +919,63 @@ describe('Execute: stream directive', () => {
876919
path: ['asyncIterableNonNullError', 1],
877920
},
878921
],
922+
hasNext: true,
923+
},
924+
{
925+
data: {
926+
name: 'Han',
927+
},
928+
path: ['asyncIterableNonNullError', 2],
929+
hasNext: false,
930+
},
931+
]);
932+
});
933+
it('Handles null returned in non-null async iterable list items after initialCount is reached with parallel streaming', async () => {
934+
const document = parse(`
935+
query {
936+
asyncIterableNonNullError @stream(initialCount: 0, inParallel: true) {
937+
name
938+
}
939+
}
940+
`);
941+
const result = await complete(document);
942+
expectJSON(result).toDeepEqual([
943+
{
944+
data: {
945+
asyncIterableNonNullError: [],
946+
},
947+
hasNext: true,
948+
},
949+
{
950+
data: {
951+
name: 'Luke',
952+
},
953+
path: ['asyncIterableNonNullError', 0],
954+
hasNext: true,
955+
},
956+
{
957+
data: null,
958+
path: ['asyncIterableNonNullError', 1],
959+
errors: [
960+
{
961+
message:
962+
'Cannot return null for non-nullable field Query.asyncIterableNonNullError.',
963+
locations: [
964+
{
965+
line: 3,
966+
column: 9,
967+
},
968+
],
969+
path: ['asyncIterableNonNullError', 1],
970+
},
971+
],
972+
hasNext: true,
973+
},
974+
{
975+
data: {
976+
name: 'Han',
977+
},
978+
path: ['asyncIterableNonNullError', 2],
879979
hasNext: false,
880980
},
881981
]);

0 commit comments

Comments
 (0)