You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
class X extends Stream (the bare node:stream base) throws 'is not a constructor' — #10649 covers only Readable/Writable/Duplex/Transform; blocks nodemailer #10798
class X extends Stream — the bare node:stream base, not one of its subclasses — throws TypeError: is not a constructor. This blocks nodemailer, where class XOAuth2 extends Stream is loaded unconditionally on import.
Found by the package-compilability probe on origin/main (v0.5.1617), nodemailer 9.0.3.
Why it is a gap rather than an oversight
PR #10649 fixed bound-export heritage dispatch for Readable, Writable, Duplex and Transform — its runtime arm matches exactly those four names:
Stream (the legacy base class that Readable and friends themselves derive from) is not in that list, so it falls through to the ordinary-call dispatch and never installs. This was predicted from reading #10649's match list against a static scan of nodemailer's heritage sites, and the probe confirmed it.
It is a small surface — one real site across the packages scanned — but it is on nodemailer's import path, so it is fatal rather than partial: the package cannot be loaded at all.
Relationship to the other two stream-heritage gaps
There are now three distinct holes in native stream subclassing, worth tracking together even if fixed separately:
#10745 — deeper, HIR does not recognise it as a native parent at all
Stream may be the cheapest of the three: unlike PassThrough (where the hidden _transform field is never pre-seeded, so adding a dispatch arm alone provably changes nothing), Stream is a real constructor with a real prototype, so it may only need adding to #10649's match list plus whatever js_node_stream_*_subclass_init equivalent applies. That should be verified rather than assumed — #10745 is precisely the case where the obvious one-line fix was confirmed not to work.
Reproduction
const{ Stream }=require('stream');classTapextendsStream{}newTap();// TypeError: is not a constructor
Also reachable via import nodemailer from "nodemailer" with the binding removed — it throws on import, before any API is called.
Fixed on main. PR #10805 landed — verified by content: js_node_stream_legacy_subclass_init exists in node_stream_constructors/builders.rs, wired into js_fetch_or_value_super's dynamic dispatch with a two-hop class-id chain so instanceof Stream holds only for genuine subclasses.
Two things worth recording for anyone reading this later:
The first attempt was provably inert. Reusing js_event_emitter_subclass_init unchanged fired the code path (confirmed with a diagnostic) but changed nothing observable — .on/.emit already worked via a pre-existing parent_static.rs registration, and that function never carried pipe. Same trap as #10745, caught by verifying end-to-end rather than trusting a plausible fix.
The crash reported in this issue did not reproduce.extends Stream worked on pristine main for every shape tested, including nodemailer's real XOAuth2 path. The TypeError: is not a constructor came from a different cause entirely — now filed as #10806. The Stream gap was real and is fixed; the symptom that led us here was not it.
#10745 (PassThrough) confirmed not fixed by this change, by control test.
Activity
fix(runtime): dispatch node:stream super() through the legacy Stream …
proggeramlug commentedon Sep 21, 2026
Fixed on main. PR #10805 landed — verified by content:
js_node_stream_legacy_subclass_initexists innode_stream_constructors/builders.rs, wired intojs_fetch_or_value_super's dynamic dispatch with a two-hop class-id chain soinstanceof Streamholds only for genuine subclasses.Two things worth recording for anyone reading this later:
The first attempt was provably inert. Reusing
js_event_emitter_subclass_initunchanged fired the code path (confirmed with a diagnostic) but changed nothing observable —.on/.emitalready worked via a pre-existingparent_static.rsregistration, and that function never carriedpipe. Same trap as #10745, caught by verifying end-to-end rather than trusting a plausible fix.The crash reported in this issue did not reproduce.
extends Streamworked on pristine main for every shape tested, including nodemailer's realXOAuth2path. TheTypeError: is not a constructorcame from a different cause entirely — now filed as #10806. TheStreamgap was real and is fixed; the symptom that led us here was not it.#10745 (
PassThrough) confirmed not fixed by this change, by control test.fix(runtime): reach the stream subclass-init shim through the stream …
perf: this is a parameter of every JS body; the implicit-this cell is…
perf: function objects point to a static JsFunctionInfo; delete the c…