@@ -53,6 +53,7 @@ import {
53
53
GraphQLIncludeDirective ,
54
54
GraphQLSkipDirective ,
55
55
GraphQLDeferDirective ,
56
+ GraphQLStreamDirective ,
56
57
} from '../type/directives' ;
57
58
import {
58
59
isObjectType ,
@@ -704,6 +705,42 @@ function getDeferValues(
704
705
} ;
705
706
}
706
707
708
+ /**
709
+ * Returns an object containing the @stream arguments if a field should be
710
+ * streamed based on the experimental flag, stream directive present and
711
+ * not disabled by the "if" argument.
712
+ */
713
+ function getStreamValues (
714
+ exeContext : ExecutionContext ,
715
+ fieldNodes : $ReadOnlyArray < FieldNode > ,
716
+ ): void | { |
717
+ initialCount ? : number ,
718
+ label ? : string ,
719
+ | } {
720
+ // validation only allows equivalent streams on multiple fields, so it is
721
+ // safe to only check the first fieldNode for the stream directive
722
+ const stream = getDirectiveValues (
723
+ GraphQLStreamDirective ,
724
+ fieldNodes [ 0 ] ,
725
+ exeContext . variableValues ,
726
+ ) ;
727
+
728
+ if ( ! stream ) {
729
+ return ;
730
+ }
731
+
732
+ if ( stream . if === false ) {
733
+ return;
734
+ }
735
+
736
+ return {
737
+ initialCount :
738
+ // istanbul ignore next (initialCount is required number argument)
739
+ typeof stream . initialCount === 'number' ? stream . initialCount : undefined ,
740
+ label : typeof stream . label === 'string' ? stream . label : undefined ,
741
+ } ;
742
+ }
743
+
707
744
/**
708
745
* Determines if a fragment is applicable to the given type.
709
746
*/
@@ -996,6 +1033,7 @@ function completeAsyncIteratorValue(
996
1033
errors : Array < GraphQLError > ,
997
1034
) : Promise < $ReadOnlyArray < mixed >> {
998
1035
let containsPromise = false ;
1036
+ const stream = getStreamValues ( exeContext , fieldNodes ) ;
999
1037
return new Promise ( ( resolve ) => {
1000
1038
function next ( index , completedResults ) {
1001
1039
const fieldPath = addPath ( path , index , undefined ) ;
@@ -1032,7 +1070,26 @@ function completeAsyncIteratorValue(
1032
1070
return ;
1033
1071
}
1034
1072
1035
- next ( index + 1 , completedResults ) ;
1073
+ const newIndex = index + 1 ;
1074
+ if (
1075
+ stream &&
1076
+ typeof stream . initialCount === 'number' &&
1077
+ newIndex >= stream . initialCount
1078
+ ) {
1079
+ exeContext . dispatcher . addAsyncIteratorValue (
1080
+ stream . label ,
1081
+ newIndex ,
1082
+ path ,
1083
+ iterator ,
1084
+ exeContext ,
1085
+ fieldNodes ,
1086
+ info ,
1087
+ itemType ,
1088
+ ) ;
1089
+ resolve ( completedResults ) ;
1090
+ return ;
1091
+ }
1092
+ next ( newIndex , completedResults ) ;
1036
1093
} ,
1037
1094
( rawError ) => {
1038
1095
completedResults . push ( null ) ;
@@ -1087,6 +1144,8 @@ function completeListValue(
1087
1144
) ;
1088
1145
}
1089
1146
1147
+ const stream = getStreamValues ( exeContext , fieldNodes ) ;
1148
+
1090
1149
// This is specified as a simple map, however we're optimizing the path
1091
1150
// where the list contains no Promises by avoiding creating another Promise.
1092
1151
let containsPromise = false ;
@@ -1096,6 +1155,23 @@ function completeListValue(
1096
1155
const itemPath = addPath ( path , index , undefined ) ;
1097
1156
try {
1098
1157
let completedItem ;
1158
+
1159
+ if (
1160
+ stream &&
1161
+ typeof stream . initialCount === 'number' &&
1162
+ index >= stream . initialCount
1163
+ ) {
1164
+ exeContext . dispatcher . addValue (
1165
+ stream . label ,
1166
+ itemPath ,
1167
+ item ,
1168
+ exeContext ,
1169
+ fieldNodes ,
1170
+ info ,
1171
+ itemType ,
1172
+ ) ;
1173
+ return ;
1174
+ }
1099
1175
if ( isPromise ( item ) ) {
1100
1176
completedItem = item . then ( ( resolved ) =>
1101
1177
completeValue (
@@ -1138,7 +1214,7 @@ function completeListValue(
1138
1214
const error = locatedError ( rawError , fieldNodes , pathToArray ( itemPath ) ) ;
1139
1215
return handleFieldError ( error , itemType , errors ) ;
1140
1216
}
1141
- } ) ;
1217
+ } ) . filter ( ( val ) => val !== undefined ) ;
1142
1218
1143
1219
return containsPromise ? Promise . all ( completedResults ) : completedResults ;
1144
1220
}
@@ -1554,6 +1630,129 @@ export class Dispatcher {
1554
1630
) ;
1555
1631
}
1556
1632
1633
+ addValue (
1634
+ label ? : string ,
1635
+ path : Path ,
1636
+ promiseOrData : PromiseOrValue < ObjMap < mixed > | mixed > ,
1637
+ exeContext : ExecutionContext ,
1638
+ fieldNodes : $ReadOnlyArray < FieldNode > ,
1639
+ info : GraphQLResolveInfo ,
1640
+ itemType : GraphQLOutputType ,
1641
+ ) : void {
1642
+ const errors = [ ] ;
1643
+ this . _subsequentPayloads . push (
1644
+ Promise . resolve ( promiseOrData )
1645
+ . then ( ( resolved ) =>
1646
+ completeValue (
1647
+ exeContext ,
1648
+ itemType ,
1649
+ fieldNodes ,
1650
+ info ,
1651
+ path ,
1652
+ resolved ,
1653
+ errors ,
1654
+ ) ,
1655
+ )
1656
+ // Note: we don't rely on a `catch` method, but we do expect "thenable"
1657
+ // to take a second callback for the error case.
1658
+ . then ( undefined , ( rawError ) => {
1659
+ const error = locatedError ( rawError , fieldNodes , pathToArray ( path ) ) ;
1660
+ return handleFieldError ( error , itemType , errors ) ;
1661
+ } )
1662
+ . then ( ( data ) => ( {
1663
+ value : createPatchResult ( data , label , path , errors ) ,
1664
+ done : false ,
1665
+ } ) ) ,
1666
+ ) ;
1667
+ }
1668
+
1669
+ addAsyncIteratorValue (
1670
+ label ? : string ,
1671
+ initialIndex : number ,
1672
+ path ? : Path ,
1673
+ iterator : AsyncIterator < mixed > ,
1674
+ exeContext : ExecutionContext ,
1675
+ fieldNodes : $ReadOnlyArray < FieldNode > ,
1676
+ info : GraphQLResolveInfo ,
1677
+ itemType : GraphQLOutputType ,
1678
+ ) : void {
1679
+ const subsequentPayloads = this . _subsequentPayloads ;
1680
+ function next ( index ) {
1681
+ const fieldPath = addPath ( path , index ) ;
1682
+ const patchErrors = [ ] ;
1683
+ subsequentPayloads . push (
1684
+ iterator . next ( ) . then (
1685
+ ( { value : data , done } ) => {
1686
+ if ( done ) {
1687
+ return { value : undefined , done : true } ;
1688
+ }
1689
+
1690
+ // eslint-disable-next-line node/callback-return
1691
+ next ( index + 1 ) ;
1692
+
1693
+ try {
1694
+ const completedItem = completeValue (
1695
+ exeContext ,
1696
+ itemType ,
1697
+ fieldNodes ,
1698
+ info ,
1699
+ fieldPath ,
1700
+ data ,
1701
+ patchErrors ,
1702
+ ) ;
1703
+
1704
+ if ( isPromise ( completedItem ) ) {
1705
+ return completedItem . then ( ( resolveItem ) => ( {
1706
+ value : createPatchResult (
1707
+ resolveItem ,
1708
+ label ,
1709
+ fieldPath ,
1710
+ patchErrors ,
1711
+ ) ,
1712
+ done : false ,
1713
+ } ) ) ;
1714
+ }
1715
+
1716
+ return {
1717
+ value : createPatchResult (
1718
+ completedItem ,
1719
+ label ,
1720
+ fieldPath ,
1721
+ patchErrors ,
1722
+ ) ,
1723
+ done : false ,
1724
+ } ;
1725
+ } catch ( rawError ) {
1726
+ const error = locatedError (
1727
+ rawError ,
1728
+ fieldNodes ,
1729
+ pathToArray ( fieldPath ) ,
1730
+ ) ;
1731
+ handleFieldError ( error , itemType , patchErrors ) ;
1732
+ return {
1733
+ value : createPatchResult ( null , label , fieldPath , patchErrors ) ,
1734
+ done : false ,
1735
+ } ;
1736
+ }
1737
+ } ,
1738
+ ( rawError ) => {
1739
+ const error = locatedError (
1740
+ rawError ,
1741
+ fieldNodes ,
1742
+ pathToArray ( fieldPath ) ,
1743
+ ) ;
1744
+ handleFieldError ( error , itemType , patchErrors ) ;
1745
+ return {
1746
+ value : createPatchResult ( null , label , fieldPath , patchErrors ) ,
1747
+ done : false ,
1748
+ } ;
1749
+ } ,
1750
+ ) ,
1751
+ ) ;
1752
+ }
1753
+ next ( initialIndex ) ;
1754
+ }
1755
+
1557
1756
_race ( ) : Promise < IteratorResult < ExecutionPatchResult , void >> {
1558
1757
return new Promise ( ( resolve ) => {
1559
1758
this . _subsequentPayloads . forEach ( ( promise ) => {
@@ -1570,7 +1769,20 @@ export class Dispatcher {
1570
1769
) ;
1571
1770
return promise ;
1572
1771
} )
1573
- . then ( ( { value } ) => {
1772
+ . then ( ( { value, done } ) => {
1773
+ if ( done && this . _subsequentPayloads . length === 0 ) {
1774
+ // async iterable resolver just finished and no more pending payloads
1775
+ return {
1776
+ value : {
1777
+ hasNext : false ,
1778
+ } ,
1779
+ done : false ,
1780
+ } ;
1781
+ } else if ( done ) {
1782
+ // async iterable resolver just finished but there are pending payloads
1783
+ // return the next one
1784
+ return this . _race ( ) ;
1785
+ }
1574
1786
const returnValue : ExecutionPatchResult = {
1575
1787
...value ,
1576
1788
hasNext : this . _subsequentPayloads . length > 0 ,
0 commit comments