nodejs / nodejs/userland-migrations
spec: `readable-stream` v2;v3;v4 to `node:stream` v24
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 85
- Forks
- 55
- Avg merge
- 3d 11h
- Merged PRs (30d)
- 9
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:boom
Example 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:boom
Second observed gap:
--- readable-stream v3
+++ node:stream v24
@@
+ERR:readable-err
Example 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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the migration examples in the issue and compare the referenced Node.js stream documentation with the readable-stream README. Separate the v2, v3, and v4 import and timing cases, then verify that the codemod guidance preserves the aligned examples and documents compensating error-handling patterns.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js
- Domain
- documentation, tooling
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100