- socket.remotePort, remoteFamily, localAddress, localPort, localFamily,
bytesRead, readyState, connecting and pending. The peer's fields are
cached at first read as remoteAddress is (undefined while
connecting), and the local ones are undefined while connecting and
after close.
- write(data, cb) runs cb with null once the data is flushed, and
end(cb) / end(data, cb) once the socket finishes.
- 'close' listeners receive hadError, 'ready' fires after 'connect',
and 'data' listeners may take the string chunks setEncoding gives.
- setTimeout(ms, cb) adds cb as a once('timeout') listener (0 removes
it), and setTimeout throws ERR_OUT_OF_RANGE for a negative, NaN or
infinite msecs where it disabled the timer before. setKeepAlive
chains.
- server.maxConnections closes connections past the cap and emits
'drop' with their addresses; server.getConnections(cb) answers the
count on the next tick.
- net.isIP, isIPv4 and isIPv6 follow Node's address patterns.
- A callback adapted to a void return keeps the identity of the
function it wraps, so removing it by that function finds it.
Corpus 5644-5647 cover each case.
The fs validation ladder and the callback dispatch check their options
argument as Node's getOptions does. null, undefined and functions take
the defaults, and a string is the encoding. Numbers, booleans, bigints
and symbols throw ERR_INVALID_ARG_TYPE ('The "options" argument must be
one of type string or object'). assertEncoding lets 'buffer' through;
before, every 'buffer' encoding threw ERR_INVALID_ARG_VALUE.
With 'buffer':
- readFile and readFileSync read the file first, so a missing file
reports ENOENT. Then buffer.toString('buffer') throws
ERR_UNKNOWN_ENCODING; an empty file answers "".
- fs.mkdtemp's callback receives the path as a Buffer.
- mkdtempSync, opendirSync, createReadStream and createWriteStream
throw the fence error.
fs.mkdtemp's callback form now checks its encoding. It also returns its
path in the requested encoding: re-encoded text for names other than
utf8, as the binding does.
Corpus 5545 covers these forms against Node 24.22.
opendirSync(path[, options]) and fs.promises.opendir(path[, options])
return a Dir that reads one Dirent at a time in OS order, as libuv's
uv_fs_readdir does. Dir supports path, readSync(), read(), closeSync(),
close() and for await. readSync and read answer null at the end. A
closed Dir throws ERR_DIR_CLOSED from readSync and closeSync, and
rejects with it from read and close.
for await over a Dir reads with read() until null and closes the Dir
in a finally, as Node's Dir.entries() does: normal exhaustion, break
and a throw all close it, and a loop whose body closes the Dir ends
with ERR_DIR_CLOSED.
A Dir also crosses 'unknown' slots as a native handle (Dir), so a
closure returning one can be passed to untyped JavaScript functions.
Through such a slot, path and closeSync() work, and other members throw
the "not supported yet" Error.
Open failures carry the opendir syscall and no path. options.bufferSize
is checked after the directory opens, with Node's ERR_OUT_OF_RANGE
messages. The options must be an object literal with a numeric
bufferSize and a utf8 encoding. Recursive walks, buffer encodings and
the callback forms are not lowered. TypeScript sources get a compile
diagnostic for them; in JavaScript sources, recursive walks and the
callback forms throw the fence error when the call runs.
Corpus 5544 covers both forms against Node 24.22.
The three glob entry points run a port of Node 24's
lib/internal/fs/glob.js walk in scr_glob.c: the same LIFO queue of
(path, patterns) items over the sorted directory listings, the same
lstat, stat, readdir, realpath and seen-pattern caches, the same result
set in insertion order, and the same exclusion checks, so matches come
out in Node's order. Patterns compile with the existing minimatch port.
glob_match is split into a compile step and a test step so the walk can
reuse each compiled pattern and test it segment by segment.
nocaseMagicOnly is now an option, because exclude patterns turn it off.
Patterns (a string or string array) and options (cwd as a path or
file: URL, exclude as patterns or a function, followSymlinks,
withFileTypes) are validated in Node's order with Node's errors,
including the internal assertion Node hits for a non-string cwd with
exclude patterns. withFileTypes results are Dirent rows: name,
parentPath and the type probes. parentPath follows Node's rule for each
form: the joined path for globSync, the resolved path for fs/promises
glob.
fs.glob checks the callback and validates synchronously, then delivers
string[] or the walk's error through the filesystem callback queue.
fs/promises glob lowers as the source of a for await loop. The walk runs
when the loop starts, and the matches found before an error bind before
the error is thrown, as Node's async iterator yields them first. The
older glob(3)-based fs.glob branch is removed.
Corpus 5448-5452 and a node-types fixture check the behavior against
Node.
crypto.createSecretKey and generateKey/generateKeySync for "hmac" and
"aes" return SecretKeyObject values, native dyn handles over a copy of
the key bytes. The type and symmetricKeySize getters, export() as a
Buffer or a JWK, equals(), instanceof KeyObject, String(),
JSON.stringify and util.inspect follow Node 24.22, as do the argument
checks, their order and their errors. createHmac and hkdf/hkdfSync
accept these keys. toCryptoKey() and KeyObject.from() need WebCrypto
values and are refused with SC2020.
Dyn handle ops gain a @@toStringTag that differs from the class name
and a flag for views with no properties, which util.inspect prints at
any depth. KeyObject method calls dispatch through dynInvoke.
crypto.timingSafeEqual accepts every TypedArray, DataView, Buffer,
ArrayBuffer and SharedArrayBuffer and compares their bytes. Values of
other types throw Node 24.22's ERR_INVALID_ARG_TYPE for buf1 and then
buf2, before the byte lengths are compared. Views of every element kind
keep the typed call; other arguments take the checks at run time.
randomFillSync accepts every TypedArray, DataView, Buffer, ArrayBuffer and
SharedArrayBuffer and returns its argument at its own type. Offsets and
sizes count elements and are checked in bytes with Node 24.22's
ERR_OUT_OF_RANGE and ERR_INVALID_ARG_TYPE errors. randomFill fills the
same buffers and calls back in a later macrotask, or synchronously when
nothing is filled, as Node does.
randomInt takes a callback in both forms, and its errors carry Node's
received values and the max or "max - min" label. Values come from a
6 KiB cache, as in Node, so a callback runs on the next tick while the
cache has data and after an asynchronous refill when it is empty.
randomUUID and the new randomUUIDv7 validate options.disableEntropyCache
and draw from a 128-UUID batch unless it is set.
A non-u8 view passed to randomFillSync no longer needs a Uint8Array
type, and the bytes callback adapters pass views of any element kind
and dyn values through.
util.stripVTControlCharacters ran Node's ANSI pattern through the
regex replace loop, which tried the OSC branch's lazy scan at every
introducer. Text with many unterminated OSC introducers took quadratic
time (about 200 s for 100,000 of them).
The function now scans once and gives the same result as Node 24.22's
pattern: the OSC branch ends at the first terminator after ESC ], and
that position is cached across introducers; the CSI branch resolves its
first match without backtracking. A removal that brings lone surrogates
together keeps canonical storage.
Corpus programs 5470-5474 cover OSC and CSI forms, seeded random input,
long unterminated runs and surrogates, and strip-vt-linear.test.ts checks
that 400,000 unterminated introducers finish quickly.
Hash.update and Hmac.update accept every Buffer input encoding and any
TypedArray or DataView. Hex input of odd length throws Node's
ERR_INVALID_ARG_VALUE, an encoding Node does not know reads as UTF-8, and
data of other types throws ERR_INVALID_ARG_TYPE. digest() returns a
string for every Buffer encoding and a Buffer for a falsy or unknown one,
and a repeated Hmac.digest() returns Node's empty result.
createHash and hash.copy take options.outputLength for the SHAKE and
KECCAK-KMAC digests, with Node 24.22's validation and its
ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH error, including the library and
reason properties, for other digests. crypto.hash takes an output
encoding or an options object with outputEncoding (including "buffer")
and outputLength. The functionName and customization options are
validated and then refused with Node 24.22's errors. createHmac takes
TypedArray, DataView and ArrayBuffer keys and options.encoding for string
keys, and checks them in Node's order.
Calls whose encodings and options the compiler can resolve keep their
typed fast paths. Other forms go through runtime entry points that apply
Node's checks and return a string or Buffer, checked at the call's type.
A literal digest encoding for which Node returns a Buffer is refused
when the declared result is a string.
The digest compression functions start on a cache line, so their speed
no longer depends on the size of unrelated runtime code.
createHash, createHmac, crypto.hash, pbkdf2, pbkdf2Sync, hkdf and hkdfSync
accept every digest crypto.getHashes() lists in Node 24: SHA-512/224,
SHA-512/256, SHA-256/192, SHA3-224 to SHA3-512, Keccak-224 to Keccak-512,
SHAKE128, SHAKE256, KECCAK-KMAC-128, KECCAK-KMAC-256, RIPEMD-160,
BLAKE2b-512, BLAKE2s-256, SM3 and MD5-SHA1. The new runtime unit
scr_hash.c implements Keccak, RIPEMD-160, BLAKE2b, BLAKE2s and SM3, and
is registered in both runtime pack matrices and the C-compile path.
Names resolve as Node 24.22 resolves them: case-insensitively, with
OpenSSL's provider aliases and object identifiers, ending at the first
NUL, and with trailing spaces allowed except on the RSA signature
aliases. "dss1" and "DSS1" are SHA-1, and createHash and crypto.hash
accept "dss1" in any case. The compiler no longer refuses literal digest
names outside SHA-1/SHA-2 and MD5; unknown names throw Node's errors at
run time.
SHAKE and KMAC digests use their default output lengths. createHmac
rejects them with Node's OpenSSL error, pbkdf2 with "xof digests not
allowed", and hkdf with "Deriving bits failed". pbkdf2 with a key length
of 0 now fails with "Deriving bits failed" as in Node 24.22, and the
callback forms pass these failures to the callback.
PBKDF2 computes the keyed HMAC states once instead of on every
iteration, and digest contexts are cleared with memset and a compiler
barrier instead of a byte loop. The Keccak permutation, the RIPEMD-160
and BLAKE2 compression functions and the SM3 rounds are unrolled with
constant indices.
Blob and File (globals and node:buffer exports) compile to native
handles in the new scr_blob.c, following Node 24.22's
lib/internal/blob.js and file.js. A Blob is a window over a shared,
immutable byte store plus the byte length of each part. Sources (strings,
typed arrays, DataView, ArrayBuffer, Buffer, other Blobs, Set and Map)
convert as a Web IDL sequence, with each non-buffer part converted to a
string, before the options are read, and are copied when the Blob is
built. The constructors throw Node's Web IDL TypeErrors and its
ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE and ERR_MISSING_ARGS errors,
and read and convert their options in Node's order. The type is
lowercased, or cleared when it holds a character outside U+0020..U+007E.
endings: "native" rewrites CRLF, CR and LF to the platform's line break.
File takes its name with lone surrogates replaced and lastModified as a
number (Date.now() when absent).
slice() converts its offsets as a [Clamp] long long (rounded half to
even) and keeps the parts Node's data queue keeps. text(), bytes() and
arrayBuffer() settle on the same microtask turn as in Node, which depends
on the part count. util.inspect prints Node's `Blob { size, type }` and
`File { ..., name, lastModified }` forms, with `[Blob]` past the depth
limit. String() and toString() give the class tag, JSON.stringify gives
{}, and structuredClone returns a new Blob or File over the same bytes.
instanceof works for both spellings. stream(), textStream() and the
Object.prototype members other than toString() fail at compile time.
Non-array iterables other than Set and Map throw SC2020.
Three handle-table additions support this: a structuredClone hook, a
JSON `{}` flag, and a bracketed past-depth inspect flag. scr_blob.c is an
optional unit selected by dynAsync, because the dynamic dispatch path
boxes its reads as dyn promises through scr_async_dyn.c.
Corpus 5285-5290 and a node-types fixture check the behavior against
Node.
Pin Node v24.22.0 (commit f14cd3aa7bf9, Undici 7.29.1, OpenSSL 3.5.8) in
.node-version, Dockerfile.sandbox, the docs matrix fallback, the
compatibility pin, the fetch profile, and process.version. A node-pin test
checks that these pins agree. The Node compatibility snapshot is
regenerated from the v24.22.0 API docs; the new stream_iter chapter maps
to node:stream/iter, and the SuiteContext rows no longer match the
TestContext evidence.
Runtime and compiler changes follow Node 24.22 behavior:
- fs.read, fs.readSync, and FileHandle.read validate the position before
the zero-length return.
- fs.statfs results include frsize; Windows reads
FileFsFullSizeInformation and reports bfree and bavail separately.
- fs.utimes, futimes, and lutimes keep nanosecond precision.
- ServerResponse sets writableFinished where 'finish' is emitted and runs
end() callbacks before the 'finish' listeners while the socket is still
attached; a request read to 'end' closes before its response.
- socket.write(string, 'buffer') throws ERR_UNKNOWN_ENCODING.
- URL protocol-setter rejections keep the current URL, the host setter
no longer leaves a "/." escape when it adds a port to a "//" path, and
new URLSearchParams(null) parses "null".
- util.stripVTControlCharacters uses Node 24.22's ANSI pattern,
util.styleText accepts #RGB and #RRGGBB colors, and
util.types.isDataView has its JS name and length.
- structuredClone and AbortSignal.any use the "cannot be converted to
sequence" message.
- crypto.getCiphers, getHashes, and constants match Node 24.22.
- dns.lookup, net.connect, and tls.connect throw ERR_INVALID_ARG_VALUE for
hosts with null bytes.
- fs.readFile validates options.buffer and refuses its valid forms;
httpValidation and certificateCompression refuse by name.
- url.domainToASCII, url.domainToUnicode, and url.parse hostnames follow
ada 4.0: ASCII domains are lowercased without checking their xn--
labels, the RFC 5893 rule checks how LTR labels end and how RTL labels
start, a Punycode label whose decoded form begins with "xn--" fails, and
ToUnicode decodes only labels that pass the ToASCII checks. The url
legacy test cases are regenerated with Node 24.22.
Corpus programs 5275-5279 cover the new behavior. 1640, 2283, 2599, and
3136 are updated where they pinned Node 24.15 output.
Typed and JavaScript classes can extend the global EventTarget and Event.
%EventTarget and %Event are runtime-provided hierarchy roots like the
EventEmitter: a subclass instance embeds a prefix pointing at the engine
state that super() creates (the target's listener core, the event's
state), and the backend's emitter prefix handling becomes a table of
runtime prefixes that covers all three bases.
The instances' checked-dynamic capsules use the runtime RC adapters, so
the engine recognizes them: a target instance is its listeners' `this`
and its events' target, and an Event subclass instance is the event its
listeners receive, with its own fields and methods. The inherited
methods and accessors lower directly on typed receivers (including
super.dispatchEvent) and answer through the class property dispatch and
the runtime's capsule hooks for checked-dynamic values, with Node's
util.inspect output, @@toStringTag and leak-warning class name. Members
read as values are the prototype's functions, also for plain handles.
A target keeps its own capsule in its state, so repeated dispatches reuse
it; the state and an event's target are traced, so the collector
reclaims targets whose listeners capture them. Subclass members that
spell an inherited member are refused, since the engine would never call
or read them.
Listener objects that are class instances now work: when a program can
register EventTarget listeners and declares a class with a handleEvent
method, %main installs a compiled `listener.handleEvent` reader that the
engine uses at every dispatch, so the class's own, inherited or
overridden method runs with the instance as `this`, as in Node. An
unannotated arrow in an object literal whose contextual type maps to a
checked-dynamic slot or to no lowered type (an EventTarget subclass's
listener union) no longer adopts the void callback slot's return type,
so `{ handleEvent: (e) => n++ }` passed as a listener keeps the result
its record field promises.
- Support `new Console(...)` over process stdio and writable streams
- Write `log`/`info`/`debug`/`error`/`warn` output like Node, including `ignoreErrors`
- Mark the `Console` class as partially supported in the compatibility data
- Support `arrayBuffer()` from `node:stream/consumers`
- Print ArrayBuffer and SharedArrayBuffer values like Node
- Mark the stream consumers as partially supported in the compatibility data
The runtime answers like libuv 1.51's uv_available_parallelism, which
Node's os.availableParallelism() returns: on Linux the CPUs in the
process's affinity mask, lowered to a smaller cgroup (v2 cpu.max or v1
CFS) CPU quota; on Darwin hw.activecpu, hw.logicalcpu or hw.ncpu; on
Windows GetSystemInfo's processor count; at least 1. WASI answers 1, as
the dynamic island does.
Parallel programs (tsc-ts --checkers) need it to size their thread pools.
Corpus 4598 pins the value against the host's Node.
- Preserve callback identity, receiver binding, and class references across unions and collections.
- Track record field presence independently of undefined values and improve native runtime behavior.
- Expand differential coverage and streamline CI bootstrap and compiler rebuild checks.
- Add native worker execution with isolated module state, structured messages, and lifecycle controls.
- Support shared buffers and integer atomics with coordinated runtime ownership and cancellation.
- Extend compiler, runtime-pack, differential, and compatibility coverage for native concurrency.
- Preserve live class identity through interface receivers and object methods.
- Extend synchronous text reads and regular expression replacement callbacks.
- Keep unsupported generic dispatch boundaries explicit.
- Enforce oxlint and oxfmt for owned JavaScript and TypeScript with independent CI checks.
- Preserve compiler import boundaries and protected fixtures while cleaning up lint findings.
- Standardize source formatting and validate import, fixture, and embedded-source protections.
* Add Node 24 filesystem link support
* Use executable extensions in Windows link fixtures
* Remove Windows links without walking their targets
* Match Windows symlink inference and Buffer validation
* Match Node Windows symlink paths and fallback state
* Preserve Windows errors when following filesystem links
* feat: support Node 24 filesystem timestamp operations
* test: record frontend baselines for timestamp fixtures
* test: link timestamp validation into the runtime smoke test