A constructor-function stream now reads `_final` and `_flush` when its
writable side finishes, and `_transform` for each chunk, as Node does. A hook
assigned after `Writable.call(this)` or `Transform.call(this)` runs, and an
override of a PassThrough's `_transform` replaces the pass-through. Without
`_final` or `_flush` the finish continues as Node's does.
A runtime stream passed to a constructor-function stream's listeners, such
as the source of a 'pipe' event, is the view whose pipe and unpipe accept
constructor-function streams, so `src.unpipe(this)` in a 'pipe' listener
unpipes instead of throwing.
The backend can emit scr_wasm_memory_construct, scr_wasm_table_construct,
scr_wasm_global_construct, scr_wasm_construct_without_new,
scr_wasm_module_imports, scr_wasm_module_exports,
scr_wasm_module_custom_sections and scr_wasm_value_of (#1410), and
scr_stream_dyn_native, scr_stream_pipe_dyn and scr_stream_unpipe_dyn
(#1524). Their prototypes were in scr_wasm.c or nowhere, so the runtime
ABI guard failed. They are now in scr_wasm.h and scr_stream.h, and
scr_wasm.c no longer repeats the header's declarations.
The speculative-hoisting test relied on `v instanceof Num` in a
JavaScript function failing, which constructor-function instanceof now
compiles (#1512). Its hoisted function reads the prototype through
Reflect.getPrototypeOf instead, which still fences.
With checked-dynamic stream slots, readable.unpipe() also unpipes every
JavaScript destination stream.pipeDyn registered from the source, oldest
pipe first. Each destination hears 'unpipe' and the source pauses, as in
Node's unpipe(), which empties every pipe. Before, those pipes stayed
attached and later chunks still reached them.
pipe() into an untyped destination now comes from main's pipeIntoDyn
(stream.unboxDyn); programs with constructor-function streams unbox
through scr_dyn_builtin_stream.c, which also takes those streams, so the
separate dyn.builtinStreamPipe entry point is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundled npm code (from2, bl, tar-stream, yazl, multistream) builds its
streams as ES5 constructor functions: util.inherits(Ctor, Readable) and
Readable.call(this, opts), with _read/_write/_transform on the
prototype. Readable.call(this) threw SC2020.
A new runtime unit, scr_dyn_builtin_stream.c (feature
dynBuiltinStream), gives the untyped stream constructors their stream
half. Base.call(this, opts) creates a runtime stream over the receiver
and keeps it in a hidden slot; the runtime calls the receiver's
underscore methods through its property lookup; the stream prototypes
carry Node's methods and accessors over the hidden stream; and
EventEmitter's methods register listeners with the runtime stream's
emitter, so runtime events and pipes reach JavaScript listeners with the
receiver as this. _readableState/_writableState are live views.
Stream class values now come from dyn.builtinStreamClass, so only
programs that use them link the stream half. Typed code sees these
objects as their runtime streams: checked casts to the stream classes
take them, readable.pipe() into an untyped destination pipes into them,
and runtime streams crossing into JavaScript answer the constructor
object.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Merge origin/main (e9fb01603); the import lines at the top of
lowerer.ts conflicted and both are kept.
- Corpus 6385 gains a wrapper whose block-local `args` alias shadows an
outer one in the same function; each call forwards its own arguments.
- EventEmitter.defaultMaxListeners on the untyped constructor is Node's
accessor over the default the typed EventEmitter reads: assignments
validate as validateNumber(arg, 'defaultMaxListeners', 0) does, and
getMaxListeners() of emitters without their own limit answers the new
value.
- dyn.builtinInstanceOf and dyn.functionInstanceOf link the
constructor-function unit and the units it calls (dyn-invoke, inspect,
symbol, emitter), so a program whose only use is `this instanceof F`
links.
- The legacy pipe's cleanup runs once: when the destination emits
'close' from end(), the cleanup still queued on the source's 'end'
finds the listeners removed and emit('end') answers true.
pipe() called from untyped JavaScript on a native stream went through
scr_stream_pipe directly, so a helper that piped into the converted
process.stdout or process.stderr ended it at the source's end, and later
writes through the program's NodeJS.WritableStream slots failed. The
stream view's pipe now calls scr_stream_pipe_general, which keeps end off
for those Writables, as Node's pipe() does for process.stdout and
process.stderr.
Adds corpus 6469.
Externrefs a guest holds are borrowed pointers. An import's result was
kept only until the outermost call returned, which was before the export
call converted its results to JavaScript, so a result created by an
import was freed before conversion. A table or global slot released the
reference it replaced at once, although the guest may still hold it in a
local after reading the slot. The kept references are now released after
the outermost caller has converted its results (scr_wasm_leave), and a
table or global store during a call keeps the reference it replaces until
then.
Checked memories (explicit bounds checks: sanitized builds, Windows)
grew with realloc, which can move the storage that memory.buffer and its
views alias. They now reserve the address space for their largest size
(mmap on POSIX hosts, VirtualAlloc on Windows) and commit pages as they
grow, so the base never moves. Hosts without address-space reservation
grow in place only until a buffer has been handed out; after that,
growth that would move the storage fails.
Tests: webassembly-externref-lifetimes.mjs (import results through an
export, table and global references replaced by the guest and by
JavaScript), webassembly-memory-growth.test.ts (views written after
growth from JavaScript and from the guest), and translate.test.ts cases
for memargs naming memory 0 explicitly and for local.tee operand types,
which b3bcbaaf7 made consistent with validation. Under SCRIPTC_SAN=1 the
two runtime tests report heap-use-after-free without these changes.
Merges main; internal/compatibility/generated/node-v24-backlog.json is
regenerated with pnpm node-compat.
events.once(emitter, name, { signal }) lowers to events.onceSignal, served
by a new runtime unit (scr_events_once_signal.c, linked only through the
eventsOnceSignal feature). It follows Node 24's lib/events.js: an
undefined signal is the plain form, a value that is not an AbortSignal
rejects with ERR_INVALID_ARG_TYPE, an aborted signal rejects with an
AbortError whose cause is signal.reason, and otherwise a once,
resist-stop-propagation 'abort' listener removes the event and 'error'
listeners and rejects. The event or the error removes the abort listener.
Options other than an object literal with one signal stay refused.
events.once now accepts stream receivers: a stream's runtime-emitted
events use their fixed tuples, and its 'data' and 'keypress' events stay
refused. A NodeJS stream interface slot that holds a stdio handle requests
the general stream representation.
Node's once is an async function that returns the executor's promise, so
its result adopts that promise through a thenable job. The plain form
settled its result two turns early; both forms now settle on Node's turn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- matchInferSignature reads a fixed-tuple rest as its positions, so
(...args: [string, number]) needs two arguments, and a positional
any[] pattern parameter matches only when the function's parameter
there accepts an array, as TypeScript's assignability does
(corpus 6398).
- Merge origin/main (200fe8d22); node-v24-backlog.json regenerated.
- http.request, https.request and get validate each name and value of a
flat [name, value, ...] headers array, as Node's _storeHeader does: a
name that is not an HTTP token throws ERR_INVALID_HTTP_TOKEN, a value
with an invalid character throws ERR_INVALID_CHAR, and an odd length
throws ERR_INVALID_ARG_VALUE with the inspected array. The
[[name, value], ...] entry form throws the options fence.
- A closing socket drops its 'lookup' listeners with its other listeners,
so a listener that holds the socket no longer keeps it alive.
- The options-record diagnostics snapshot no longer expects the
socketPath refusal.
- A checked-dynamic entry of a tuple Promise.all goes through
PromiseResolve (`dyn.promiseResolve`): a native promise is itself, a
thenable adopts its settlement through its resolve job, and any other
value fulfills. Before, a thenable fulfilled the slot with the object.
- Tuple fields read their entry by position. Shape fields are sorted by
name, so tuples of more than ten entries paired fields with the wrong
entries.
- Merge origin/main (e1c27176a).
- matchInferSignature answers false when a function needs more arguments
than a fixed-parameter pattern passes, as TypeScript's assignability
does (corpus 6398).
- Record corpus costs for the programs this change adds.
Under the general stream representation, every conversion of
process.stdout or process.stderr into a Writable slot made a new stream,
so two NodeJS.WritableStream slots holding process.stdout compared
unequal and kept separate listeners and writable state. A pipe into such
a slot also ended the converted Writable at the source's end, so later
writes through the slot failed; Node never ends process.stdout or
process.stderr from a pipe.
scr_stream_stdio_writable now keeps one Writable per descriptor and
thread, released at exit. Under the general representation, a pipe into
a Writable slot lowers to readable.pipeGeneral (scr_stream_pipe_general,
last in the stream unit), which keeps end off when the destination is
one of those Writables; programs without the general representation
lower pipes as before.
The fallback stream.Writable's on() and once() overloads for drain,
finish, close and error return the stream again, so listener chains such
as out.on("finish", listener).end(data) type-check.
Adds corpus 6468.
Under the general stream representation, NodeJS.ReadableStream and
NodeJS.WritableStream compile as the runtime Readable and Writable
classes, so a JavaScript stream object (a constructor-function stream, a
plain object with on/pipe, a stream from bundled npm code) passed through
a TypeScript function typed with them took the checked unwrap and threw.
A program that moves an untyped value into a runtime stream class slot,
or passes an untyped pipe() destination or pipeline stage, now lowers
once more with checked-dynamic stream slots: the two interfaces (and
intersections over them) map to dyn. A native stream rides its typed
capsule and keeps its identity; any other value is the JavaScript object
itself. Member calls dispatch per value: stream.dynNative tests for a
boxed native stream, which takes the ordinary class-method lowering;
other values call their own member through dynInvoke. pipe() into a
JavaScript destination follows Node's Readable.prototype.pipe listener
protocol (stream.pipeDyn). for await and pipeline stages keep the
not-supported error for values that are not native streams.
The runtime code lives in scr_stream_dyn.c behind a new streamDyn
feature, so programs without these libCalls link the same objects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node answers a Unix-socket server's address() with its path. Programs
that call listen() with a string argument now get a Unix-aware
address(): typed `string | AddressInfo | null` (the @types/node
declaration), it answers the path; typed as the AddressInfo record alone,
which cannot hold the path, it throws for such a server, as does the
composed address().port read. Every other program keeps the record-only
lowering, so their output is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in the review merge and "Keep an Error's code slot in step with
dynamic writes and own properties". With main's split, `'code' in err`
(now the runtime's error.hasCode) lands in expressions/relational.ts.
Corpus 6417-error-code-dynamic-writes renumbers to 6415 (main has its own
6417), and the corpus-cost entries of 6407/6408 follow their renumbering
to 6412/6413.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An 'unknown' value flowing or cast into a NodeJS.ReadableStream slot and
'for await' over one now select the general representation, where the
checked cast to Readable applies. pipeline() stages that arrive untyped
unwrap boxed native streams at run time. @types/node's zlib interface
declarations map to the runtime Transform.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
http.request, https.request and get now accept options the compiler
cannot see as an object literal: a RequestOptions variable, a literal
that spreads one, or a literal that sets socketPath. The runtime reads
the record in the ClientRequest constructor's order with Node's errors;
documented options it does not implement throw the runtime fence when
they carry a value. https adds rejectUnauthorized, ca and servername.
socketPath dials a Unix domain socket in the net unit; the agent keys
its sockets by the path as Node's getName does, and the Host header
defaults to localhost. server.listen(path[, cb]) binds a Unix socket and
removes the socket file when the server closes; a numeric string is
still a port. Errors name the path ("connect ENOENT /path", "listen
EADDRINUSE: address already in use /path"). Windows reports ENOTSUP.
A TypeScript object literal with no contextual type that spreads a
checked-dynamic value maps to the checked dynamic, so
`const options = { ...location, path, timeout }` compiles.
A socket's 'lookup' event reaches its listeners with (err, address,
family, host) for each address a host name resolves to, or once with
the lookup error, before 'connect'. Only programs that listen for
'lookup' install the lookup records; elsewhere the hook sites are a NULL
check.
server.close((err) => ...) runs the callback with undefined at 'close',
or with ERR_SERVER_NOT_RUNNING when the server was not listening; a
server that never listened now emits 'close'. IncomingMessage 'data'
listeners may take string chunks after setEncoding, and res.end()
accepts a string-or-undefined chunk.
fetch's BodyInit and HeadersInit unions, as RequestInit-derived option
types spell them under @types/node, map to the checked dynamic, and a
URLSearchParams body is sent form-encoded, as by Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fallback NodeJS stream interfaces declare Node's EventEmitter listener
signature for events beyond data/end, so checked JavaScript keeps its
contextual typing for process stream listeners. The child stdin fence
fixture becomes a differential test of the forms that now compile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>