@@ -45,6 +45,7 @@ import {
45
45
TypeMetaFieldDef ,
46
46
TypeNameMetaFieldDef ,
47
47
} from '../type/introspection' ;
48
+ import { GraphQLStreamDirective } from '../type/directives' ;
48
49
import {
49
50
isObjectType ,
50
51
isAbstractType ,
@@ -53,7 +54,11 @@ import {
53
54
isNonNullType ,
54
55
} from '../type/definition' ;
55
56
56
- import { getVariableValues , getArgumentValues } from './values' ;
57
+ import {
58
+ getVariableValues ,
59
+ getArgumentValues ,
60
+ getDirectiveValues ,
61
+ } from './values' ;
57
62
import {
58
63
collectFields ,
59
64
collectSubfields as _collectSubfields ,
@@ -158,7 +163,7 @@ export interface FormattedExecutionResult<
158
163
* - `extensions` is reserved for adding non-standard properties.
159
164
*/
160
165
export interface ExecutionPatchResult <
161
- TData = ObjMap < unknown > ,
166
+ TData = ObjMap < unknown > | unknown ,
162
167
TExtensions = ObjMap < unknown > ,
163
168
> {
164
169
errors ?: ReadonlyArray < GraphQLError > ;
@@ -170,7 +175,7 @@ export interface ExecutionPatchResult<
170
175
}
171
176
172
177
export interface FormattedExecutionPatchResult <
173
- TData = ObjMap < unknown > ,
178
+ TData = ObjMap < unknown > | unknown ,
174
179
TExtensions = ObjMap < unknown > ,
175
180
> {
176
181
errors ?: ReadonlyArray < GraphQLFormattedError > ;
@@ -804,6 +809,44 @@ function completeValue(
804
809
) ;
805
810
}
806
811
812
+ /**
813
+ * Returns an object containing the `@stream` arguments if a field should be
814
+ * streamed based on the experimental flag, stream directive present and
815
+ * not disabled by the "if" argument.
816
+ */
817
+ function getStreamValues (
818
+ exeContext : ExecutionContext ,
819
+ fieldNodes : ReadonlyArray < FieldNode > ,
820
+ ) :
821
+ | undefined
822
+ | {
823
+ initialCount ?: number ;
824
+ label ?: string ;
825
+ } {
826
+ // validation only allows equivalent streams on multiple fields, so it is
827
+ // safe to only check the first fieldNode for the stream directive
828
+ const stream = getDirectiveValues (
829
+ GraphQLStreamDirective ,
830
+ fieldNodes [ 0 ] ,
831
+ exeContext . variableValues ,
832
+ ) ;
833
+
834
+ if ( ! stream ) {
835
+ return ;
836
+ }
837
+
838
+ if ( stream . if === false ) {
839
+ return ;
840
+ }
841
+
842
+ return {
843
+ initialCount :
844
+ // istanbul ignore next (initialCount is required number argument)
845
+ typeof stream . initialCount === 'number' ? stream . initialCount : undefined ,
846
+ label : typeof stream . label === 'string' ? stream . label : undefined ,
847
+ } ;
848
+ }
849
+
807
850
/**
808
851
* Complete a async iterator value by completing the result and calling
809
852
* recursively until all the results are completed.
@@ -818,8 +861,28 @@ function completeAsyncIteratorValue(
818
861
errors : Array < GraphQLError > ,
819
862
) : Promise < ReadonlyArray < unknown > > {
820
863
let containsPromise = false ;
864
+ const stream = getStreamValues ( exeContext , fieldNodes ) ;
821
865
return new Promise < ReadonlyArray < unknown > > ( ( resolve ) => {
822
866
function next ( index : number , completedResults : Array < unknown > ) {
867
+ if (
868
+ stream &&
869
+ typeof stream . initialCount === 'number' &&
870
+ index >= stream . initialCount
871
+ ) {
872
+ exeContext . dispatcher . addAsyncIteratorValue (
873
+ index ,
874
+ iterator ,
875
+ exeContext ,
876
+ fieldNodes ,
877
+ info ,
878
+ itemType ,
879
+ path ,
880
+ stream . label ,
881
+ ) ;
882
+ resolve ( completedResults ) ;
883
+ return ;
884
+ }
885
+
823
886
const fieldPath = addPath ( path , index , undefined ) ;
824
887
iterator . next ( ) . then (
825
888
( { value, done } ) => {
@@ -908,15 +971,37 @@ function completeListValue(
908
971
) ;
909
972
}
910
973
974
+ const stream = getStreamValues ( exeContext , fieldNodes ) ;
975
+
911
976
// This is specified as a simple map, however we're optimizing the path
912
977
// where the list contains no Promises by avoiding creating another Promise.
913
978
let containsPromise = false ;
914
- const completedResults = Array . from ( result , ( item , index ) => {
979
+ const completedResults = [ ] ;
980
+ let index = 0 ;
981
+ for ( const item of result ) {
915
982
// No need to modify the info object containing the path,
916
983
// since from here on it is not ever accessed by resolver functions.
917
984
const itemPath = addPath ( path , index , undefined ) ;
918
985
try {
919
986
let completedItem ;
987
+
988
+ if (
989
+ stream &&
990
+ typeof stream . initialCount === 'number' &&
991
+ index >= stream . initialCount
992
+ ) {
993
+ exeContext . dispatcher . addValue (
994
+ itemPath ,
995
+ item ,
996
+ exeContext ,
997
+ fieldNodes ,
998
+ info ,
999
+ itemType ,
1000
+ stream . label ,
1001
+ ) ;
1002
+ index ++ ;
1003
+ continue ;
1004
+ }
920
1005
if ( isPromise ( item ) ) {
921
1006
completedItem = item . then ( ( resolved ) =>
922
1007
completeValue (
@@ -945,21 +1030,25 @@ function completeListValue(
945
1030
containsPromise = true ;
946
1031
// Note: we don't rely on a `catch` method, but we do expect "thenable"
947
1032
// to take a second callback for the error case.
948
- return completedItem . then ( undefined , ( rawError ) => {
949
- const error = locatedError (
950
- rawError ,
951
- fieldNodes ,
952
- pathToArray ( itemPath ) ,
953
- ) ;
954
- return handleFieldError ( error , itemType , errors ) ;
955
- } ) ;
1033
+ completedResults . push (
1034
+ completedItem . then ( undefined , ( rawError ) => {
1035
+ const error = locatedError (
1036
+ rawError ,
1037
+ fieldNodes ,
1038
+ pathToArray ( itemPath ) ,
1039
+ ) ;
1040
+ return handleFieldError ( error , itemType , errors ) ;
1041
+ } ) ,
1042
+ ) ;
1043
+ } else {
1044
+ completedResults . push ( completedItem ) ;
956
1045
}
957
- return completedItem ;
958
1046
} catch ( rawError ) {
959
1047
const error = locatedError ( rawError , fieldNodes , pathToArray ( itemPath ) ) ;
960
- return handleFieldError ( error , itemType , errors ) ;
1048
+ completedResults . push ( handleFieldError ( error , itemType , errors ) ) ;
961
1049
}
962
- } ) ;
1050
+ index ++ ;
1051
+ }
963
1052
964
1053
return containsPromise ? Promise . all ( completedResults ) : completedResults ;
965
1054
}
@@ -1301,7 +1390,7 @@ export function getFieldDef(
1301
1390
*/
1302
1391
interface DispatcherResult {
1303
1392
errors ?: ReadonlyArray < GraphQLError > ;
1304
- data ?: ObjMap < unknown > | null ;
1393
+ data ?: ObjMap < unknown > | unknown | null ;
1305
1394
path : ReadonlyArray < string | number > ;
1306
1395
label ?: string ;
1307
1396
extensions ?: ObjMap < unknown > ;
@@ -1341,6 +1430,129 @@ export class Dispatcher {
1341
1430
) ;
1342
1431
}
1343
1432
1433
+ addValue (
1434
+ path : Path ,
1435
+ promiseOrData : PromiseOrValue < unknown > ,
1436
+ exeContext : ExecutionContext ,
1437
+ fieldNodes : ReadonlyArray < FieldNode > ,
1438
+ info : GraphQLResolveInfo ,
1439
+ itemType : GraphQLOutputType ,
1440
+ label ?: string ,
1441
+ ) : void {
1442
+ const errors : Array < GraphQLError > = [ ] ;
1443
+ this . _subsequentPayloads . push (
1444
+ Promise . resolve ( promiseOrData )
1445
+ . then ( ( resolved ) =>
1446
+ completeValue (
1447
+ exeContext ,
1448
+ itemType ,
1449
+ fieldNodes ,
1450
+ info ,
1451
+ path ,
1452
+ resolved ,
1453
+ errors ,
1454
+ ) ,
1455
+ )
1456
+ // Note: we don't rely on a `catch` method, but we do expect "thenable"
1457
+ // to take a second callback for the error case.
1458
+ . then ( undefined , ( rawError ) => {
1459
+ const error = locatedError ( rawError , fieldNodes , pathToArray ( path ) ) ;
1460
+ return handleFieldError ( error , itemType , errors ) ;
1461
+ } )
1462
+ . then ( ( data ) => ( {
1463
+ value : createPatchResult ( data , label , path , errors ) ,
1464
+ done : false ,
1465
+ } ) ) ,
1466
+ ) ;
1467
+ }
1468
+
1469
+ addAsyncIteratorValue (
1470
+ initialIndex : number ,
1471
+ iterator : AsyncIterator < unknown > ,
1472
+ exeContext : ExecutionContext ,
1473
+ fieldNodes : ReadonlyArray < FieldNode > ,
1474
+ info : GraphQLResolveInfo ,
1475
+ itemType : GraphQLOutputType ,
1476
+ path ?: Path ,
1477
+ label ?: string ,
1478
+ ) : void {
1479
+ const subsequentPayloads = this . _subsequentPayloads ;
1480
+ function next ( index : number ) {
1481
+ const fieldPath = addPath ( path , index , undefined ) ;
1482
+ const patchErrors : Array < GraphQLError > = [ ] ;
1483
+ subsequentPayloads . push (
1484
+ iterator . next ( ) . then (
1485
+ ( { value : data , done } ) => {
1486
+ if ( done ) {
1487
+ return { value : undefined , done : true } ;
1488
+ }
1489
+
1490
+ // eslint-disable-next-line node/callback-return
1491
+ next ( index + 1 ) ;
1492
+
1493
+ try {
1494
+ const completedItem = completeValue (
1495
+ exeContext ,
1496
+ itemType ,
1497
+ fieldNodes ,
1498
+ info ,
1499
+ fieldPath ,
1500
+ data ,
1501
+ patchErrors ,
1502
+ ) ;
1503
+
1504
+ if ( isPromise ( completedItem ) ) {
1505
+ return completedItem . then ( ( resolveItem ) => ( {
1506
+ value : createPatchResult (
1507
+ resolveItem ,
1508
+ label ,
1509
+ fieldPath ,
1510
+ patchErrors ,
1511
+ ) ,
1512
+ done : false ,
1513
+ } ) ) ;
1514
+ }
1515
+
1516
+ return {
1517
+ value : createPatchResult (
1518
+ completedItem ,
1519
+ label ,
1520
+ fieldPath ,
1521
+ patchErrors ,
1522
+ ) ,
1523
+ done : false ,
1524
+ } ;
1525
+ } catch ( rawError ) {
1526
+ const error = locatedError (
1527
+ rawError ,
1528
+ fieldNodes ,
1529
+ pathToArray ( fieldPath ) ,
1530
+ ) ;
1531
+ handleFieldError ( error , itemType , patchErrors ) ;
1532
+ return {
1533
+ value : createPatchResult ( null , label , fieldPath , patchErrors ) ,
1534
+ done : false ,
1535
+ } ;
1536
+ }
1537
+ } ,
1538
+ ( rawError ) => {
1539
+ const error = locatedError (
1540
+ rawError ,
1541
+ fieldNodes ,
1542
+ pathToArray ( fieldPath ) ,
1543
+ ) ;
1544
+ handleFieldError ( error , itemType , patchErrors ) ;
1545
+ return {
1546
+ value : createPatchResult ( null , label , fieldPath , patchErrors ) ,
1547
+ done : false ,
1548
+ } ;
1549
+ } ,
1550
+ ) ,
1551
+ ) ;
1552
+ }
1553
+ next ( initialIndex ) ;
1554
+ }
1555
+
1344
1556
_race ( ) : Promise < IteratorResult < ExecutionPatchResult , void > > {
1345
1557
return new Promise < {
1346
1558
promise : Promise < IteratorResult < DispatcherResult , void > > ;
@@ -1360,7 +1572,20 @@ export class Dispatcher {
1360
1572
) ;
1361
1573
return promise ;
1362
1574
} )
1363
- . then ( ( { value } ) => {
1575
+ . then ( ( { value, done } ) => {
1576
+ if ( done && this . _subsequentPayloads . length === 0 ) {
1577
+ // async iterable resolver just finished and no more pending payloads
1578
+ return {
1579
+ value : {
1580
+ hasNext : false ,
1581
+ } ,
1582
+ done : false ,
1583
+ } ;
1584
+ } else if ( done ) {
1585
+ // async iterable resolver just finished but there are pending payloads
1586
+ // return the next one
1587
+ return this . _race ( ) ;
1588
+ }
1364
1589
const returnValue : ExecutionPatchResult = {
1365
1590
...value ,
1366
1591
hasNext : this . _subsequentPayloads . length > 0 ,
@@ -1402,7 +1627,7 @@ export class Dispatcher {
1402
1627
}
1403
1628
1404
1629
function createPatchResult (
1405
- data : ObjMap < unknown > | null ,
1630
+ data : ObjMap < unknown > | unknown | null ,
1406
1631
label ?: string ,
1407
1632
path ?: Path ,
1408
1633
errors ?: ReadonlyArray < GraphQLError > ,
0 commit comments