socket.address(), socket.resetAndDestroy() and server.dropMaxConnection
were refused at compile time, and address() and resetAndDestroy() also
through untyped socket handles. They now match Node:
- address() answers { address, family, port } while the socket has a
local address, and {} while it connects or once it is destroyed. The
declared AddressInfo | {} result is a checked-dynamic value, so a type
assertion to AddressInfo narrows it.
- resetAndDestroy() closes the connection with an RST (SO_LINGER with a
zero timeout), so the peer reads ECONNRESET, and 'close' reports
hadError false. A connecting socket resets once it connects: the reset
is a once('connect') listener, so it runs at that place among the
'connect' listeners. A destroyed socket is left alone, a TLS socket
throws ERR_INVALID_HANDLE_TYPE, and the call answers the socket.
- dropMaxConnection is a stored value (undefined until set), since Node
reads it only in cluster mode.
Corpus 5657 (typed) and 5658 (untyped handles and the TLS throw) and the
node-types fixture net-reset-address.ts cover each member.
fs.statSync(path, options) and fs.lstatSync(path, options) now compile
with an inline options object, where throwIfNoEntry takes any boolean and
bigint must be false, or with a stored options object whose only member
is throwIfNoEntry, read at each call. With throwIfNoEntry: false the
result is Stats | undefined, and a missing entry (ENOENT, or ENOTDIR for a
path through a file, a dangling link's stat included) answers undefined
without building the error object, as Node's binding does. Other
failures still throw. Before, any second argument was refused.
Stats | undefined is a nullable union, so the result is the Stats pointer
itself (NULL for undefined) rather than a union box, and Stats snapshots
come from the small-object allocator. A stat-and-test probe such as
`stat !== undefined && stat.isFile()` therefore makes no system
allocator calls. stat?.isFile() and the other type tests also lower
through optional chains.
existsSync answers false for a path with a null byte, as Node does,
instead of testing the path up to that byte.
Corpus 5747 and 5748 cover the typed and JavaScript forms.
dgram.Socket's setBroadcast, setTTL, setMulticastTTL,
setMulticastLoopback, setMulticastInterface, addMembership,
dropMembership, get/setRecvBufferSize, get/setSendBufferSize,
getSendQueueSize, getSendQueueCount and disconnect were refused at
compile time. They now lower to scr_dgram.c and match Node:
- The TTL setters and setMulticastLoopback return their argument. The
value checks run first: a TTL outside 1-255 (0-255 for multicast,
after ToInt32) throws "setTTL EINVAL" with code, errno and syscall.
- A socket that has not been bound has no descriptor yet, so the
options throw EBADF, and the buffer size methods throw Node's
ERR_SOCKET_BUFFER_SIZE SystemError.
- After close(), the members that read Node's null handle throw its
TypeError ("Cannot read properties of null (reading 'setTTL')"), and
setMulticastInterface and the membership calls throw "Not running".
- Buffer sizes must be uint32 values (ERR_SOCKET_BAD_BUFFER_SIZE), 0
reads the current size, and sizes past the int32 range fail as EINVAL.
- IPv6 interfaces and groups go to the IPv6 socket options, so a udp4
socket answers ENOPROTOOPT as in Node.
- disconnect() throws ERR_SOCKET_DGRAM_NOT_CONNECTED until 'connect' was
emitted, and dissolves the association with an AF_UNSPEC connect.
addMembership and dropMembership before bind() throw SC2020, where
Node binds the socket to an ephemeral port first; the limitations page
documents it, along with the missing info object on the buffer size
SystemError. Corpus 5655 and 5656 and the node-types fixture
dgram-options.ts cover each method.
A const binding of promisify(exec) now lowers like promisify(execFile):
each call runs the command line with /bin/sh -c, as execSync does, and
settles with { stdout, stderr }. A non-zero exit, a missing command (the
shell's 127) or a timeout rejects with Node's "Command failed: <command>\n
<stderr>" message. The options are the execFile slice: encoding utf8,
cwd, env (inline spread included), timeout, and maxBuffer accepted.
Three gaps closed for both targets along the way:
- the { stdout, stderr } result maps as a record when it is held in a
variable, passed or returned, instead of only when destructured at the
await (it is an anonymous literal in @types/node and in the fallback);
- a namespace member is a recognized target (promisify(cp.exec));
- util.promisify(...) through a namespace or default import is
recognized like the named import.
Buffer.from of strings, arrays and Uint8Arrays, Buffer.allocUnsafe and
Buffer.concat now return views into a per-thread pool slab of
Buffer.poolSize bytes (64 KiB by default) when the size is below
Buffer.poolSize >>> 1, following Node's allocate, fromStringFast and
fromArrayLike: 8-byte aligned offsets, a new slab when a request does not
fit, and the four-bytes-per-unit reservation for strings. Buffer.poolSize
can be read and set, and Buffer.allocUnsafeSlow returns an unpooled Buffer.
A slab lives while any of its views does. Its ArrayBuffer cannot be
transferred, and structuredClone of a typed array view copies the whole
ArrayBuffer and keeps the view's offset. Buffer.alloc, allocUnsafe and
allocUnsafeSlow validate sizes with Node's ERR_OUT_OF_RANGE error.
The first pooled allocation finds the startup slab, which has the
default 64 KiB size. A request it cannot hold gets a slab of the current
Buffer.poolSize, as in Node, and the room is checked after every new
slab, so a Buffer.poolSize raised before the first pooled allocation
cannot place a reservation past the end of a 64 KiB slab. A reservation
larger than a fresh slab traps instead of writing past it.
Corpus 5531 (typed) and 5532 (untyped) raise Buffer.poolSize before the
first pooled allocation and allocate just under the new threshold
through each pooled path, each case in a fresh child process, and also
shrink Buffer.poolSize and change it between allocations. The
node-types fenced fixture no longer lists Buffer.poolSize, which now
compiles.
node:test's mock.fn(original[, implementation]) and t.mock.fn compile.
A Mock<F> is the function-with-properties record { %call, mock }: the
callable is a wrapper closure that shares the mock context with the mock
field. The context records each call's arguments, result, error and this,
and keeps the current implementation, the original and the one-time
implementations by call index. fn.mock supports, as in Node 24.22:
- calls, read as a copy of the log;
- callCount();
- mockImplementation(fn);
- mockImplementationOnce(fn[, onCall]), for the next call by default, a
later registration for the same index replacing the earlier one;
- resetCalls();
- restore(), which brings back the original.
A throwing implementation records the error and rethrows it. Functions
with fixed parameters or a single rest array parameter are supported; a
union return type needs an undefined arm. mock.fn() without an original,
mock.method, the tracker's reset and restoreAll, and t.mock.method are
refused with SC2020.
The node:test fixture mock-fn compares the behavior with Node, and
tests/diagnostics/mock-fences.ts covers the refusals.
fs.createReadStream, fs.createWriteStream and FileHandle
createReadStream, createWriteStream and readLines return runtime
Readable and Writable streams (scr_fs_stream.c), as do new fs.ReadStream
and new fs.WriteStream in JavaScript sources. They follow
lib/internal/fs/streams.js on the stream engine:
- _construct opens the file, then emits 'open' (with the fd) and
'ready'; _read reads up to end; _write retries short writes as
writeAll does; _destroy waits for I/O in flight, then closes (with
fsync first for the flush option). Every open, read, write, fsync and
close runs on the shared worker pool.
- Options are checked in Node's order: getOptions, options.fd or the
path, start, end, then highWaterMark, encoding, emitClose and
autoClose. flags and mode are checked when the file opens, so their
errors arrive as 'error' events.
- bytesRead, bytesWritten, pending, path and close(cb) compile, also
through untyped values (close() there).
A FileHandle stream holds a reference on the handle; closing the stream
releases it and closes the handle unless autoClose is false.
FileHandle.close() follows Node's reference count: it waits for the
streams over the handle and runs their close(). readLines is a readline
Interface over createReadStream with crlfDelay: Infinity.
The stream engine gains Node's _construct (reads, buffered writes,
finish and _destroy wait for construction; destroy errors combine with
aggregateTwoErrors) and _writev, both internal. Writable.destroy() now
runs the buffered write callbacks and end() callbacks (errorBuffer),
which it dropped before. highWaterMark in a dynamic options record is
checked as getHighWaterMark does, and for await over a constructing
stream waits for construction.
Numeric flags, the fs, signal and windowsHandle options, non-UTF-8
write encodings, option functions that replace the file implementation,
and instanceof fs.ReadStream/WriteStream are refused.
Corpus 5546, 5547 and 5554 cover both lanes against Node 24.22.
node:test changes, matching Node 24.22:
- t.before, t.after, t.beforeEach and t.afterEach lower. t.before runs
when it is called; a throw fails the test and the subtests that follow,
which skip their body and beforeEach hooks and run their afterEach hooks.
t.beforeEach and t.afterEach wrap every descendant subtest. t.after runs
after the test's afterEach chain, also when the body throws, and a throw
fails the test.
- t.plan(count) counts the test's t.assert calls and subtests. A mismatch
fails the test with Node's "plan expected N assertions but received M"
string, unless an assertion failed first.
- t.fullName joins the names from the outermost suite or test with " > ".
- Tests and suites under a todo suite or test are todo, with a bare TODO
directive, unless they are skipped or carry their own todo message. A
todo suite reports its directive.
- describe.only, suite.only and { only: true } on a suite mark it. At each
level, when a child is marked or contains a marked test or suite, only
those children run.
- The failing-tests section lists failures in completion order, so
subtests come before the test that ran them, and prints thrown strings
quoted, as Node inspects them.
The fixtures ctx-hooks, ctx-plan, todo-suites, suite-variants,
only-nested and thrown-values compare the reporter output and exit code
with Node.
createInterface accepts history, historySize, removeHistoryDuplicates,
completer, tabSize and escapeCodeTimeout, as Node does for a non-terminal
interface: tabSize, escapeCodeTimeout and historySize are checked in the
constructor's order before the interface opens (ERR_OUT_OF_RANGE and
ERR_INVALID_ARG_VALUE with Node's messages), a completer that is not a
function throws ERR_INVALID_ARG_VALUE, and the values are kept on the
interface. Options may be written as shorthand properties.
rl.history answers the history option itself, or the interface's own
empty array, and historySize, historyIndex, crlfDelay, tabSize,
escapeCodeTimeout, line and terminal read as in Node. 'history'
listeners can be registered; without terminal mode Node records no
history, so they never fire.
pipeline() accepts a source function (an async generator function called
with { signal }) and a destination async function, following
pipeline.js: the destination's promise value resolves the
stream/promises form and reaches the callback's second argument, and
pipeline() answers a PassThrough for it. A rejected destination destroys
that PassThrough with the reason and fails the pipeline.
Stages may take the { signal } argument: the pipeline creates its own
AbortController when one does, and aborts it when the pipeline finishes
or fails, as finishImpl does. Stage functions take the source and
{ signal } through the closure ABI, and the PassThrough and the value
are released with the pipeline.
The stream unit now links the emitter unit whenever it links, so a
pipeline of functions builds without constructing a stream. An async
generator destination and reading the PassThrough a function destination
returns are refused at compile time.
events.once(emitter, name) was refused. The emitter dispatches statically
emitted events to typed listeners through invokers built per argument
tuple, so the lowering takes the event's tuple from the program-wide event
table and builds a capture-free function that boxes that tuple into the
any[] Node fulfills with, and one for the 'error' tuple.
scr_events_once registers both as one-shot typed listeners behind emitted
adapters (emitterOnceAdapter). The first to fire settles the promise
(fulfilled with the arguments, or rejected with the error) and removes
the other, as in Node; awaiting 'error' itself registers only the one
listener. The shared state holds the emitter and the listener closures
weakly, so a never-fired once creates no cycle.
Options, computed or meta event names, EventTarget and stream receivers,
and events emitted with computed names or checked-dynamic listeners stay
refused. The fallback declarations type the result Promise<unknown[]>;
under @types/node (Promise<any[]>) TypeScript code narrows it to
unknown[].
timers/promises setTimeout(delay, value) and setImmediate(value) were
refused: only the void forms lowered. The value now travels as an
already-resolved promise of its own type (the Promise.resolve lowering),
and scr_tp_set_timeout_value / scr_tp_set_immediate_value copy its
settlement into the result when the timer or immediate fires, so the
result keeps the value's type. An options object literal takes `ref`;
`ref: false` unrefs the timer, so it no longer keeps the process alive.
AbortSignal options and other option forms stay refused, with their own
messages.
@types/node types setInterval as NodeJS.AsyncIterator<T>, which maps to
no IR type, so a for await loop over setInterval(delay, value) was refused
in projects with @types/node. The interval's async generator type now
comes from the iterator's type argument, and a for await loop whose head
is that call consumes it directly.
The fallback declarations type the new forms.
- getProtocol() answers the negotiated version ("TLSv1.2" or
"TLSv1.3"), the highest version on offer before the handshake as
OpenSSL does, and null after close.
- getCipher() answers { name, standardName, version }: OpenSSL's name
for the negotiated suite, its IANA name, and the suite's minimum
protocol version. A suite without a known OpenSSL name throws the
"not supported yet" error.
- alpnProtocol is null before the handshake, then the negotiated
protocol or false.
- tls.DEFAULT_MIN_VERSION and DEFAULT_MAX_VERSION are 'TLSv1.2' and
'TLSv1.3', and the bundled declarations type the server's
secureConnection socket as a TLSSocket.
- The limitations page notes that a typed getCipher() read before the
handshake or after close throws where Node answers undefined or null.
Corpus 5651 covers each case.