Skip to content

Commit 5156819

Browse files
robrichardyaacovCR
authored andcommitted
deduplicate non-leaf fields
# Conflicts: # src/execution/execute.ts
1 parent 9eb30c2 commit 5156819

File tree

2 files changed

+53
-59
lines changed

2 files changed

+53
-59
lines changed

src/execution/__tests__/defer-test.ts

Lines changed: 12 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -395,26 +395,14 @@ describe('Execute: defer directive', () => {
395395
}
396396
`);
397397
const result = await complete(document);
398-
expectJSON(result).toDeepEqual([
399-
{
400-
data: {
401-
hero: {
402-
id: '1',
403-
name: 'Luke',
404-
},
398+
expectJSON(result).toDeepEqual({
399+
data: {
400+
hero: {
401+
id: '1',
402+
name: 'Luke',
405403
},
406-
hasNext: true,
407-
},
408-
{
409-
incremental: [
410-
{
411-
data: {},
412-
path: ['hero'],
413-
},
414-
],
415-
hasNext: false,
416404
},
417-
]);
405+
});
418406
});
419407
it('Can defer a fragment that is also not deferred, non-deferred fragment is first', async () => {
420408
const document = parse(`
@@ -430,26 +418,14 @@ describe('Execute: defer directive', () => {
430418
}
431419
`);
432420
const result = await complete(document);
433-
expectJSON(result).toDeepEqual([
434-
{
435-
data: {
436-
hero: {
437-
id: '1',
438-
name: 'Luke',
439-
},
421+
expectJSON(result).toDeepEqual({
422+
data: {
423+
hero: {
424+
id: '1',
425+
name: 'Luke',
440426
},
441-
hasNext: true,
442-
},
443-
{
444-
incremental: [
445-
{
446-
data: {},
447-
path: ['hero'],
448-
},
449-
],
450-
hasNext: false,
451427
},
452-
]);
428+
});
453429
});
454430

455431
it('Can defer an inline fragment', async () => {
@@ -578,9 +554,6 @@ describe('Execute: defer directive', () => {
578554
bar: 'bar',
579555
},
580556
},
581-
anotherNestedObject: {
582-
deeperObject: {},
583-
},
584557
},
585558
path: ['hero'],
586559
},
@@ -772,14 +745,6 @@ describe('Execute: defer directive', () => {
772745
},
773746
path: ['hero', 'nestedObject', 'deeperObject'],
774747
},
775-
{
776-
data: {
777-
nestedObject: {
778-
deeperObject: {},
779-
},
780-
},
781-
path: ['hero'],
782-
},
783748
],
784749
hasNext: false,
785750
},

src/execution/execute.ts

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ const collectSubfields = memoize3(
8585
),
8686
);
8787

88+
const SKIPPED_FIELD_SYMBOL: unique symbol = Symbol('SkippedField');
89+
8890
/**
8991
* Terminology
9092
*
@@ -566,6 +568,10 @@ function executeOperation(
566568
const path = undefined;
567569
let result;
568570

571+
const willBranch =
572+
newDeferDepth !== undefined &&
573+
shouldBranch(groupedFieldSet, exeContext, path);
574+
569575
switch (operation.operation) {
570576
case OperationTypeNode.QUERY:
571577
result = executeFields(
@@ -574,6 +580,7 @@ function executeOperation(
574580
rootValue,
575581
path,
576582
groupedFieldSet,
583+
willBranch,
577584
);
578585
break;
579586
case OperationTypeNode.MUTATION:
@@ -594,13 +601,11 @@ function executeOperation(
594601
rootValue,
595602
path,
596603
groupedFieldSet,
604+
willBranch,
597605
);
598606
}
599607

600-
if (
601-
newDeferDepth !== undefined &&
602-
shouldBranch(groupedFieldSet, exeContext, path)
603-
) {
608+
if (willBranch) {
604609
executeDeferredFragment(
605610
exeContext,
606611
rootType,
@@ -611,6 +616,11 @@ function executeOperation(
611616
);
612617
}
613618

619+
invariant(
620+
result !== SKIPPED_FIELD_SYMBOL,
621+
'fields in initial payload should not be skipped',
622+
);
623+
614624
return result;
615625
}
616626

@@ -713,10 +723,12 @@ function executeFields(
713723
sourceValue: unknown,
714724
path: Path | undefined,
715725
groupedFieldSet: GroupedFieldSet,
726+
willBranch: Boolean,
716727
asyncPayloadRecord?: AsyncPayloadRecord,
717-
): PromiseOrValue<ObjMap<unknown>> {
728+
): PromiseOrValue<ObjMap<unknown>> | typeof SKIPPED_FIELD_SYMBOL {
718729
const results = Object.create(null);
719730
let containsPromise = false;
731+
let allFieldsSkipped = willBranch ? false : Boolean(groupedFieldSet.size);
720732

721733
try {
722734
for (const [responseName, fieldGroup] of groupedFieldSet) {
@@ -732,6 +744,11 @@ function executeFields(
732744
asyncPayloadRecord,
733745
);
734746

747+
if (result === SKIPPED_FIELD_SYMBOL) {
748+
continue;
749+
}
750+
allFieldsSkipped = false;
751+
735752
if (result !== undefined) {
736753
results[responseName] = result;
737754
if (isPromise(result)) {
@@ -750,6 +767,10 @@ function executeFields(
750767
throw error;
751768
}
752769

770+
if (allFieldsSkipped) {
771+
return SKIPPED_FIELD_SYMBOL;
772+
}
773+
753774
// If there are no promises, we can just return the object
754775
if (!containsPromise) {
755776
return results;
@@ -1395,7 +1416,7 @@ function completeAbstractValue(
13951416
path: Path,
13961417
result: unknown,
13971418
asyncPayloadRecord?: AsyncPayloadRecord,
1398-
): PromiseOrValue<ObjMap<unknown>> {
1419+
): PromiseOrValue<ObjMap<unknown> | typeof SKIPPED_FIELD_SYMBOL> {
13991420
const resolveTypeFn = returnType.resolveType ?? exeContext.typeResolver;
14001421
const contextValue = exeContext.contextValue;
14011422
const runtimeType = resolveTypeFn(result, contextValue, info, returnType);
@@ -1505,7 +1526,7 @@ function completeObjectValue(
15051526
path: Path,
15061527
result: unknown,
15071528
asyncPayloadRecord?: AsyncPayloadRecord,
1508-
): PromiseOrValue<ObjMap<unknown>> {
1529+
): PromiseOrValue<ObjMap<unknown> | typeof SKIPPED_FIELD_SYMBOL> {
15091530
// If there is an isTypeOf predicate function, call it with the
15101531
// current result. If isTypeOf returns false, then raise an error rather
15111532
// than continuing execution.
@@ -1561,27 +1582,29 @@ function collectAndExecuteSubfields(
15611582
path: Path,
15621583
result: unknown,
15631584
asyncPayloadRecord?: AsyncPayloadRecord,
1564-
): PromiseOrValue<ObjMap<unknown>> {
1585+
): PromiseOrValue<ObjMap<unknown>> | typeof SKIPPED_FIELD_SYMBOL {
15651586
// Collect sub-fields to execute to complete this value.
15661587
const { groupedFieldSet, newDeferDepth } = collectSubfields(
15671588
exeContext,
15681589
returnType,
15691590
fieldGroup,
15701591
);
15711592

1593+
const willBranch =
1594+
newDeferDepth !== undefined &&
1595+
shouldBranch(groupedFieldSet, exeContext, path);
1596+
15721597
const subFields = executeFields(
15731598
exeContext,
15741599
returnType,
15751600
result,
15761601
path,
15771602
groupedFieldSet,
1603+
willBranch,
15781604
asyncPayloadRecord,
15791605
);
15801606

1581-
if (
1582-
newDeferDepth !== undefined &&
1583-
shouldBranch(groupedFieldSet, exeContext, path)
1584-
) {
1607+
if (willBranch) {
15851608
executeDeferredFragment(
15861609
exeContext,
15871610
returnType,
@@ -1913,9 +1936,15 @@ function executeDeferredFragment(
19131936
sourceValue,
19141937
path,
19151938
groupedFieldSet,
1939+
false,
19161940
asyncPayloadRecord,
19171941
);
19181942

1943+
if (promiseOrData === SKIPPED_FIELD_SYMBOL) {
1944+
exeContext.subsequentPayloads.delete(asyncPayloadRecord);
1945+
return;
1946+
}
1947+
19191948
if (isPromise(promiseOrData)) {
19201949
promiseOrData = promiseOrData.then(null, (e) => {
19211950
asyncPayloadRecord.errors.push(e);

0 commit comments

Comments
 (0)