Skip to content

class X extends Stream (the bare node:stream base) throws 'is not a constructor' — #10649 covers only Readable/Writable/Duplex/Transform; blocks nodemailer #10798

Description

@proggeramlug

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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    package-auditFound by the 2026 package audit: compiling real npm packages from source instead of native bindings

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions