@@ -44,6 +44,7 @@ import {
44
44
TypeMetaFieldDef ,
45
45
TypeNameMetaFieldDef ,
46
46
} from '../type/introspection' ;
47
+ import { GraphQLStreamDirective } from '../type/directives' ;
47
48
import {
48
49
isObjectType ,
49
50
isAbstractType ,
@@ -54,7 +55,11 @@ import {
54
55
55
56
import { getOperationRootType } from '../utilities/getOperationRootType' ;
56
57
57
- import { getVariableValues , getArgumentValues } from './values' ;
58
+ import {
59
+ getVariableValues ,
60
+ getArgumentValues ,
61
+ getDirectiveValues ,
62
+ } from './values' ;
58
63
import type { FieldsAndPatches , PatchFields } from './collectFields' ;
59
64
import { collectFields } from './collectFields' ;
60
65
@@ -136,7 +141,7 @@ export interface FormattedExecutionResult<
136
141
* - `extensions` is reserved for adding non-standard properties.
137
142
*/
138
143
export interface ExecutionPatchResult <
139
- TData = ObjMap < unknown > ,
144
+ TData = ObjMap < unknown > | unknown ,
140
145
TExtensions = ObjMap < unknown > ,
141
146
> {
142
147
errors ?: ReadonlyArray < GraphQLError > ;
@@ -148,7 +153,7 @@ export interface ExecutionPatchResult<
148
153
}
149
154
150
155
export interface FormattedExecutionPatchResult <
151
- TData = ObjMap < unknown > ,
156
+ TData = ObjMap < unknown > | unknown ,
152
157
TExtensions = ObjMap < unknown > ,
153
158
> {
154
159
errors ?: ReadonlyArray < GraphQLFormattedError > ;
@@ -762,6 +767,44 @@ function completeValue(
762
767
) ;
763
768
}
764
769
770
+ /**
771
+ * Returns an object containing the `@stream` arguments if a field should be
772
+ * streamed based on the experimental flag, stream directive present and
773
+ * not disabled by the "if" argument.
774
+ */
775
+ function getStreamValues (
776
+ exeContext : ExecutionContext ,
777
+ fieldNodes : ReadonlyArray < FieldNode > ,
778
+ ) :
779
+ | undefined
780
+ | {
781
+ initialCount ?: number ;
782
+ label ?: string ;
783
+ } {
784
+ // validation only allows equivalent streams on multiple fields, so it is
785
+ // safe to only check the first fieldNode for the stream directive
786
+ const stream = getDirectiveValues (
787
+ GraphQLStreamDirective ,
788
+ fieldNodes [ 0 ] ,
789
+ exeContext . variableValues ,
790
+ ) ;
791
+
792
+ if ( ! stream ) {
793
+ return ;
794
+ }
795
+
796
+ if ( stream . if === false ) {
797
+ return ;
798
+ }
799
+
800
+ return {
801
+ initialCount :
802
+ // istanbul ignore next (initialCount is required number argument)
803
+ typeof stream . initialCount === 'number' ? stream . initialCount : undefined ,
804
+ label : typeof stream . label === 'string' ? stream . label : undefined ,
805
+ } ;
806
+ }
807
+
765
808
/**
766
809
* Complete a async iterator value by completing the result and calling
767
810
* recursively until all the results are completed.
@@ -776,8 +819,28 @@ function completeAsyncIteratorValue(
776
819
errors : Array < GraphQLError > ,
777
820
) : Promise < ReadonlyArray < unknown > > {
778
821
let containsPromise = false ;
822
+ const stream = getStreamValues ( exeContext , fieldNodes ) ;
779
823
return new Promise < ReadonlyArray < unknown > > ( ( resolve ) => {
780
824
function next ( index : number , completedResults : Array < unknown > ) {
825
+ if (
826
+ stream &&
827
+ typeof stream . initialCount === 'number' &&
828
+ index >= stream . initialCount
829
+ ) {
830
+ exeContext . dispatcher . addAsyncIteratorValue (
831
+ index ,
832
+ iterator ,
833
+ exeContext ,
834
+ fieldNodes ,
835
+ info ,
836
+ itemType ,
837
+ path ,
838
+ stream . label ,
839
+ ) ;
840
+ resolve ( completedResults ) ;
841
+ return ;
842
+ }
843
+
781
844
const fieldPath = addPath ( path , index , undefined ) ;
782
845
iterator . next ( ) . then (
783
846
( { value, done } ) => {
@@ -866,15 +929,37 @@ function completeListValue(
866
929
) ;
867
930
}
868
931
932
+ const stream = getStreamValues ( exeContext , fieldNodes ) ;
933
+
869
934
// This is specified as a simple map, however we're optimizing the path
870
935
// where the list contains no Promises by avoiding creating another Promise.
871
936
let containsPromise = false ;
872
- const completedResults = Array . from ( result , ( item , index ) => {
937
+ const completedResults = [ ] ;
938
+ let index = 0 ;
939
+ for ( const item of result ) {
873
940
// No need to modify the info object containing the path,
874
941
// since from here on it is not ever accessed by resolver functions.
875
942
const itemPath = addPath ( path , index , undefined ) ;
876
943
try {
877
944
let completedItem ;
945
+
946
+ if (
947
+ stream &&
948
+ typeof stream . initialCount === 'number' &&
949
+ index >= stream . initialCount
950
+ ) {
951
+ exeContext . dispatcher . addValue (
952
+ itemPath ,
953
+ item ,
954
+ exeContext ,
955
+ fieldNodes ,
956
+ info ,
957
+ itemType ,
958
+ stream . label ,
959
+ ) ;
960
+ index ++ ;
961
+ continue ;
962
+ }
878
963
if ( isPromise ( item ) ) {
879
964
completedItem = item . then ( ( resolved ) =>
880
965
completeValue (
@@ -903,21 +988,25 @@ function completeListValue(
903
988
containsPromise = true ;
904
989
// Note: we don't rely on a `catch` method, but we do expect "thenable"
905
990
// to take a second callback for the error case.
906
- return completedItem . then ( undefined , ( rawError ) => {
907
- const error = locatedError (
908
- rawError ,
909
- fieldNodes ,
910
- pathToArray ( itemPath ) ,
911
- ) ;
912
- return handleFieldError ( error , itemType , errors ) ;
913
- } ) ;
991
+ completedResults . push (
992
+ completedItem . then ( undefined , ( rawError ) => {
993
+ const error = locatedError (
994
+ rawError ,
995
+ fieldNodes ,
996
+ pathToArray ( itemPath ) ,
997
+ ) ;
998
+ return handleFieldError ( error , itemType , errors ) ;
999
+ } ) ,
1000
+ ) ;
1001
+ } else {
1002
+ completedResults . push ( completedItem ) ;
914
1003
}
915
- return completedItem ;
916
1004
} catch ( rawError ) {
917
1005
const error = locatedError ( rawError , fieldNodes , pathToArray ( itemPath ) ) ;
918
- return handleFieldError ( error , itemType , errors ) ;
1006
+ completedResults . push ( handleFieldError ( error , itemType , errors ) ) ;
919
1007
}
920
- } ) ;
1008
+ index ++ ;
1009
+ }
921
1010
922
1011
return containsPromise ? Promise . all ( completedResults ) : completedResults ;
923
1012
}
@@ -1295,7 +1384,7 @@ export function getFieldDef(
1295
1384
*/
1296
1385
interface DispatcherResult {
1297
1386
errors ?: ReadonlyArray < GraphQLError > ;
1298
- data ?: ObjMap < unknown > | null ;
1387
+ data ?: ObjMap < unknown > | unknown | null ;
1299
1388
path : ReadonlyArray < string | number > ;
1300
1389
label ?: string ;
1301
1390
extensions ?: ObjMap < unknown > ;
@@ -1335,6 +1424,129 @@ export class Dispatcher {
1335
1424
) ;
1336
1425
}
1337
1426
1427
+ addValue (
1428
+ path : Path ,
1429
+ promiseOrData : PromiseOrValue < unknown > ,
1430
+ exeContext : ExecutionContext ,
1431
+ fieldNodes : ReadonlyArray < FieldNode > ,
1432
+ info : GraphQLResolveInfo ,
1433
+ itemType : GraphQLOutputType ,
1434
+ label ?: string ,
1435
+ ) : void {
1436
+ const errors : Array < GraphQLError > = [ ] ;
1437
+ this . _subsequentPayloads . push (
1438
+ Promise . resolve ( promiseOrData )
1439
+ . then ( ( resolved ) =>
1440
+ completeValue (
1441
+ exeContext ,
1442
+ itemType ,
1443
+ fieldNodes ,
1444
+ info ,
1445
+ path ,
1446
+ resolved ,
1447
+ errors ,
1448
+ ) ,
1449
+ )
1450
+ // Note: we don't rely on a `catch` method, but we do expect "thenable"
1451
+ // to take a second callback for the error case.
1452
+ . then ( undefined , ( rawError ) => {
1453
+ const error = locatedError ( rawError , fieldNodes , pathToArray ( path ) ) ;
1454
+ return handleFieldError ( error , itemType , errors ) ;
1455
+ } )
1456
+ . then ( ( data ) => ( {
1457
+ value : createPatchResult ( data , label , path , errors ) ,
1458
+ done : false ,
1459
+ } ) ) ,
1460
+ ) ;
1461
+ }
1462
+
1463
+ addAsyncIteratorValue (
1464
+ initialIndex : number ,
1465
+ iterator : AsyncIterator < unknown > ,
1466
+ exeContext : ExecutionContext ,
1467
+ fieldNodes : ReadonlyArray < FieldNode > ,
1468
+ info : GraphQLResolveInfo ,
1469
+ itemType : GraphQLOutputType ,
1470
+ path ?: Path ,
1471
+ label ?: string ,
1472
+ ) : void {
1473
+ const subsequentPayloads = this . _subsequentPayloads ;
1474
+ function next ( index : number ) {
1475
+ const fieldPath = addPath ( path , index , undefined ) ;
1476
+ const patchErrors : Array < GraphQLError > = [ ] ;
1477
+ subsequentPayloads . push (
1478
+ iterator . next ( ) . then (
1479
+ ( { value : data , done } ) => {
1480
+ if ( done ) {
1481
+ return { value : undefined , done : true } ;
1482
+ }
1483
+
1484
+ // eslint-disable-next-line node/callback-return
1485
+ next ( index + 1 ) ;
1486
+
1487
+ try {
1488
+ const completedItem = completeValue (
1489
+ exeContext ,
1490
+ itemType ,
1491
+ fieldNodes ,
1492
+ info ,
1493
+ fieldPath ,
1494
+ data ,
1495
+ patchErrors ,
1496
+ ) ;
1497
+
1498
+ if ( isPromise ( completedItem ) ) {
1499
+ return completedItem . then ( ( resolveItem ) => ( {
1500
+ value : createPatchResult (
1501
+ resolveItem ,
1502
+ label ,
1503
+ fieldPath ,
1504
+ patchErrors ,
1505
+ ) ,
1506
+ done : false ,
1507
+ } ) ) ;
1508
+ }
1509
+
1510
+ return {
1511
+ value : createPatchResult (
1512
+ completedItem ,
1513
+ label ,
1514
+ fieldPath ,
1515
+ patchErrors ,
1516
+ ) ,
1517
+ done : false ,
1518
+ } ;
1519
+ } catch ( rawError ) {
1520
+ const error = locatedError (
1521
+ rawError ,
1522
+ fieldNodes ,
1523
+ pathToArray ( fieldPath ) ,
1524
+ ) ;
1525
+ handleFieldError ( error , itemType , patchErrors ) ;
1526
+ return {
1527
+ value : createPatchResult ( null , label , fieldPath , patchErrors ) ,
1528
+ done : false ,
1529
+ } ;
1530
+ }
1531
+ } ,
1532
+ ( rawError ) => {
1533
+ const error = locatedError (
1534
+ rawError ,
1535
+ fieldNodes ,
1536
+ pathToArray ( fieldPath ) ,
1537
+ ) ;
1538
+ handleFieldError ( error , itemType , patchErrors ) ;
1539
+ return {
1540
+ value : createPatchResult ( null , label , fieldPath , patchErrors ) ,
1541
+ done : false ,
1542
+ } ;
1543
+ } ,
1544
+ ) ,
1545
+ ) ;
1546
+ }
1547
+ next ( initialIndex ) ;
1548
+ }
1549
+
1338
1550
_race ( ) : Promise < IteratorResult < ExecutionPatchResult , void > > {
1339
1551
return new Promise < {
1340
1552
promise : Promise < IteratorResult < DispatcherResult , void > > ;
@@ -1354,7 +1566,20 @@ export class Dispatcher {
1354
1566
) ;
1355
1567
return promise ;
1356
1568
} )
1357
- . then ( ( { value } ) => {
1569
+ . then ( ( { value, done } ) => {
1570
+ if ( done && this . _subsequentPayloads . length === 0 ) {
1571
+ // async iterable resolver just finished and no more pending payloads
1572
+ return {
1573
+ value : {
1574
+ hasNext : false ,
1575
+ } ,
1576
+ done : false ,
1577
+ } ;
1578
+ } else if ( done ) {
1579
+ // async iterable resolver just finished but there are pending payloads
1580
+ // return the next one
1581
+ return this . _race ( ) ;
1582
+ }
1358
1583
const returnValue : ExecutionPatchResult = {
1359
1584
...value ,
1360
1585
hasNext : this . _subsequentPayloads . length > 0 ,
@@ -1396,7 +1621,7 @@ export class Dispatcher {
1396
1621
}
1397
1622
1398
1623
function createPatchResult (
1399
- data : ObjMap < unknown > | null ,
1624
+ data : ObjMap < unknown > | unknown | null ,
1400
1625
label ?: string ,
1401
1626
path ?: Path ,
1402
1627
errors ?: ReadonlyArray < GraphQLError > ,
0 commit comments