@@ -2117,6 +2117,28 @@ describe('Execute: stream directive (legacy)', () => {
21172117 ] ) ;
21182118 } ) ;
21192119 it ( 'Handles overlapping deferred and non-deferred streams' , async ( ) => {
2120+ // NOTE: This is an *invalid* query, but it should be an *executable* query.
2121+ // Validation rejects the overlapping @stream selections. See
2122+ // https://github.com/graphql/defer-stream-wg/discussions/100.
2123+ //
2124+ // The query selects the same stream field twice. The non-deferred selection
2125+ // asks for `id`. The deferred stream selection asks for `id`, `name`, and
2126+ // an inner deferred `innerName`.
2127+ //
2128+ // Stream item completion later sees one merged stream. Without rewriting
2129+ // the stream field details to clear inherited defer usage, `name` would be
2130+ // treated as still belonging to the outer @defer instead of to the stream
2131+ // item. The bad stream payload would be `items: [{ id: '1' }]` and then
2132+ // `items: [{ id: '2' }]`; the `name` fields would be dropped even though
2133+ // the `innerName` payloads would still be emitted.
2134+ //
2135+ // `getStreamUsage` avoids that by rewriting the stream field details to
2136+ // clear inherited defer usage before those details are reused to complete
2137+ // stream items.
2138+ //
2139+ // The inner @defer is included only because it forces stream item
2140+ // completion through execution planning. Without it, a fast path executes
2141+ // all collected fields directly and this bug would not be exposed.
21202142 const document = parse ( `
21212143 query {
21222144 nestedObject {
@@ -2129,6 +2151,9 @@ describe('Execute: stream directive (legacy)', () => {
21292151 nestedFriendList @stream(initialCount: 0) {
21302152 id
21312153 name
2154+ ... @defer {
2155+ innerName: name
2156+ }
21322157 }
21332158 }
21342159 }
@@ -2176,6 +2201,10 @@ describe('Execute: stream directive (legacy)', () => {
21762201 items : [ { id : '2' } ] ,
21772202 path : [ 'nestedObject' , 'nestedFriendList' , 1 ] ,
21782203 } ,
2204+ {
2205+ data : { innerName : 'Luke' } ,
2206+ path : [ 'nestedObject' , 'nestedFriendList' , 0 ] ,
2207+ } ,
21792208 ] ,
21802209 hasNext : true ,
21812210 } ,
@@ -2185,6 +2214,10 @@ describe('Execute: stream directive (legacy)', () => {
21852214 items : [ { id : '2' , name : 'Han' } ] ,
21862215 path : [ 'nestedObject' , 'nestedFriendList' , 1 ] ,
21872216 } ,
2217+ {
2218+ data : { innerName : 'Han' } ,
2219+ path : [ 'nestedObject' , 'nestedFriendList' , 1 ] ,
2220+ } ,
21882221 ] ,
21892222 hasNext : false ,
21902223 } ,
@@ -2193,6 +2226,9 @@ describe('Execute: stream directive (legacy)', () => {
21932226 it ( 'Re-promotes a completed stream when a slower sibling defer resolves later' , async ( ) => {
21942227 const { promise : slowFieldPromise , resolve : resolveSlowField } =
21952228 promiseWithResolvers < string > ( ) ;
2229+ // NOTE: This is an *invalid* query, but it should be an *executable* query.
2230+ // Validation rejects the overlapping @stream selections. See
2231+ // https://github.com/graphql/defer-stream-wg/discussions/100.
21962232 const document = parse ( `
21972233 query {
21982234 nestedObject {
@@ -2397,7 +2433,7 @@ describe('Execute: stream directive (legacy)', () => {
23972433 const document = parse ( `
23982434 query {
23992435 friendList @stream(label:"stream-label") {
2400- ...NameFragment @defer(label: "DeferName") @defer(label: "DeferName")
2436+ ...NameFragment @defer(label: "DeferName")
24012437 id
24022438 }
24032439 }
@@ -2500,7 +2536,7 @@ describe('Execute: stream directive (legacy)', () => {
25002536 const document = parse ( `
25012537 query {
25022538 friendList @stream(initialCount: 1, label:"stream-label") {
2503- ...NameFragment @defer(label: "DeferName") @defer(label: "DeferName")
2539+ ...NameFragment @defer(label: "DeferName")
25042540 id
25052541 }
25062542 }
0 commit comments