@@ -124,6 +124,7 @@ export interface ExecutionContext {
124
124
errors : Array < GraphQLError > ;
125
125
subsequentPayloads : Set < AsyncPayloadRecord > ;
126
126
branches : WeakMap < GroupedFieldSet , Set < string > > ;
127
+ leaves : Set < string > ;
127
128
}
128
129
129
130
/**
@@ -503,6 +504,7 @@ export function buildExecutionContext(
503
504
subscribeFieldResolver : subscribeFieldResolver ?? defaultFieldResolver ,
504
505
subsequentPayloads : new Set ( ) ,
505
506
branches : new WeakMap ( ) ,
507
+ leaves : new Set ( ) ,
506
508
errors : [ ] ,
507
509
} ;
508
510
}
@@ -516,6 +518,7 @@ function buildPerEventExecutionContext(
516
518
rootValue : payload ,
517
519
subsequentPayloads : new Set ( ) ,
518
520
branches : new WeakMap ( ) ,
521
+ leaves : new Set ( ) ,
519
522
errors : [ ] ,
520
523
} ;
521
524
}
@@ -638,7 +641,9 @@ function executeFieldsSerially(
638
641
639
642
const returnType = fieldDef . type ;
640
643
641
- if ( ! shouldExecuteFieldSet ( returnType , fieldSet , undefined ) ) {
644
+ const isLeaf = isLeafType ( getNamedType ( returnType ) ) ;
645
+
646
+ if ( ! shouldExecuteFieldSet ( fieldSet , isLeaf , undefined ) ) {
642
647
return results ;
643
648
}
644
649
const result = executeField (
@@ -665,11 +670,11 @@ function executeFieldsSerially(
665
670
}
666
671
667
672
function shouldExecuteFieldSet (
668
- returnType : GraphQLOutputType ,
669
673
fieldSet : ReadonlyArray < TaggedFieldNode > ,
674
+ isLeaf : boolean ,
670
675
deferDepth : number | undefined ,
671
676
) : boolean {
672
- if ( deferDepth === undefined || ! isLeafType ( getNamedType ( returnType ) ) ) {
677
+ if ( deferDepth === undefined || ! isLeaf ) {
673
678
return fieldSet . some (
674
679
( { deferDepth : fieldDeferDepth } ) => fieldDeferDepth === deferDepth ,
675
680
) ;
@@ -703,6 +708,8 @@ function executeFields(
703
708
const results = Object . create ( null ) ;
704
709
let containsPromise = false ;
705
710
711
+ const shouldMask = Object . create ( null ) ;
712
+
706
713
try {
707
714
for ( const [ responseName , fieldSet ] of groupedFieldSet ) {
708
715
const fieldPath = addPath ( path , responseName , parentType . name ) ;
@@ -715,7 +722,23 @@ function executeFields(
715
722
716
723
const returnType = fieldDef . type ;
717
724
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
+
719
742
const result = executeField (
720
743
exeContext ,
721
744
parentType ,
@@ -748,13 +771,27 @@ function executeFields(
748
771
749
772
// If there are no promises, we can just return the object
750
773
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
+ } ) ;
752
780
}
753
781
754
782
// Otherwise, results is a map from field name to the result of resolving that
755
783
// field, which is possibly a promise. Return a promise that will return this
756
784
// 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
+ ) ;
758
795
}
759
796
760
797
/**
0 commit comments