Skip to content

Commit 0e8830e

Browse files
committed
add fix for multiple deferred grouped field sets failing
1 parent e15c3ec commit 0e8830e

File tree

2 files changed

+161
-7
lines changed

2 files changed

+161
-7
lines changed

src/execution/IncrementalPublisher.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -508,13 +508,16 @@ class IncrementalPublisher {
508508
) {
509509
for (const deferredFragmentRecord of deferredGroupedFieldSetResult.deferredFragmentRecords) {
510510
const id = deferredFragmentRecord.id;
511-
if (id !== undefined) {
512-
this._completed.push({
513-
id,
514-
errors: deferredGroupedFieldSetResult.errors,
515-
});
516-
this._pending.delete(deferredFragmentRecord);
511+
// This can occur if multiple deferred grouped field sets error for a fragment.
512+
if (!this._pending.has(deferredFragmentRecord)) {
513+
continue;
517514
}
515+
invariant(id !== undefined);
516+
this._completed.push({
517+
id,
518+
errors: deferredGroupedFieldSetResult.errors,
519+
});
520+
this._pending.delete(deferredFragmentRecord);
518521
}
519522
return;
520523
}
@@ -535,8 +538,12 @@ class IncrementalPublisher {
535538
// TODO: add test case for this.
536539
// Presumably, this can occur if an error causes a fragment to be completed early,
537540
// while an asynchronous deferred grouped field set result is enqueued.
541+
// Presumably, this can also occur if multiple deferred fragments are executed
542+
// early and share a deferred grouped field set. This could be worked around
543+
// another way, such as by checking whether the completedResultQueue already
544+
// contains the completedResult prior to pushing.
538545
/* c8 ignore next 3 */
539-
if (id === undefined) {
546+
if (!this._pending.has(deferredFragmentRecord)) {
540547
continue;
541548
}
542549
const reconcilableResults = deferredFragmentRecord.reconcilableResults;
@@ -546,6 +553,7 @@ class IncrementalPublisher {
546553
) {
547554
continue;
548555
}
556+
invariant(id !== undefined);
549557
for (const reconcilableResult of reconcilableResults) {
550558
if (reconcilableResult.sent) {
551559
continue;

src/execution/__tests__/defer-test.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1334,6 +1334,152 @@ describe('Execute: defer directive', () => {
13341334
]);
13351335
});
13361336

1337+
it('Handles multiple erroring deferred grouped field sets', async () => {
1338+
const document = parse(`
1339+
query {
1340+
... @defer {
1341+
a {
1342+
b {
1343+
c {
1344+
someError: nonNullErrorField
1345+
}
1346+
}
1347+
}
1348+
}
1349+
... @defer {
1350+
a {
1351+
b {
1352+
c {
1353+
anotherError: nonNullErrorField
1354+
}
1355+
}
1356+
}
1357+
}
1358+
}
1359+
`);
1360+
const result = await complete(document, {
1361+
a: {
1362+
b: { c: { nonNullErrorField: null } },
1363+
},
1364+
});
1365+
expectJSON(result).toDeepEqual([
1366+
{
1367+
data: {},
1368+
pending: [
1369+
{ id: '0', path: [] },
1370+
{ id: '1', path: [] },
1371+
],
1372+
hasNext: true,
1373+
},
1374+
{
1375+
completed: [
1376+
{
1377+
id: '0',
1378+
errors: [
1379+
{
1380+
message:
1381+
'Cannot return null for non-nullable field c.nonNullErrorField.',
1382+
locations: [{ line: 7, column: 17 }],
1383+
path: ['a', 'b', 'c', 'someError'],
1384+
},
1385+
],
1386+
},
1387+
{
1388+
id: '1',
1389+
errors: [
1390+
{
1391+
message:
1392+
'Cannot return null for non-nullable field c.nonNullErrorField.',
1393+
locations: [{ line: 16, column: 17 }],
1394+
path: ['a', 'b', 'c', 'anotherError'],
1395+
},
1396+
],
1397+
},
1398+
],
1399+
hasNext: false,
1400+
},
1401+
]);
1402+
});
1403+
1404+
it('Handles multiple erroring deferred grouped field sets for the same fragment', async () => {
1405+
const document = parse(`
1406+
query {
1407+
... @defer {
1408+
a {
1409+
b {
1410+
someC: c {
1411+
d: d
1412+
}
1413+
anotherC: c {
1414+
d: d
1415+
}
1416+
}
1417+
}
1418+
}
1419+
... @defer {
1420+
a {
1421+
b {
1422+
someC: c {
1423+
someError: nonNullErrorField
1424+
}
1425+
anotherC: c {
1426+
anotherError: nonNullErrorField
1427+
}
1428+
}
1429+
}
1430+
}
1431+
}
1432+
`);
1433+
const result = await complete(document, {
1434+
a: {
1435+
b: { c: { d: 'd', nonNullErrorField: null } },
1436+
},
1437+
});
1438+
expectJSON(result).toDeepEqual([
1439+
{
1440+
data: {},
1441+
pending: [
1442+
{ id: '0', path: [] },
1443+
{ id: '1', path: [] },
1444+
],
1445+
hasNext: true,
1446+
},
1447+
{
1448+
incremental: [
1449+
{
1450+
data: { a: { b: { someC: {}, anotherC: {} } } },
1451+
id: '0',
1452+
},
1453+
{
1454+
data: { d: 'd' },
1455+
id: '0',
1456+
subPath: ['a', 'b', 'someC'],
1457+
},
1458+
{
1459+
data: { d: 'd' },
1460+
id: '0',
1461+
subPath: ['a', 'b', 'anotherC'],
1462+
},
1463+
],
1464+
completed: [
1465+
{
1466+
id: '1',
1467+
errors: [
1468+
{
1469+
message:
1470+
'Cannot return null for non-nullable field c.nonNullErrorField.',
1471+
locations: [{ line: 19, column: 17 }],
1472+
path: ['a', 'b', 'someC', 'someError'],
1473+
},
1474+
],
1475+
},
1476+
{ id: '0' },
1477+
],
1478+
hasNext: false,
1479+
},
1480+
]);
1481+
});
1482+
13371483
it('filters a payload with a null that cannot be merged', async () => {
13381484
const document = parse(`
13391485
query {

0 commit comments

Comments
 (0)