gongxiangp2/lib/node-readable-stream.ts
2026-09-14 16:45:42 +08:00

68 lines
1.7 KiB
TypeScript

import { Readable } from 'node:stream'
export interface NodeReadableToWebStreamOptions {
onCancel?: (reason?: unknown) => void | Promise<void>
onComplete?: () => void
}
/**
* 将 Node.js Readable 转换为受 Web Stream 消费速度约束的流。
*
* 每次 pull 只向下游交付一个分块,避免 data 事件持续读取大文件、
* 而浏览器或代理来不及接收时在 Node 进程内堆积大量内存。
*/
export function nodeReadableToWebStream(
source: Readable,
options: NodeReadableToWebStreamOptions = {},
): ReadableStream<Uint8Array> {
const iterator = source[Symbol.asyncIterator]()
let finished = false
return new ReadableStream<Uint8Array>(
{
async pull(controller) {
if (finished) {
return
}
try {
const next = await iterator.next()
if (next.done) {
finished = true
controller.close()
try {
options.onComplete?.()
} catch (error) {
console.error('[node-readable-stream] completion callback failed:', error)
}
return
}
const chunk = next.value
controller.enqueue(chunk instanceof Uint8Array ? chunk : Buffer.from(chunk))
} catch (error) {
finished = true
if (!source.destroyed) {
source.destroy()
}
controller.error(error)
}
},
async cancel(reason) {
finished = true
try {
await options.onCancel?.(reason)
} finally {
if (!source.destroyed) {
source.destroy()
}
}
},
},
{
highWaterMark: 1,
size: () => 1,
},
)
}