@@ -99,7 +99,6 @@ export type ExecutionContext = {
99
99
operation : OperationDefinitionNode ,
100
100
variableValues : { [ variable : string ] : mixed } ,
101
101
fieldResolver : GraphQLFieldResolver < any , any> ,
102
- errors : Array < GraphQLError > ,
103
102
} ;
104
103
105
104
/**
@@ -113,6 +112,23 @@ export type ExecutionResult = {
113
112
data ?: ObjMap < mixed > ,
114
113
} ;
115
114
115
+ /**
116
+ * A partial result of GraphQL execution. `data` and `errors` as above.
117
+ * "NP" = "Not Promise".
118
+ */
119
+ export type ExecutionPartialResultNP < T > = {
120
+ errors ?: $ReadOnlyArray < GraphQLError > ,
121
+ data ?: T | null ,
122
+ } ;
123
+
124
+ /**
125
+ * A partial result of GraphQL execution. `data` and `errors` as above.
126
+ * This version might be a promise.
127
+ */
128
+ export type ExecutionPartialResult < T > = MaybePromise <
129
+ ExecutionPartialResultNP < T > ,
130
+ > ;
131
+
116
132
export type ExecutionArgs = { |
117
133
schema : GraphQLSchema ,
118
134
document : DocumentNode ,
@@ -218,24 +234,21 @@ function executeImpl(
218
234
// be executed. An execution which encounters errors will still result in a
219
235
// resolved Promise.
220
236
const data = executeOperation(context, context.operation, rootValue);
221
- return buildResponse(context, data);
237
+ return buildResponse(data);
222
238
}
223
239
224
240
/**
225
- * Given a completed execution context and data, build the { errors, data }
226
- * response defined by the "Response" section of the GraphQL specification.
241
+ * Strip out `errors` if empty.
227
242
*/
228
- function buildResponse (
229
- context : ExecutionContext ,
230
- data : MaybePromise < ObjMap < mixed > | null> ,
231
- ) {
232
- const promise = getPromise ( data ) ;
243
+ function buildResponse ( result : ExecutionPartialResult < mixed > ) {
244
+ const promise = getPromise ( result ) ;
233
245
if ( promise ) {
234
- return promise . then ( resolved => buildResponse ( context , resolved ) ) ;
246
+ return promise . then ( buildResponse ) ;
235
247
}
236
- return context . errors . length === 0
237
- ? { data }
238
- : { errors : context . errors , data } ;
248
+ if ( result . data && ( ! result . errors || ! result . errors . length ) ) {
249
+ return { data : result . data } ;
250
+ }
251
+ return result ;
239
252
}
240
253
241
254
/**
@@ -370,7 +383,6 @@ export function buildExecutionContext(
370
383
operation ,
371
384
variableValues ,
372
385
fieldResolver : fieldResolver || defaultFieldResolver ,
373
- errors ,
374
386
} ;
375
387
}
376
388
@@ -381,7 +393,7 @@ function executeOperation(
381
393
exeContext : ExecutionContext ,
382
394
operation : OperationDefinitionNode ,
383
395
rootValue : mixed ,
384
- ) : MaybePromise < ObjMap < mixed > | null > {
396
+ ) : ExecutionPartialResult < mixed > {
385
397
const type = getOperationRootType ( exeContext . schema , operation ) ;
386
398
const fields = collectFields (
387
399
exeContext ,
@@ -405,15 +417,14 @@ function executeOperation(
405
417
: executeFields ( exeContext , type , rootValue , path , fields ) ;
406
418
const promise = getPromise ( result ) ;
407
419
if ( promise ) {
408
- return promise . then ( undefined , error => {
409
- exeContext . errors . push ( error ) ;
410
- return Promise . resolve ( null ) ;
411
- } ) ;
420
+ return promise . then ( undefined , error => ( {
421
+ data : null ,
422
+ errors : [ error ] ,
423
+ } ) ) ;
412
424
}
413
425
return result ;
414
426
} catch (error) {
415
- exeContext . errors . push ( error ) ;
416
- return null ;
427
+ return { data : null , errors : [ error ] } ;
417
428
}
418
429
}
419
430
@@ -468,8 +479,8 @@ function executeFieldsSerially(
468
479
sourceValue: mixed,
469
480
path: ResponsePath | void,
470
481
fields: ObjMap< Array < FieldNode > > ,
471
- ) : MaybePromise < ObjMap < mixed > > {
472
- return promiseReduce (
482
+ ) : ExecutionPartialResult < mixed > {
483
+ const finalResult = promiseReduce (
473
484
Object . keys ( fields ) ,
474
485
( results , responseName ) => {
475
486
const fieldNodes = fields [ responseName ] ;
@@ -481,7 +492,7 @@ function executeFieldsSerially(
481
492
fieldNodes ,
482
493
fieldPath ,
483
494
) ;
484
- if ( result === undefined ) {
495
+ if ( result === null ) {
485
496
return results ;
486
497
}
487
498
const promise = getPromise ( result ) ;
@@ -496,6 +507,8 @@ function executeFieldsSerially(
496
507
} ,
497
508
Object . create ( null ) ,
498
509
) ;
510
+ const promise = getPromise ( finalResult ) ;
511
+ return promise ? promise . then ( mergeEPRs ) : mergeEPRs ( ( finalResult : any ) ) ;
499
512
}
500
513
501
514
/**
@@ -508,7 +521,7 @@ function executeFields(
508
521
sourceValue: mixed,
509
522
path: ResponsePath | void,
510
523
fields: ObjMap< Array < FieldNode > > ,
511
- ) : MaybePromise < ObjMap < mixed > > {
524
+ ) : ExecutionPartialResult < mixed > {
512
525
let containsPromise = false ;
513
526
514
527
const finalResults = Object . keys ( fields ) . reduce ( ( results , responseName ) => {
@@ -521,7 +534,7 @@ function executeFields(
521
534
fieldNodes ,
522
535
fieldPath ,
523
536
) ;
524
- if ( result === undefined ) {
537
+ if ( result === null ) {
525
538
return results ;
526
539
}
527
540
results [ responseName ] = result ;
@@ -533,14 +546,31 @@ function executeFields(
533
546
534
547
// If there are no promises, we can just return the object
535
548
if ( ! containsPromise ) {
536
- return finalResults ;
549
+ return mergeEPRs ( finalResults ) ;
537
550
}
538
551
539
552
// Otherwise, results is a map from field name to the result
540
553
// of resolving that field, which is possibly a promise. Return
541
554
// a promise that will return this same map, but with any
542
555
// promises replaced with the values they resolved to.
543
- return promiseForObject(finalResults);
556
+ return promiseForObject(finalResults).then(mergeEPRs);
557
+ }
558
+
559
+ function mergeEPRs (
560
+ eprMap : ObjMap < ExecutionPartialResultNP < mixed > > ,
561
+ ) : ExecutionPartialResultNP < mixed > {
562
+ const keys = Object . keys ( eprMap ) ;
563
+ const values = keys . map ( name => eprMap [ name ] ) ;
564
+ return values . reduce (
565
+ ( merged , value , i ) => {
566
+ merged . data [ keys [ i ] ] = value . data ;
567
+ if ( value . errors && value . errors . length ) {
568
+ merged . errors . push ( ...value . errors ) ;
569
+ }
570
+ return merged ;
571
+ } ,
572
+ { data : Object . create ( null ) , errors : [ ] } ,
573
+ ) ;
544
574
}
545
575
546
576
/**
@@ -684,13 +714,13 @@ function resolveField(
684
714
source: mixed,
685
715
fieldNodes: $ReadOnlyArray< FieldNode > ,
686
716
path: ResponsePath,
687
- ): mixed {
717
+ ): ExecutionPartialResult < mixed > | null {
688
718
const fieldNode = fieldNodes [ 0 ] ;
689
719
const fieldName = fieldNode . name . value ;
690
720
691
721
const fieldDef = getFieldDef ( exeContext . schema , parentType , fieldName ) ;
692
722
if ( ! fieldDef ) {
693
- return;
723
+ return null ;
694
724
}
695
725
696
726
const resolveFn = fieldDef . resolve || exeContext . fieldResolver ;
@@ -795,7 +825,7 @@ function completeValueCatchingError(
795
825
info: GraphQLResolveInfo,
796
826
path: ResponsePath,
797
827
result: mixed,
798
- ): mixed {
828
+ ): ExecutionPartialResult < mixed > {
799
829
// If the field type is non-nullable, then it is resolved without any
800
830
// protection from errors, however it still properly locates the error.
801
831
if ( isNonNullType ( returnType ) ) {
@@ -826,17 +856,16 @@ function completeValueCatchingError(
826
856
// the rejection error and resolve to null.
827
857
// Note: we don't rely on a `catch` method, but we do expect "thenable"
828
858
// to take a second callback for the error case.
829
- return promise . then ( undefined , error => {
830
- exeContext . errors . push ( error ) ;
831
- return Promise . resolve ( null ) ;
832
- } ) ;
859
+ return promise . then ( undefined , error => ( {
860
+ data : null ,
861
+ errors : [ error ] ,
862
+ } ) ) ;
833
863
}
834
864
return completed ;
835
865
} catch ( error ) {
836
866
// If `completeValueWithLocatedError` returned abruptly (threw an error),
837
867
// log the error and return null.
838
- exeContext . errors . push ( error ) ;
839
- return null ;
868
+ return { data : null , errors : [ error ] } ;
840
869
}
841
870
}
842
871
@@ -849,7 +878,7 @@ function completeValueWithLocatedError(
849
878
info: GraphQLResolveInfo,
850
879
path: ResponsePath,
851
880
result: mixed,
852
- ): mixed {
881
+ ): ExecutionPartialResult < mixed > {
853
882
try {
854
883
const completed = completeValue (
855
884
exeContext ,
@@ -909,7 +938,7 @@ function completeValue(
909
938
info: GraphQLResolveInfo,
910
939
path: ResponsePath,
911
940
result: mixed,
912
- ): mixed {
941
+ ): ExecutionPartialResult < mixed > {
913
942
// If result is a Promise, apply-lift over completeValue.
914
943
const promise = getPromise ( result ) ;
915
944
if ( promise ) {
@@ -934,7 +963,7 @@ function completeValue(
934
963
path ,
935
964
result ,
936
965
) ;
937
- if ( completed === null ) {
966
+ if ( completed . data === null ) {
938
967
throw new Error (
939
968
`Cannot return null for non-nullable field ${ info . parentType . name } .${
940
969
info . fieldName
@@ -946,7 +975,7 @@ function completeValue(
946
975
947
976
// If result value is null-ish (null, undefined, or NaN) then return null.
948
977
if ( isNullish ( result ) ) {
949
- return null ;
978
+ return { data : null } ;
950
979
}
951
980
952
981
// If field type is List, complete each item in the list with the inner type
@@ -964,7 +993,7 @@ function completeValue(
964
993
// If field type is a leaf type, Scalar or Enum, serialize to a valid value,
965
994
// returning null if serialization is not possible.
966
995
if ( isLeafType ( returnType ) ) {
967
- return completeLeafValue ( returnType , result ) ;
996
+ return { data : completeLeafValue ( returnType , result ) } ;
968
997
}
969
998
970
999
// If field type is an abstract type, Interface or Union, determine the
@@ -1012,7 +1041,7 @@ function completeListValue(
1012
1041
info : GraphQLResolveInfo ,
1013
1042
path : ResponsePath ,
1014
1043
result : mixed ,
1015
- ) : mixed {
1044
+ ) : ExecutionPartialResult < mixed > {
1016
1045
invariant (
1017
1046
isCollection ( result ) ,
1018
1047
`Expected Iterable, but did not find one for field ${
@@ -1044,7 +1073,23 @@ function completeListValue(
1044
1073
completedResults . push ( completedItem ) ;
1045
1074
} ) ;
1046
1075
1047
- return containsPromise ? Promise . all ( completedResults ) : completedResults ;
1076
+ return containsPromise
1077
+ ? Promise . all ( completedResults ) . then ( flattenEPRs )
1078
+ : flattenEPRs ( completedResults ) ;
1079
+ }
1080
+
1081
+ function flattenEPRs (
1082
+ eprs : $ReadOnlyArray < ExecutionPartialResultNP < mixed >> ,
1083
+ ) : ExecutionPartialResultNP < mixed > {
1084
+ const errors = [ ] ;
1085
+ const data = [ ] ;
1086
+ forEach ( ( eprs : any ) , item => {
1087
+ data . push ( item . data ) ;
1088
+ if ( item . errors && item . errors . length ) {
1089
+ errors . push ( ...item . errors ) ;
1090
+ }
1091
+ } ) ;
1092
+ return { data , errors } ;
1048
1093
}
1049
1094
1050
1095
/**
@@ -1074,7 +1119,7 @@ function completeAbstractValue(
1074
1119
info : GraphQLResolveInfo ,
1075
1120
path : ResponsePath ,
1076
1121
result : mixed ,
1077
- ) : mixed {
1122
+ ) : ExecutionPartialResult < mixed > {
1078
1123
const runtimeType = returnType . resolveType
1079
1124
? returnType . resolveType ( result , exeContext . contextValue , info )
1080
1125
: defaultResolveTypeFn ( result , exeContext . contextValue , info , returnType ) ;
@@ -1163,7 +1208,7 @@ function completeObjectValue(
1163
1208
info : GraphQLResolveInfo ,
1164
1209
path : ResponsePath ,
1165
1210
result : mixed ,
1166
- ) : mixed {
1211
+ ) : ExecutionPartialResult < mixed > {
1167
1212
// If there is an isTypeOf predicate function, call it with the
1168
1213
// current result. If isTypeOf returns false, then raise an error rather
1169
1214
// than continuing execution.
@@ -1220,7 +1265,7 @@ function collectAndExecuteSubfields(
1220
1265
info : GraphQLResolveInfo ,
1221
1266
path : ResponsePath ,
1222
1267
result : mixed ,
1223
- ) : mixed {
1268
+ ) : ExecutionPartialResult < mixed > {
1224
1269
// Collect sub-fields to execute to complete this value.
1225
1270
const subFieldNodes = collectSubfields ( exeContext , returnType , fieldNodes ) ;
1226
1271
return executeFields ( exeContext , returnType , result , path , subFieldNodes ) ;
0 commit comments