Skip to content

Commit 34ff573

Browse files
committed
do not initiate multiple streams at the same path
1 parent cb6fac3 commit 34ff573

File tree

2 files changed

+237
-44
lines changed

2 files changed

+237
-44
lines changed

src/execution/__tests__/stream-test.ts

Lines changed: 192 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,30 @@ const friends = [
3737
{ name: 'Leia', id: 3 },
3838
];
3939

40+
const nestedObjectType = new GraphQLObjectType({
41+
name: 'NestedObject',
42+
fields: {
43+
scalarField: {
44+
type: GraphQLString,
45+
},
46+
nonNullScalarField: {
47+
type: new GraphQLNonNull(GraphQLString),
48+
},
49+
nestedFriendList: { type: new GraphQLList(friendType) },
50+
deeperNestedObject: {
51+
type: new GraphQLObjectType({
52+
name: 'DeeperNestedObject',
53+
fields: {
54+
nonNullScalarField: {
55+
type: new GraphQLNonNull(GraphQLString),
56+
},
57+
deeperNestedFriendList: { type: new GraphQLList(friendType) },
58+
},
59+
}),
60+
},
61+
},
62+
});
63+
4064
const query = new GraphQLObjectType({
4165
fields: {
4266
scalarList: {
@@ -52,29 +76,10 @@ const query = new GraphQLObjectType({
5276
type: new GraphQLList(new GraphQLNonNull(friendType)),
5377
},
5478
nestedObject: {
55-
type: new GraphQLObjectType({
56-
name: 'NestedObject',
57-
fields: {
58-
scalarField: {
59-
type: GraphQLString,
60-
},
61-
nonNullScalarField: {
62-
type: new GraphQLNonNull(GraphQLString),
63-
},
64-
nestedFriendList: { type: new GraphQLList(friendType) },
65-
deeperNestedObject: {
66-
type: new GraphQLObjectType({
67-
name: 'DeeperNestedObject',
68-
fields: {
69-
nonNullScalarField: {
70-
type: new GraphQLNonNull(GraphQLString),
71-
},
72-
deeperNestedFriendList: { type: new GraphQLList(friendType) },
73-
},
74-
}),
75-
},
76-
},
77-
}),
79+
type: nestedObjectType,
80+
},
81+
nestedList: {
82+
type: new GraphQLList(nestedObjectType),
7883
},
7984
},
8085
name: 'Query',
@@ -678,6 +683,170 @@ describe('Execute: stream directive', () => {
678683
},
679684
]);
680685
});
686+
it('Does not initiate multiple streams at the same path', async () => {
687+
const document = parse(`
688+
query {
689+
nestedList {
690+
nestedFriendList @stream(initialCount: 2) {
691+
name
692+
id
693+
}
694+
... @defer {
695+
nestedFriendList @stream(initialCount: 2) {
696+
name
697+
id
698+
}
699+
}
700+
}
701+
}
702+
`);
703+
const result = await complete(document, {
704+
nestedList: [
705+
{ nestedFriendList: friends },
706+
{ nestedFriendList: friends },
707+
],
708+
});
709+
expectJSON(result).toDeepEqual([
710+
{
711+
data: {
712+
nestedList: [
713+
{
714+
nestedFriendList: [
715+
{ name: 'Luke', id: '1' },
716+
{ name: 'Han', id: '2' },
717+
],
718+
},
719+
{
720+
nestedFriendList: [
721+
{ name: 'Luke', id: '1' },
722+
{ name: 'Han', id: '2' },
723+
],
724+
},
725+
],
726+
},
727+
hasNext: true,
728+
},
729+
{
730+
incremental: [
731+
{
732+
items: [{ name: 'Leia', id: '3' }],
733+
path: ['nestedList', 0, 'nestedFriendList', 2],
734+
},
735+
{
736+
data: {
737+
nestedFriendList: [
738+
{ name: 'Luke', id: '1' },
739+
{ name: 'Han', id: '2' },
740+
],
741+
},
742+
path: ['nestedList', 0],
743+
},
744+
{
745+
items: [{ name: 'Leia', id: '3' }],
746+
path: ['nestedList', 1, 'nestedFriendList', 2],
747+
},
748+
{
749+
data: {
750+
nestedFriendList: [
751+
{ name: 'Luke', id: '1' },
752+
{ name: 'Han', id: '2' },
753+
],
754+
},
755+
path: ['nestedList', 1],
756+
},
757+
],
758+
hasNext: false,
759+
},
760+
]);
761+
});
762+
it('Does not initiate multiple streams at the same path for async iterables', async () => {
763+
const document = parse(`
764+
query {
765+
nestedList {
766+
nestedFriendList @stream(initialCount: 2) {
767+
name
768+
id
769+
}
770+
... @defer {
771+
nestedFriendList @stream(initialCount: 2) {
772+
name
773+
id
774+
}
775+
}
776+
}
777+
}
778+
`);
779+
const result = await complete(document, {
780+
nestedList: [
781+
{
782+
async *nestedFriendList() {
783+
yield await Promise.resolve(friends[0]);
784+
yield await Promise.resolve(friends[1]);
785+
yield await Promise.resolve(friends[2]);
786+
},
787+
},
788+
{
789+
async *nestedFriendList() {
790+
yield await Promise.resolve(friends[0]);
791+
yield await Promise.resolve(friends[1]);
792+
yield await Promise.resolve(friends[2]);
793+
},
794+
},
795+
],
796+
});
797+
expectJSON(result).toDeepEqual([
798+
{
799+
data: {
800+
nestedList: [
801+
{
802+
nestedFriendList: [
803+
{ name: 'Luke', id: '1' },
804+
{ name: 'Han', id: '2' },
805+
],
806+
},
807+
{
808+
nestedFriendList: [
809+
{ name: 'Luke', id: '1' },
810+
{ name: 'Han', id: '2' },
811+
],
812+
},
813+
],
814+
},
815+
hasNext: true,
816+
},
817+
{
818+
incremental: [
819+
{
820+
data: {
821+
nestedFriendList: [
822+
{ name: 'Luke', id: '1' },
823+
{ name: 'Han', id: '2' },
824+
],
825+
},
826+
path: ['nestedList', 0],
827+
},
828+
{
829+
data: {
830+
nestedFriendList: [
831+
{ name: 'Luke', id: '1' },
832+
{ name: 'Han', id: '2' },
833+
],
834+
},
835+
path: ['nestedList', 1],
836+
},
837+
{
838+
items: [{ name: 'Leia', id: '3' }],
839+
path: ['nestedList', 0, 'nestedFriendList', 2],
840+
},
841+
{
842+
items: [{ name: 'Leia', id: '3' }],
843+
path: ['nestedList', 1, 'nestedFriendList', 2],
844+
},
845+
],
846+
hasNext: false,
847+
},
848+
]);
849+
});
681850
it('Negative values of initialCount throw field errors on a field that returns an async iterable', async () => {
682851
const document = parse(`
683852
query {

src/execution/execute.ts

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export interface ExecutionContext {
125125
errors: Array<GraphQLError>;
126126
subsequentPayloads: Set<AsyncPayloadRecord>;
127127
branches: WeakMap<GroupedFieldSet, Set<Path | undefined>>;
128+
streams: WeakMap<FieldGroup, Set<Path>>;
128129
}
129130

130131
/**
@@ -505,6 +506,7 @@ export function buildExecutionContext(
505506
addPath: createPathFactory(),
506507
subsequentPayloads: new Set(),
507508
branches: new WeakMap(),
509+
streams: new WeakMap(),
508510
errors: [],
509511
};
510512
}
@@ -519,6 +521,7 @@ function buildPerEventExecutionContext(
519521
addPath: createPathFactory(),
520522
subsequentPayloads: new Set(),
521523
branches: new WeakMap(),
524+
streams: new WeakMap(),
522525
errors: [],
523526
};
524527
}
@@ -540,6 +543,23 @@ function shouldBranch(
540543
return false;
541544
}
542545

546+
function shouldStream(
547+
fieldGroup: FieldGroup,
548+
exeContext: ExecutionContext,
549+
path: Path,
550+
): boolean {
551+
const set = exeContext.streams.get(fieldGroup);
552+
if (set === undefined) {
553+
exeContext.streams.set(fieldGroup, new Set([path]));
554+
return true;
555+
}
556+
if (!set.has(path)) {
557+
set.add(path);
558+
return true;
559+
}
560+
return false;
561+
}
562+
543563
/**
544564
* Implements the "Executing operations" section of the spec.
545565
*/
@@ -1102,17 +1122,19 @@ async function completeAsyncIteratorValue(
11021122
typeof stream.initialCount === 'number' &&
11031123
index >= stream.initialCount
11041124
) {
1105-
// eslint-disable-next-line @typescript-eslint/no-floating-promises
1106-
executeStreamAsyncIterator(
1107-
index,
1108-
asyncIterator,
1109-
exeContext,
1110-
fieldGroup,
1111-
info,
1112-
itemType,
1113-
path,
1114-
asyncPayloadRecord,
1115-
);
1125+
if (shouldStream(fieldGroup, exeContext, path)) {
1126+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
1127+
executeStreamAsyncIterator(
1128+
index,
1129+
asyncIterator,
1130+
exeContext,
1131+
fieldGroup,
1132+
info,
1133+
itemType,
1134+
path,
1135+
asyncPayloadRecord,
1136+
);
1137+
}
11161138
break;
11171139
}
11181140

@@ -1208,16 +1230,18 @@ function completeListValue(
12081230
typeof stream.initialCount === 'number' &&
12091231
index >= stream.initialCount
12101232
) {
1211-
executeStreamIterator(
1212-
index,
1213-
iterator,
1214-
exeContext,
1215-
fieldGroup,
1216-
info,
1217-
itemType,
1218-
path,
1219-
asyncPayloadRecord,
1220-
);
1233+
if (shouldStream(fieldGroup, exeContext, path)) {
1234+
executeStreamIterator(
1235+
index,
1236+
iterator,
1237+
exeContext,
1238+
fieldGroup,
1239+
info,
1240+
itemType,
1241+
path,
1242+
asyncPayloadRecord,
1243+
);
1244+
}
12211245
break;
12221246
}
12231247

0 commit comments

Comments
 (0)