@@ -107,16 +107,23 @@ export async function recordResponseDraft(
107107/** Build a Fetch API `Response` from a recorded response body. */
108108export async function buildResponse (
109109 recorded : RecordedResponse | RecordedResponseOrDraft ,
110- ctx ?: { store : CassetteStore ; name : string } ,
110+ ctx ?: {
111+ store : CassetteStore ;
112+ name : string ;
113+ waitForRequestFrames ?: ( count : number ) => Promise < void > ;
114+ } ,
111115) : Promise < Response > {
112116 // Expand \n-joined set-cookie back into multiple header entries.
113117 // Strip encoding headers: stored bytes are already decoded by the fetch
114118 // layer (undici decompresses gzip/deflate before handing the body to us).
115119 // Preserving content-encoding would cause callers to attempt a second
116120 // decode of already-plain bytes, which throws a zlib error.
117121 const headers = expandSetCookieHeader ( stripEncodingHeaders ( recorded . headers ) ) ;
118- const init : ResponseInit = { status : recorded . status , headers } ;
119- if ( recorded . statusText ) init . statusText = recorded . statusText ;
122+ const init : ResponseInit = {
123+ status : recorded . status ,
124+ headers,
125+ ...( recorded . statusText ? { statusText : recorded . statusText } : { } ) ,
126+ } ;
120127 // 1xx/204/304 responses must not have a body, per Fetch spec.
121128 if ( isNullBodyStatus ( recorded . status ) ) return new Response ( null , init ) ;
122129
@@ -146,6 +153,33 @@ export async function buildResponse(
146153 ? recorded . body . bytes
147154 : await decodeBody ( recorded . body , ctx ) ;
148155 if ( bytes . length === 0 ) return new Response ( null , init ) ;
156+ if ( recorded . streamChunks ) {
157+ // Interactive providers wait for client messages between chunks. Frame
158+ // counts preserve that ordering even if replay's client is slower than
159+ // recording's client; recorded delays alone cannot enforce it.
160+ const chunks = recorded . streamChunks ;
161+ let index = 0 ;
162+ let offset = 0 ;
163+ const stream = new ReadableStream ( {
164+ async pull ( controller ) {
165+ if ( index === chunks . length ) {
166+ controller . close ( ) ;
167+ return ;
168+ }
169+ const { size, delayMs, requestFrames } = chunks [ index ++ ] ! ;
170+ if ( requestFrames !== undefined ) {
171+ if ( ! ctx ?. waitForRequestFrames ) {
172+ throw new Error ( "Connect replay requires request frame tracking" ) ;
173+ }
174+ await ctx . waitForRequestFrames ( requestFrames ) ;
175+ }
176+ await new Promise ( ( resolve ) => setTimeout ( resolve , delayMs ) ) ;
177+ controller . enqueue ( bytes . slice ( offset , offset + size ) ) ;
178+ offset += size ;
179+ } ,
180+ } ) ;
181+ return new Response ( stream , init ) ;
182+ }
149183 // Copy into a fresh ArrayBuffer. (TS 5.7 typed Uint8Array as
150184 // Uint8Array<ArrayBufferLike>, and `.buffer` may be `SharedArrayBuffer` —
151185 // neither satisfies the DOM lib's strict ArrayBuffer-only BodyInit.)
0 commit comments