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:
if normalize_native_module_alias(module.as_str()) == "stream" {
match method.as_str() {
"Readable" | "Writable" | "Duplex" | "Transform" => ...
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:
| base |
status |
Readable/Writable/Duplex/Transform |
fixed by #10649 |
Stream (bare) |
this issue |
PassThrough |
#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');
class Tap extends Stream {}
new Tap(); // 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.
class X extends Stream— the barenode:streambase, not one of its subclasses — throwsTypeError: is not a constructor. This blocksnodemailer, whereclass XOAuth2 extends Streamis loaded unconditionally on import.Found by the package-compilability probe on
origin/main(v0.5.1617),nodemailer9.0.3.Why it is a gap rather than an oversight
PR #10649 fixed bound-export heritage dispatch for
Readable,Writable,DuplexandTransform— its runtime arm matches exactly those four names:Stream(the legacy base class thatReadableand 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:
Readable/Writable/Duplex/TransformStream(bare)PassThroughStreammay be the cheapest of the three: unlikePassThrough(where the hidden_transformfield is never pre-seeded, so adding a dispatch arm alone provably changes nothing),Streamis a real constructor with a real prototype, so it may only need adding to #10649's match list plus whateverjs_node_stream_*_subclass_initequivalent 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
Also reachable via
import nodemailer from "nodemailer"with the binding removed — it throws on import, before any API is called.