Skip to content

Commit 271d6e3

Browse files
committed
fix(api): stream response body in _httpsRequestFetch rather than buffering
Resolving the promise as soon as headers arrive and constructing the Response with a ReadableStream body, matching fetch() semantics. Previously the response body was fully buffered before the promise resolved, which broke streaming call sites such as streamDownloadWithFetch that pipe response.body to disk expecting a live stream.
1 parent ee86ba4 commit 271d6e3

1 file changed

Lines changed: 35 additions & 20 deletions

File tree

src/utils/api.mts

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -151,29 +151,44 @@ function _httpsRequestFetch(
151151
)
152152
return
153153
}
154-
const chunks: Buffer[] = []
155-
res.on('data', (chunk: Buffer) => chunks.push(chunk))
156-
res.on('end', () => {
157-
const body = Buffer.concat(chunks)
158-
const responseHeaders = new Headers()
159-
for (const [key, value] of Object.entries(res.headers)) {
160-
if (typeof value === 'string') {
161-
responseHeaders.set(key, value)
162-
} else if (Array.isArray(value)) {
163-
for (const v of value) {
164-
responseHeaders.append(key, v)
165-
}
154+
// Build response headers immediately on receipt.
155+
const responseHeaders = new Headers()
156+
for (const [key, value] of Object.entries(res.headers)) {
157+
if (typeof value === 'string') {
158+
responseHeaders.set(key, value)
159+
} else if (Array.isArray(value)) {
160+
for (const v of value) {
161+
responseHeaders.append(key, v)
166162
}
167163
}
168-
resolve(
169-
new Response(body, {
170-
status: statusCode ?? 0,
171-
statusText: res.statusMessage ?? '',
172-
headers: responseHeaders,
173-
}),
174-
)
164+
}
165+
// Resolve with a streaming body as soon as headers are available,
166+
// matching fetch() semantics. Callers that pipe response.body (e.g.
167+
// streamDownloadWithFetch) receive a live ReadableStream rather than
168+
// a fully-buffered Buffer.
169+
const body = new ReadableStream<Uint8Array>({
170+
start(controller) {
171+
res.on('data', (chunk: Buffer) => {
172+
controller.enqueue(chunk)
173+
})
174+
res.on('end', () => {
175+
controller.close()
176+
})
177+
res.on('error', (err: Error) => {
178+
controller.error(err)
179+
})
180+
},
181+
cancel() {
182+
res.destroy()
183+
},
175184
})
176-
res.on('error', reject)
185+
resolve(
186+
new Response(body, {
187+
status: statusCode ?? 0,
188+
statusText: res.statusMessage ?? '',
189+
headers: responseHeaders,
190+
}),
191+
)
177192
},
178193
)
179194
if (init.body) {

0 commit comments

Comments
 (0)