nodejs / nodejs/userland-migrations

spec: `readable-stream` v2;v3;v4 to `node:stream` v24

Open
#471 2 comments 0 reactions 0 assignees View on GitHub

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-stream v2, 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-stream v2 and v3 share the same legacy timing behavior in the observed gaps.
  • readable-stream v4 matches node:stream v24 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:stream imports 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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.