Skip to content

Commit f15c6aa

Browse files
committed
deduplicate (mask) leaf values that have already been sent
causes some additional minor changes to `hasNext` property on some payloads due to modification of returned promises resulting in an extra tick
1 parent dd3d9e9 commit f15c6aa

File tree

3 files changed

+48
-24
lines changed

3 files changed

+48
-24
lines changed

src/execution/__tests__/defer-test.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ describe('Execute: defer directive', () => {
591591
]);
592592
});
593593

594-
it('Can deduplicate initial fields with deferred fragments at multiple levels', async () => {
594+
it('Can deduplicate fields with deferred fragments at multiple levels', async () => {
595595
const document = parse(`
596596
query {
597597
hero {
@@ -650,19 +650,14 @@ describe('Execute: defer directive', () => {
650650
},
651651
{
652652
data: {
653-
deeperObject: {
654-
bar: 'bar',
655-
baz: 'baz',
656-
},
653+
deeperObject: {},
657654
},
658655
path: ['hero', 'nestedObject'],
659656
},
660657
{
661658
data: {
662659
nestedObject: {
663-
deeperObject: {
664-
bar: 'bar',
665-
},
660+
deeperObject: {},
666661
},
667662
},
668663
path: ['hero'],
@@ -732,7 +727,7 @@ describe('Execute: defer directive', () => {
732727
]);
733728
});
734729

735-
it('can deduplicate initial fields with deferred fragments in different branches at multiple non-overlapping levels', async () => {
730+
it('can deduplicate fields with deferred fragments in different branches at multiple non-overlapping levels', async () => {
736731
const document = parse(`
737732
query {
738733
a {
@@ -789,9 +784,7 @@ describe('Execute: defer directive', () => {
789784
data: {
790785
a: {
791786
b: {
792-
e: {
793-
f: 'f',
794-
},
787+
e: {},
795788
},
796789
},
797790
g: {

src/execution/__tests__/stream-test.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,9 +1170,6 @@ describe('Execute: stream directive', () => {
11701170
],
11711171
},
11721172
],
1173-
hasNext: true,
1174-
},
1175-
{
11761173
hasNext: false,
11771174
},
11781175
]);
@@ -1355,9 +1352,6 @@ describe('Execute: stream directive', () => {
13551352
],
13561353
},
13571354
],
1358-
hasNext: true,
1359-
},
1360-
{
13611355
hasNext: false,
13621356
},
13631357
]);

src/execution/execute.ts

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export interface ExecutionContext {
124124
errors: Array<GraphQLError>;
125125
subsequentPayloads: Set<AsyncPayloadRecord>;
126126
branches: WeakMap<GroupedFieldSet, Set<string>>;
127+
leaves: Set<string>;
127128
}
128129

129130
/**
@@ -503,6 +504,7 @@ export function buildExecutionContext(
503504
subscribeFieldResolver: subscribeFieldResolver ?? defaultFieldResolver,
504505
subsequentPayloads: new Set(),
505506
branches: new WeakMap(),
507+
leaves: new Set(),
506508
errors: [],
507509
};
508510
}
@@ -516,6 +518,7 @@ function buildPerEventExecutionContext(
516518
rootValue: payload,
517519
subsequentPayloads: new Set(),
518520
branches: new WeakMap(),
521+
leaves: new Set(),
519522
errors: [],
520523
};
521524
}
@@ -638,7 +641,9 @@ function executeFieldsSerially(
638641

639642
const returnType = fieldDef.type;
640643

641-
if (!shouldExecuteFieldSet(returnType, fieldSet, undefined)) {
644+
const isLeaf = isLeafType(getNamedType(returnType));
645+
646+
if (!shouldExecuteFieldSet(fieldSet, isLeaf, undefined)) {
642647
return results;
643648
}
644649
const result = executeField(
@@ -665,11 +670,11 @@ function executeFieldsSerially(
665670
}
666671

667672
function shouldExecuteFieldSet(
668-
returnType: GraphQLOutputType,
669673
fieldSet: ReadonlyArray<TaggedFieldNode>,
674+
isLeaf: boolean,
670675
deferDepth: number | undefined,
671676
): boolean {
672-
if (deferDepth === undefined || !isLeafType(getNamedType(returnType))) {
677+
if (deferDepth === undefined || !isLeaf) {
673678
return fieldSet.some(
674679
({ deferDepth: fieldDeferDepth }) => fieldDeferDepth === deferDepth,
675680
);
@@ -703,6 +708,8 @@ function executeFields(
703708
const results = Object.create(null);
704709
let containsPromise = false;
705710

711+
const shouldMask = Object.create(null);
712+
706713
try {
707714
for (const [responseName, fieldSet] of groupedFieldSet) {
708715
const fieldPath = addPath(path, responseName, parentType.name);
@@ -715,7 +722,23 @@ function executeFields(
715722

716723
const returnType = fieldDef.type;
717724

718-
if (shouldExecuteFieldSet(returnType, fieldSet, deferDepth)) {
725+
const isLeaf = isLeafType(getNamedType(returnType));
726+
727+
if (shouldExecuteFieldSet(fieldSet, isLeaf, deferDepth)) {
728+
if (
729+
asyncPayloadRecord !== undefined &&
730+
isLeafType(getNamedType(returnType))
731+
) {
732+
shouldMask[responseName] = () => {
733+
const key = pathToArray(fieldPath).join('.');
734+
if (exeContext.leaves.has(key)) {
735+
return true;
736+
}
737+
exeContext.leaves.add(key);
738+
return false;
739+
};
740+
}
741+
719742
const result = executeField(
720743
exeContext,
721744
parentType,
@@ -748,13 +771,27 @@ function executeFields(
748771

749772
// If there are no promises, we can just return the object
750773
if (!containsPromise) {
751-
return results;
774+
return asyncPayloadRecord === undefined
775+
? results
776+
: new Proxy(results, {
777+
ownKeys: (target) =>
778+
Reflect.ownKeys(target).filter((key) => !shouldMask[key]?.()),
779+
});
752780
}
753781

754782
// Otherwise, results is a map from field name to the result of resolving that
755783
// field, which is possibly a promise. Return a promise that will return this
756784
// same map, but with any promises replaced with the values they resolved to.
757-
return promiseForObject(results);
785+
const promisedResult = promiseForObject(results);
786+
return asyncPayloadRecord === undefined
787+
? promisedResult
788+
: promisedResult.then(
789+
(resolved) =>
790+
new Proxy(resolved, {
791+
ownKeys: (target) =>
792+
Reflect.ownKeys(target).filter((key) => !shouldMask[key]?.()),
793+
}),
794+
);
758795
}
759796

760797
/**

0 commit comments

Comments
 (0)