@@ -58,7 +58,21 @@ const query = new GraphQLObjectType({
58
58
scalarField : {
59
59
type : GraphQLString ,
60
60
} ,
61
+ nonNullScalarField : {
62
+ type : new GraphQLNonNull ( GraphQLString ) ,
63
+ } ,
61
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
+ } ,
62
76
} ,
63
77
} ) ,
64
78
} ,
@@ -765,9 +779,15 @@ describe('Execute: stream directive', () => {
765
779
` ) ;
766
780
const result = await complete ( document , {
767
781
async * nonNullFriendList ( ) {
768
- yield await Promise . resolve ( friends [ 0 ] ) ;
769
- yield await Promise . resolve ( null ) ;
770
- yield await Promise . resolve ( friends [ 1 ] ) ;
782
+ try {
783
+ yield await Promise . resolve ( friends [ 0 ] ) ;
784
+ yield await Promise . resolve ( null ) ; /* c8 ignore start */
785
+ // Not reachable, early return
786
+ } finally {
787
+ /* c8 ignore stop */
788
+ // eslint-disable-next-line no-unsafe-finally
789
+ throw new Error ( 'Oops' ) ;
790
+ }
771
791
} ,
772
792
} ) ;
773
793
expectJSON ( result ) . toDeepEqual ( [
@@ -792,18 +812,6 @@ describe('Execute: stream directive', () => {
792
812
] ,
793
813
} ,
794
814
] ,
795
- hasNext : true ,
796
- } ,
797
- {
798
- incremental : [
799
- {
800
- items : [ { name : 'Han' } ] ,
801
- path : [ 'nonNullFriendList' , 2 ] ,
802
- } ,
803
- ] ,
804
- hasNext : true ,
805
- } ,
806
- {
807
815
hasNext : false ,
808
816
} ,
809
817
] ) ;
@@ -886,15 +894,6 @@ describe('Execute: stream directive', () => {
886
894
] ,
887
895
} ,
888
896
] ,
889
- hasNext : true ,
890
- } ,
891
- {
892
- incremental : [
893
- {
894
- items : [ { nonNullName : 'Han' } ] ,
895
- path : [ 'nonNullFriendList' , 2 ] ,
896
- } ,
897
- ] ,
898
897
hasNext : false ,
899
898
} ,
900
899
] ) ;
@@ -953,6 +952,302 @@ describe('Execute: stream directive', () => {
953
952
} ,
954
953
] ) ;
955
954
} ) ;
955
+ it ( 'Filters payloads that are nulled' , async ( ) => {
956
+ const document = parse ( `
957
+ query {
958
+ nestedObject {
959
+ nonNullScalarField
960
+ nestedFriendList @stream(initialCount: 0) {
961
+ name
962
+ }
963
+ }
964
+ }
965
+ ` ) ;
966
+ const result = await complete ( document , {
967
+ nestedObject : {
968
+ nonNullScalarField : ( ) => Promise . resolve ( null ) ,
969
+ async * nestedFriendList ( ) {
970
+ yield await Promise . resolve ( friends [ 0 ] ) ;
971
+ } ,
972
+ } ,
973
+ } ) ;
974
+ expectJSON ( result ) . toDeepEqual ( {
975
+ errors : [
976
+ {
977
+ message :
978
+ 'Cannot return null for non-nullable field NestedObject.nonNullScalarField.' ,
979
+ locations : [ { line : 4 , column : 11 } ] ,
980
+ path : [ 'nestedObject' , 'nonNullScalarField' ] ,
981
+ } ,
982
+ ] ,
983
+ data : {
984
+ nestedObject : null ,
985
+ } ,
986
+ } ) ;
987
+ } ) ;
988
+ it ( 'Does not filter payloads when null error is in a different path' , async ( ) => {
989
+ const document = parse ( `
990
+ query {
991
+ otherNestedObject: nestedObject {
992
+ ... @defer {
993
+ scalarField
994
+ }
995
+ }
996
+ nestedObject {
997
+ nestedFriendList @stream(initialCount: 0) {
998
+ name
999
+ }
1000
+ }
1001
+ }
1002
+ ` ) ;
1003
+ const result = await complete ( document , {
1004
+ nestedObject : {
1005
+ scalarField : ( ) => Promise . reject ( new Error ( 'Oops' ) ) ,
1006
+ async * nestedFriendList ( ) {
1007
+ yield await Promise . resolve ( friends [ 0 ] ) ;
1008
+ } ,
1009
+ } ,
1010
+ } ) ;
1011
+ expectJSON ( result ) . toDeepEqual ( [
1012
+ {
1013
+ data : {
1014
+ otherNestedObject : { } ,
1015
+ nestedObject : { nestedFriendList : [ ] } ,
1016
+ } ,
1017
+ hasNext : true ,
1018
+ } ,
1019
+ {
1020
+ incremental : [
1021
+ {
1022
+ data : { scalarField : null } ,
1023
+ path : [ 'otherNestedObject' ] ,
1024
+ errors : [
1025
+ {
1026
+ message : 'Oops' ,
1027
+ locations : [ { line : 5 , column : 13 } ] ,
1028
+ path : [ 'otherNestedObject' , 'scalarField' ] ,
1029
+ } ,
1030
+ ] ,
1031
+ } ,
1032
+ {
1033
+ items : [ { name : 'Luke' } ] ,
1034
+ path : [ 'nestedObject' , 'nestedFriendList' , 0 ] ,
1035
+ } ,
1036
+ ] ,
1037
+ hasNext : true ,
1038
+ } ,
1039
+ {
1040
+ hasNext : false ,
1041
+ } ,
1042
+ ] ) ;
1043
+ } ) ;
1044
+ it ( 'Filters stream payloads that are nulled in a deferred payload' , async ( ) => {
1045
+ const document = parse ( `
1046
+ query {
1047
+ nestedObject {
1048
+ ... @defer {
1049
+ deeperNestedObject {
1050
+ nonNullScalarField
1051
+ deeperNestedFriendList @stream(initialCount: 0) {
1052
+ name
1053
+ }
1054
+ }
1055
+ }
1056
+ }
1057
+ }
1058
+ ` ) ;
1059
+ const result = await complete ( document , {
1060
+ nestedObject : {
1061
+ deeperNestedObject : {
1062
+ nonNullScalarField : ( ) => Promise . resolve ( null ) ,
1063
+ async * deeperNestedFriendList ( ) {
1064
+ yield await Promise . resolve ( friends [ 0 ] ) ;
1065
+ } ,
1066
+ } ,
1067
+ } ,
1068
+ } ) ;
1069
+ expectJSON ( result ) . toDeepEqual ( [
1070
+ {
1071
+ data : {
1072
+ nestedObject : { } ,
1073
+ } ,
1074
+ hasNext : true ,
1075
+ } ,
1076
+ {
1077
+ incremental : [
1078
+ {
1079
+ data : {
1080
+ deeperNestedObject : null ,
1081
+ } ,
1082
+ path : [ 'nestedObject' ] ,
1083
+ errors : [
1084
+ {
1085
+ message :
1086
+ 'Cannot return null for non-nullable field DeeperNestedObject.nonNullScalarField.' ,
1087
+ locations : [ { line : 6 , column : 15 } ] ,
1088
+ path : [
1089
+ 'nestedObject' ,
1090
+ 'deeperNestedObject' ,
1091
+ 'nonNullScalarField' ,
1092
+ ] ,
1093
+ } ,
1094
+ ] ,
1095
+ } ,
1096
+ ] ,
1097
+ hasNext : false ,
1098
+ } ,
1099
+ ] ) ;
1100
+ } ) ;
1101
+ it ( 'Filters defer payloads that are nulled in a stream response' , async ( ) => {
1102
+ const document = parse ( `
1103
+ query {
1104
+ friendList @stream(initialCount: 0) {
1105
+ nonNullName
1106
+ ... @defer {
1107
+ name
1108
+ }
1109
+ }
1110
+ }
1111
+ ` ) ;
1112
+ const result = await complete ( document , {
1113
+ async * friendList ( ) {
1114
+ yield await Promise . resolve ( {
1115
+ name : friends [ 0 ] . name ,
1116
+ nonNullName : ( ) => Promise . resolve ( null ) ,
1117
+ } ) ;
1118
+ } ,
1119
+ } ) ;
1120
+ expectJSON ( result ) . toDeepEqual ( [
1121
+ {
1122
+ data : {
1123
+ friendList : [ ] ,
1124
+ } ,
1125
+ hasNext : true ,
1126
+ } ,
1127
+ {
1128
+ incremental : [
1129
+ {
1130
+ items : [ null ] ,
1131
+ path : [ 'friendList' , 0 ] ,
1132
+ errors : [
1133
+ {
1134
+ message :
1135
+ 'Cannot return null for non-nullable field Friend.nonNullName.' ,
1136
+ locations : [ { line : 4 , column : 9 } ] ,
1137
+ path : [ 'friendList' , 0 , 'nonNullName' ] ,
1138
+ } ,
1139
+ ] ,
1140
+ } ,
1141
+ ] ,
1142
+ hasNext : true ,
1143
+ } ,
1144
+ {
1145
+ hasNext : false ,
1146
+ } ,
1147
+ ] ) ;
1148
+ } ) ;
1149
+
1150
+ it ( 'Returns iterator and ignores errors when stream payloads are filtered' , async ( ) => {
1151
+ let returned = false ;
1152
+ let index = 0 ;
1153
+ const iterable = {
1154
+ [ Symbol . asyncIterator ] : ( ) => ( {
1155
+ next : ( ) => {
1156
+ const friend = friends [ index ++ ] ;
1157
+ if ( ! friend ) {
1158
+ return Promise . resolve ( { done : true , value : undefined } ) ;
1159
+ }
1160
+ return Promise . resolve ( {
1161
+ done : false ,
1162
+ value : {
1163
+ name : friend . name ,
1164
+ nonNullName : null ,
1165
+ } ,
1166
+ } ) ;
1167
+ } ,
1168
+ return : ( ) => {
1169
+ returned = true ;
1170
+ return Promise . reject ( new Error ( 'Oops' ) ) ;
1171
+ } ,
1172
+ } ) ,
1173
+ } ;
1174
+
1175
+ const document = parse ( `
1176
+ query {
1177
+ nestedObject {
1178
+ ... @defer {
1179
+ deeperNestedObject {
1180
+ nonNullScalarField
1181
+ deeperNestedFriendList @stream(initialCount: 0) {
1182
+ name
1183
+ }
1184
+ }
1185
+ }
1186
+ }
1187
+ }
1188
+ ` ) ;
1189
+
1190
+ const executeResult = await experimentalExecuteIncrementally ( {
1191
+ schema,
1192
+ document,
1193
+ rootValue : {
1194
+ nestedObject : {
1195
+ deeperNestedObject : {
1196
+ nonNullScalarField : ( ) => Promise . resolve ( null ) ,
1197
+ deeperNestedFriendList : iterable ,
1198
+ } ,
1199
+ } ,
1200
+ } ,
1201
+ } ) ;
1202
+ assert ( 'initialResult' in executeResult ) ;
1203
+ const iterator = executeResult . subsequentResults [ Symbol . asyncIterator ] ( ) ;
1204
+
1205
+ const result1 = executeResult . initialResult ;
1206
+ expectJSON ( result1 ) . toDeepEqual ( {
1207
+ data : {
1208
+ nestedObject : { } ,
1209
+ } ,
1210
+ hasNext : true ,
1211
+ } ) ;
1212
+
1213
+ const result2 = await iterator . next ( ) ;
1214
+ expectJSON ( result2 ) . toDeepEqual ( {
1215
+ done : false ,
1216
+ value : {
1217
+ incremental : [
1218
+ {
1219
+ data : {
1220
+ deeperNestedObject : null ,
1221
+ } ,
1222
+ path : [ 'nestedObject' ] ,
1223
+ errors : [
1224
+ {
1225
+ message :
1226
+ 'Cannot return null for non-nullable field DeeperNestedObject.nonNullScalarField.' ,
1227
+ locations : [ { line : 6 , column : 15 } ] ,
1228
+ path : [
1229
+ 'nestedObject' ,
1230
+ 'deeperNestedObject' ,
1231
+ 'nonNullScalarField' ,
1232
+ ] ,
1233
+ } ,
1234
+ ] ,
1235
+ } ,
1236
+ ] ,
1237
+ hasNext : true ,
1238
+ } ,
1239
+ } ) ;
1240
+ const result3 = await iterator . next ( ) ;
1241
+ expectJSON ( result3 ) . toDeepEqual ( {
1242
+ done : false ,
1243
+ value : { hasNext : false } ,
1244
+ } ) ;
1245
+
1246
+ const result4 = await iterator . next ( ) ;
1247
+ expectJSON ( result4 ) . toDeepEqual ( { done : true , value : undefined } ) ;
1248
+
1249
+ assert ( returned ) ;
1250
+ } ) ;
956
1251
it ( 'Handles promises returned by completeValue after initialCount is reached' , async ( ) => {
957
1252
const document = parse ( `
958
1253
query {
0 commit comments