Fix Duplex.from() for iterables and async generator functions by Feldt · Pull Request #7858 · cloudflare/workerd · GitHub
Skip to content

Fix Duplex.from() for iterables and async generator functions - #7858

Open
Feldt wants to merge 1 commit into
cloudflare:mainfrom
Feldt:fix/duplex-from-iterable
Open

Feldt wants to merge 1 commit into
cloudflare:mainfrom
Feldt:fix/duplex-from-iterable

Conversation

@Feldt

@Feldt Feldt commented Oct 20, 2026

Copy link
Copy Markdown

Duplex.from() throws ERR_INVALID_ARG_TYPE for every input that takes its iterable path: an array, a string, a Set, an async iterable, or an async generator function. stream.compose() calls Duplex.from() on a leading or trailing function stage, so it fails the same way:

import { Duplex, Readable, compose } from 'node:stream';

Duplex.from(['a', 'b']);
// TypeError: The "iterable" argument must be an instance of Iterable. Received function Duplexify

compose(Readable.from(['a']), async function* (source) {
  for await (const chunk of source) yield chunk;
});
// same error

What was wrong. duplexify() in src/node/internal/streams_duplex.js calls Readable.from(Duplexify, value, opts). That is the three-argument signature of the internal from(Readable, iterable, opts) helper in streams_readable.js, but it calls the public Readable.from(iterable, opts) instead, which treats the Duplexify class as the iterable and throws. Node.js calls the internal helper at this point (lib/internal/streams/duplexify.js).

What changed. streams_duplex.js imports the internal from from node-internal:streams_readable (as readableFrom, because the module already exports its own from) and calls it in both places. With that, Duplex.from() over an iterable returns a readable-only objectMode Duplex, and over an async generator function it returns a Duplex that feeds writes into the function and reads what it yields, as in Node.js.

Compatibility. No compatibility flag: these calls always threw before this change, so code that worked before behaves the same.

Tests. Two new cases in src/workerd/api/node/tests/streams-test.js:

  • duplexFromIterable: Duplex.from() over an array, a string (one chunk), a Set and an async generator object.
  • duplexFromAsyncGeneratorFunction: Duplex.from() over an async generator function, and compose() with a trailing function stage.

Without the fix, both fail with the error above in all three variants of streams-test; with it they pass. The same assertions pass on Node.js v26.10.0. Run locally on macOS (arm64):

bazel test //src/workerd/api/node/tests:streams-test@ //src/workerd/api/node/tests:streams-test@all-compat-flags //src/workerd/api/node/tests:streams-test@all-autogates //src/workerd/api/node/tests:streams-test@eslint //src/node:node@eslint
python3 tools/cross/format.py --check files -- src/node/internal/streams_duplex.js src/workerd/api/node/tests/streams-test.js

The other node:stream tests also pass with the change: //src/tests/node/stream/..., streams-nodejs-test and streams-v24-nodejs-test (all variants), fs-readstream-test@ and fs-writestream-test@.

I prepared this PR with an AI coding agent (Claude Code). I am responsible for it and will answer review comments.

🤖 Generated with Claude Code

duplexify() called the public Readable.from(iterable, opts) with the
three-argument signature of the internal from(Readable, iterable, opts)
helper, so it passed the Duplexify class as the iterable and threw
ERR_INVALID_ARG_TYPE. Every Duplex.from() call that takes the iterable
path failed: arrays, strings, Sets, async iterables and async generator
functions. compose() calls Duplex.from() on a leading or trailing
function stage, so it failed the same way. Call the internal helper
instead, as Node.js does.

No compatibility flag: these calls always threw, so code that worked
before behaves the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Feldt
Feldt requested review from a team as code owners October 20, 2026 20:44
@github-actions

Copy link
Copy Markdown

@clanky-the-clanker

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants