Node's util.promisify returns the custom promisified forms of
stream.finished and stream.pipeline, which are stream/promises' functions
of the same name. A const binding of promisify over the node:stream named
imports or namespace members now registers that stream/promises member
as the projection, so calls through the binding lower as the
stream/promises call and accept the stream arguments and options it
accepts.
The fallback declarations gain the matching util.promisify overloads.
pipeline's overload comes first: its (...streams: unknown[]) => unknown
type is assignable to every other target's parameter type.
The ESNext array lib is now part of the type world, and Array.fromAsync(source,
mapFn?) compiles when the source is an array of values or of promises. Each
call lowers to an async helper with the spec's loop: elements are awaited
through the async-from-sync iterator, with the same microtask turns as a
`for await` loop over an array, and a mapping function's result is awaited
before it is stored. A throwing or rejecting mapping function and a rejected
element close the iterator before the returned promise rejects, on the turn
Node rejects it. Async generator sources, a thisArg, and arrays that mix
values and promises are refused.
`using` and `await using` now accept object literals and other values typed
Disposable, AsyncDisposable or unknown. The declaration reads the value's
[Symbol.dispose] method ([Symbol.asyncDispose] first for `await using`) and
throws Node's TypeErrors for values that are not objects and for missing or
non-callable methods; null and undefined dispose nothing. Scope exit calls the
method with the value as `this`, and `await using` awaits the result of an
async method but not of the sync fallback. A class instance reached through
such a value throws an SC2020 error, because its dynamic view has no methods.
SuppressedError is its own error class: a failing disposal over a pending
error throws one whose `error` is the disposal error and whose `suppressed` is
the pending error, both non-enumerable as in Node. `new SuppressedError(error,
suppressed, message?)` and `instanceof SuppressedError` compile, and
`extends SuppressedError` is refused.
fs.cpSync, fs.cp and fs/promises cp compile with every option:
dereference, errorOnExist, filter, force, mode, preserveTimestamps,
recursive and verbatimSymlinks. They follow Node's two implementations.
cpSync follows cp-sync.js over the fs binding: copy errors are plain
Errors with a code, std::error_code failures throw an ErrnoException,
and links inside a copied directory point at their canonical targets.
fs.cp and fs/promises cp follow cp.js, whose copy errors are
SystemErrors with code, info, errno, syscall and path.
Options and paths validate in Node's order with Node's errors. filter
runs for the top-level paths and each entry in Node's order; an async
filter must return a promise that is already settled, and a pending one
throws SC2020. The callback and promise forms complete the copy before
the callback runs or the promise settles. Windows builds throw SC2020.
The copy lives in the new scr_fs_cp.c. Directory walks keep their frames
on the heap, so deep trees also copy on a fiber's stack. File contents
copy with sendfile on Linux, and COPYFILE_FICLONE uses FICLONE
where it exists. ScrError gains the SystemError context and the
ErrnoException key order, with matching fields in the compiler's Error
layout. The stored fs.cp callback uses the same implementation.
Corpus 5567-5571 cover the sync, callback and promise forms, errors,
symlinks and 1500-level trees.
union, intersection, difference, symmetricDifference, isSubsetOf,
isSupersetOf and isDisjointFrom now compile with a Map argument whose key type
is the Set's element type, as well as with a Set. A Map's set record reads its
keys, and its size, has and keys are the builtins, so the helper loops walk
and test the Map's keys in the order the spec gives. Other set-like arguments
are still refused.
The URL host parser kept a host whose last label is a number as a
domain, so new URL("http://127.1").hostname was "127.1" where Node
answers 127.0.0.1, and hosts such as 1.2.3.256, 0x100000000 and
1.2.3.4.5 parsed where Node throws Invalid URL.
A special-scheme host (file: included) that ends in a number now goes
through the WHATWG IPv4 parser: up to four labels after one trailing
empty label, each a decimal, 0x hexadecimal or leading-zero octal
number, every label but the last at most 255 and the last below
256^(5 - labels). The address serializes as dotted decimal, and any
failure makes the URL invalid. The constructor, URL.parse, URL.canParse,
the host and hostname setters and domainToASCII/domainToUnicode share
the parser. Non-special schemes keep their opaque host as written.
Corpus 5860 covers each form.
Node's util.promisify returns the custom promisified form of setTimeout
and setImmediate, which is timers/promises' function of the same name.
A const binding of promisify over the global functions, the node:timers
named imports or the node:timers namespace members now registers that
timers/promises member as the projection, so calls through the binding
lower as the timers/promises call: they fulfill with the given value
(undefined without one) after the delay or in the check phase.
The fallback declarations gain the matching util.promisify overloads.
exec(command, callback) and exec(command, { encoding, maxBuffer },
callback) lower like execFile's callback slice. The command line runs
with /bin/sh -c, as execSync runs it, the call returns the live
ChildProcess, and the error-first callback receives the captured utf8
stdout and stderr. A non-zero exit, including the shell's 127 for a
missing command, reports Node's "Command failed: <command>\n<stderr>":
scr_exec_file takes a shell flag so the message shows the command line.
Under @types/node, child_process.ExecException maps to Error, as
ExecFileException does. Its code member is the child's numeric exit
status, which the runtime error's string code slot does not hold, so
reading it, testing it with `in`, or testing its typeof is a named
refusal.
process.on/off for signals, uncaughtException, unhandledRejection,
rejectionHandled and warning pass the listener to the runtime as a
dynamic value. The int32 slot analysis treated those calls as possible
dynamic stores into any field, so every class reaching dynamic code
lost its int32 fields. The runtime only keeps each listener and calls
it with fresh event values, so these helpers are read-only.
A class instance passed, stored, returned or asserted as an anonymous
object type or an interface it does not `implements` was copied into a
native record, so writes, identity checks and inspection through the view
missed the instance (`function add(o: { count: number }, n: number)
{ o.count += n }` left a Counter at 0).
ClassProtocols now scans flow sites (arguments, typed initializers and
fields, defaults, returns, arrow bodies, yields, assignments, array
elements, property values, assertions through `unknown`, and annotated
callback parameters) and marks every program object type a class
instance flows into, then propagates the mark along flows between
structural types. Marked types are class-backed protocols: their values
are checked views that keep the original object, like implemented
interfaces already were. Only sites whose source carries identity query
the checker for their target, and a checker panic skips the site.
Assertions read or written member-wise in place keep their direct field
access, and `typeof C` is not an instance.
An assertion of a class instance to such a view boxes it like an
implicit flow, and `view.n++` on a number member lowers to the checked
expanded form.
A setup loop such as `for (i = 0; i < Math.floor(300000 * scale); i++) text.push(word())` multiplies only in its condition; its body does no integer work, so it keeps one copy. Also records the ts7 baseline of the speculative integer reads fixture.
Records in a parsed array usually spell the same keys in the same order.
Like V8's JSON parser following a map's expected transition, the dyn
parser now remembers the key sequences it has built in the current parse
as a small tree rooted at the empty object. When the next member's raw
text is exactly `"<expected key>"`, it needs no string scan, no duplicate
search and no key copy, and the entry shares the tree's interned key.
The object holds exactly the keys on its path, and a transition is
recorded only when a put appended a key that was not yet present and
needed no unescaping, so a byte match proves both the decoded key and its
novelty. Duplicates, escaped keys, keys beyond four candidate transitions
and parses past 4096 transitions finish the object on the ordinary path.
The tree is freed at the end of scr_json_parse and scr_json_parse_schema.
The recycled-key check in scr_dyn_obj_put and the true/false/null match
also use the inline scr_key_equal instead of a libc memcmp call, which
took 2.5% of the 31-json dynamic variant.
Sandbox, interleaved medians of 15 runs against the branch base (with the
read-site caches): 31-json as published 2.33 s -> 2.09 s, its
`checksum = checksum + parsed[i].score` variant 2.43 s -> 2.22 s, the
12-key constant-read benchmark 1.68 s -> 1.25 s. Peak RSS is unchanged.
Every `value.name` on an any/unknown object went through the shared key
helper's kind tests, scr_dyn_obj_read, scr_dyn_obj_read_receiver and
scr_dyn_find_entry, whose one global hint table is keyed by key length and
first byte. Keys that collide there (k05/k11, or any same-length keys with
the same first letter) evicted each other and fell back to a linear scan.
A constant-key read on an ordinary object now calls scr_dyn_obj_read_ic
with a zero-initialized per-site ScrDynReadIc, thread-local wherever
program globals are. The site remembers the entry position where it last
found the key as an own data property and holds a reference to the
interned key bytes stored there. Objects that spell a key alike share
those bytes, so a hit is one pointer compare at one position; long keys
and share-table collisions compare bytes there instead. Accessors, the
builtin Object.prototype and other receiver kinds keep the previous path.
Sites release their keys with the thread's runtime state (scr_atexit),
and the RC audit lane never fills one, so its live-key count stays exact.
Sandbox, interleaved medians against the branch base: 20k parsed 12-key
records with three constant-key reads each, 300 rounds, 1.66 s -> 1.16 s;
31-json's dynamic loop variant 2.37 s -> 2.35 s (its reads are bound by
the miss on each record's entry array). Peak RSS is unchanged.
Boxing a value into unknown converts promises through scr_async_dyn.c.
The runtime feature scan linked that unit only when the boxed value was
itself a promise or a function returning one, so boxing a record with an
async function field, an array of such records, or a union with such an
arm failed to link with an undefined scr_dyn_new_promise_adapting. The
scan now follows record fields, array, Set and Map elements and union
arms.
Corpus program 5812 boxes these values and compares the output with
Node.
Object.setPrototypeOf now follows the spec order. A null or undefined
receiver throws "Object.setPrototypeOf called on null or undefined",
then the prototype is checked, and a boolean, number, string, bigint or
symbol receiver is returned unchanged. Previously any receiver that was
not an object or array threw "Object prototype may only be an Object or
null".
A string path holding a null byte reached the system call, which saw the
path up to that byte: readFileSync("a\0b") read "a", writeFileSync wrote
it, and existsSync tested it. Node refuses such a path before any file
system access with an ERR_INVALID_ARG_VALUE TypeError naming the argument
("The argument 'path' must be a string, Uint8Array, or URL without null
bytes. Received '...'").
The fs runtime entry points now check their string paths first (each
with Node's argument name: path, prefix, oldPath/newPath, src/dest,
existingPath/newPath, target), so the sync forms throw, the promise forms
reject, and the callback form of rename throws before it queues the
operation. The Received text is util.inspect's quoted string, cut at 128
characters as Node cuts it; the JavaScript lane's validation ladders
share the same rendering, which now picks backticks for a string with
both quote kinds and escapes control characters as inspect does.
existsSync answers false.
Corpus fs-null-byte-paths.ts and fs-null-byte-paths-js.cjs cover the
sync, callback and promise forms in both lanes.
createHmac(algorithm) with no key now compiles and throws Node's
ERR_INVALID_ARG_TYPE ("Received undefined") from the key check, which
runs before the digest check as in Node; it used to be refused at
compile time. Reading copy on an Hmac, which has no copy() method,
answers undefined, so `typeof mac.copy` feature checks see what Node
reports. TypeScript already rejects that read.
Corpus 5764 covers createHmac with missing, undefined, null and number
keys, an unknown digest with and without a key, and the copy read.
The one-shot zlib forms now accept any options value and validate it at
run time, so the lowering test's options cases expect no diagnostic, as
does inflateRawSync with options. A Brotli call keeps the SC2020
refusal.
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.