@@ -58,7 +58,21 @@ const query = new GraphQLObjectType({
5858 scalarField : {
5959 type : GraphQLString ,
6060 } ,
61+ nonNullScalarField : {
62+ type : new GraphQLNonNull ( GraphQLString ) ,
63+ } ,
6164 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+ } ,
6276 } ,
6377 } ) ,
6478 } ,
@@ -765,9 +779,15 @@ describe('Execute: stream directive', () => {
765779 ` ) ;
766780 const result = await complete ( document , {
767781 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+ }
771791 } ,
772792 } ) ;
773793 expectJSON ( result ) . toDeepEqual ( [
@@ -792,18 +812,6 @@ describe('Execute: stream directive', () => {
792812 ] ,
793813 } ,
794814 ] ,
795- hasNext : true ,
796- } ,
797- {
798- incremental : [
799- {
800- items : [ { name : 'Han' } ] ,
801- path : [ 'nonNullFriendList' , 2 ] ,
802- } ,
803- ] ,
804- hasNext : true ,
805- } ,
806- {
807815 hasNext : false ,
808816 } ,
809817 ] ) ;
@@ -886,15 +894,6 @@ describe('Execute: stream directive', () => {
886894 ] ,
887895 } ,
888896 ] ,
889- hasNext : true ,
890- } ,
891- {
892- incremental : [
893- {
894- items : [ { nonNullName : 'Han' } ] ,
895- path : [ 'nonNullFriendList' , 2 ] ,
896- } ,
897- ] ,
898897 hasNext : false ,
899898 } ,
900899 ] ) ;
@@ -953,6 +952,302 @@ describe('Execute: stream directive', () => {
953952 } ,
954953 ] ) ;
955954 } ) ;
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+ } ) ;
9561251 it ( 'Handles promises returned by completeValue after initialCount is reached' , async ( ) => {
9571252 const document = parse ( `
9581253 query {
0 commit comments