Repository navigation
spec: readable-stream v2;v3;v4 to node:stream v24 #471
Copy link
Copy link
Open
Description
Description
We should document and codemod the migration from readable-stream v2/v3/v4 to node:stream v24.
- The migration should be divided by
readable-streamv2, v3, and v4. - The migration should separate import replacement from runtime behavior differences.
- The migration should call out observed output gaps so version-specific behavior is explicit.
- The migration should keep the stream wiring and example structure aligned across all baselines.
- The migration should include compensating patterns for code that depends on legacy timing behavior.
Important points
readable-streamv2 and v3 share the same legacy timing behavior in the observed gaps.readable-streamv4 matchesnode:streamv24 for the observed gaps.- Most cases are a straight source-import migration, but a few cases need behavior notes because the output differs at runtime.
- The codemod should prefer built-in
node:streamimports for Node.js-only code. - When runtime timing changes matter, the issue should show a safer migration pattern that avoids the late error.
Examples
readable-stream v2 -> node:stream v24
- import { Readable } from "readable-stream";
+ import { Readable } from "node:stream";Observed runtime gap:
--- readable-stream v2
+++ node:stream v24
@@
a
+ERR:boomExample of potential migration:
const { Readable } = require("readable-stream");
const readable = Readable.from(["a"]);
-readable.destroy(new Error("boom"));
+readable.once("error", (error) => {
+ console.error(error.message);
+});
+
+setImmediate(() => {
+ readable.destroy(new Error("boom"));
+});Example of safer migration for post-data error handling:
const { Readable } = require("readable-stream");
const readable = Readable.from(["a"]);
-readable.on("data", (chunk) => {
- console.log(chunk.toString());
-});
-
-readable.on("error", (error) => {
- console.error(error.message);
-});
+readable.once("error", (error) => {
+ console.error(error.message);
+});
+
+readable.on("data", (chunk) => {
+ console.log(chunk.toString());
+});readable-stream v3 -> node:stream v24
- import { Readable, Transform, Writable, pipeline } from "readable-stream";
+ import { Readable, Transform, Writable, pipeline } from "node:stream";Observed runtime gap:
--- readable-stream v3
+++ node:stream v24
@@
a
+ERR:boomSecond observed gap:
--- readable-stream v3
+++ node:stream v24
@@
+ERR:readable-errExample of potential migration for late destroy timing:
const { Readable } = require("readable-stream");
const readable = Readable.from(["a"]);
-readable.destroy(new Error("boom"));
+readable.once("error", (error) => {
+ process.stderr.write(`${error.message}\n`);
+});
+
+queueMicrotask(() => {
+ readable.destroy(new Error("boom"));
+});Example of potential migration for post-data error handling:
const { Readable } = require("readable-stream");
const readable = Readable.from(["a"]);
-readable.on("data", (chunk) => {
- console.log(chunk.toString());
-});
+readable.once("error", (error) => {
+ process.stderr.write(`${error.message}\n`);
+});
+
+readable.on("data", (chunk) => {
+ console.log(chunk.toString());
+});readable-stream v4 -> node:stream v24
- import * as stream from "readable-stream";
+ import * as stream from "node:stream";Observed runtime behavior:
--- readable-stream v4
+++ node:stream v24
@@
(no changes)Example of the same migration pattern with no compensation needed:
const stream = require("readable-stream");
const readable = stream.Readable.from(["a"]);
-readable.on("data", console.log);
+readable.on("data", console.log);Caveats
- These are runtime timing differences, not API surface differences.
- The migration is usually a simple import swap, but the version split matters when code depends on late
destroy()behavior or errors emitted after the first data event. - v4 aligns with Node core on the observed cases, while v2 and v3 preserve the older behavior.
- If the code relies on the legacy timing, add explicit error listeners before the operation that may fail.
- If the code only needs the stream output and not the timing artifact, prefer the Node core behavior and remove the dependency on the old ordering.
Refs
- Node.js stream documentation: https://nodejs.org/api/stream.html
readable-streamreadme: https://github.com/nodejs/readable-stream/blob/main/README.mdreadable-streamdownload chart: https://npmx.dev/package/readable-stream?modal=versions&lowUsage=true
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Type
Projects
- StatusShow more project fields📋 Backlog
Activity
AugustinMauroy commentedon May 22, 2026
cc @nodejs/streams
BTW for now it's just first part of the spec I'm gonna add some future test cases in the issue
mcollina commentedon Jun 2, 2026
I don’t understand what are the key differences between v1, v2, v3 and what we have on nodejs/node. There are more than this.