Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/goods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,12 @@ const responseToReadable = (response: Response, rs: Readable) => {
return rs
}
rs._read = async () => {
const result = await reader.read()
rs.push(result.done ? null : Buffer.from(result.value))
try {
const result = await reader.read()
rs.push(result.done ? null : Buffer.from(result.value))
} catch (err) {
rs.destroy(err as Error)
}
}
return rs
}
Expand Down
42 changes: 41 additions & 1 deletion test/goods.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import assert from 'node:assert'
import { test, describe, after } from 'node:test'
import { Duplex } from 'node:stream'
import { Duplex, Readable } from 'node:stream'
import { $, chalk, fs, path, dotenv } from '../src/index.ts'
import {
echo,
Expand Down Expand Up @@ -365,6 +365,46 @@ describe('goods', () => {
assert(p3.includes('GitHub'))
})

test('reader error in _read is caught and destroys stream', async () => {
// responseToReadable (private) assigns an async _read to a Readable.
// This test verifies the behavioral contract: when a web ReadableStream
// reader rejects, the error must surface via the Node.js Readable's
// 'error' event (via rs.destroy) rather than as an unhandled rejection.
const error = new Error('stream read failed')
const webStream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('ok'))
},
pull() {
throw error
},
})
const reader = webStream.getReader()
const rs = new Readable({ read() {} })

rs._read = async () => {
try {
const result = await reader.read()
rs.push(result.done ? null : Buffer.from(result.value))
} catch (err) {
rs.destroy(err as Error)
}
}

// First read should succeed
const firstChunk = await new Promise<Buffer | null>((resolve) => {
rs.once('data', resolve)
})
assert.ok(firstChunk)
assert.equal(firstChunk.toString(), 'ok')

// Second read triggers the error in pull(), which should be caught
const receivedError = await new Promise<Error>((resolve) => {
rs.on('error', resolve)
})
assert.equal(receivedError.message, 'stream read failed')
})

describe('dotenv', () => {
test('parse()', () => {
assert.deepEqual(dotenv.parse(''), {})
Expand Down