Commit Graph
230 Commits
Author SHA1 Message Date
Chris Tate 6ae3eb5cc8 Match Node 24 in events and timers/promises
- The EventEmitter leak warning reaches process 'warning' listeners on the next tick
- The events listener-count and max-listener helpers work over EventEmitter instances
- timers/promises setTimeout, setImmediate and scheduler.wait accept an AbortSignal
- timers/promises scheduler.wait and scheduler.yield are lowered
2026-10-17 17:48:42 -05:00
Chris Tate 0db6d27c7c Support net.SocketAddress and net.BlockList
net.SocketAddress and net.BlockList were refused at compile time. They
are now native checked-dynamic handles in scr_net.c, so their getters
and methods also work on untyped values, and they match Node 24:

- new SocketAddress(options) checks the options in Node's order
  (ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE for the family,
  ERR_SOCKET_BAD_PORT, ERR_OUT_OF_RANGE for the flow label) and throws
  ERR_INVALID_ADDRESS for an address that does not parse. parse() reads
  URL.parse(`http://${input}`)'s hostname and port and answers undefined
  on failure; isSocketAddress(), toJSON() and util.inspect match.
- BlockList takes address, range, subnet and CIDR rules (addAddress,
  addAddresses, addRange, addSubnet, addCIDR, addCIDRs), removes them
  (removeAddress, removeRange, removeSubnet, removeCIDR, clear) and lists
  them in Node's order: IPv4 addresses, IPv6 addresses, subnets, then
  ranges, each newest first, with a repeated address kept once.
- check() compares across families through the IPv4-mapped IPv6 form,
  as Node's SocketAddress::compare and is_in_network do, and answers
  false for a string that does not parse in the given family.
- rules, size, toJSON(), fromJSON() (Node's rule patterns), isBlockList(),
  PRIVATE_RANGES, instanceof and util.inspect match, with the validation
  errors of lib/internal/blocklist.js.

Corpus 5659 and the node-types fixture net-blocklist.ts cover both
classes.

The compatibility registry lists the BlockList methods and
SocketAddress.parse as supported, and both classes as partial: their
structured clone and worker transfer forms are not modeled.
2026-10-17 14:32:06 -05:00
Chris Tate 23056949da Keep readline's terminal cursor on character boundaries, and merge main 2026-10-17 11:06:19 -05:00
Chris Tate b429d4ca6f Merge pull request #1108 from vercel-labs/ctate/add-messagechannel-receivemessageonport-5d9f36
Add MessageChannel, receiveMessageOnPort and MessagePort transfer
2026-10-17 09:35:15 -05:00
Chris Tate 4b7ff49da8 Support createHash and createHmac values as streams (#1120) 2026-10-17 08:47:55 -05:00
Chris Tate d5034758fb Merge main 2026-10-17 03:33:06 -05:00
Chris Tate 53023c615d Merge main 2026-10-17 03:30:55 -05:00
Chris Tate 0c4056fb0e Add the plan and timeout test options (#1165) 2026-10-17 03:22:28 -05:00
Chris Tate 26aeeb4c6f Lower util.promisify over the zlib callback one-shots (#1179) 2026-10-17 03:10:14 -05:00
Chris Tate 2896e94b20 Merge main 2026-10-17 02:59:21 -05:00
Chris Tate 7dcb1e6c4e Lower util.promisify over the zlib callback one-shots
promisify(zlib.deflate) and the other callback one-shots (inflate,
deflateRaw, inflateRaw, gzip, gunzip, unzip) over a named or namespace
import of node:zlib now lower. A call through the binding,
gzipAsync(data, options?), runs the callback form's work on the worker
pool and returns a promise that settles where the callback would run:
fulfilled with the Buffer, or rejected with the callback form's Error
(errno, code and message). An invalid option or input rejects the promise
instead of throwing, as the throw inside Node's promisify wrapper does.

The runtime adds scr_zlib_codec_promise and its checked-dynamic twin,
which share the callback forms' options ladder and worker-pool operation
and settle a pending promise in place of the callback. The fallback
declarations gain the matching util.promisify overload.
2026-10-17 02:46:20 -05:00
Chris Tate bc0636de7b Merge main 2026-10-17 02:35:31 -05:00
Chris Tate 0777ad7223 Merge main 2026-10-17 02:33:12 -05:00
Chris Tate 5c39821726 Merge main 2026-10-17 02:30:43 -05:00
Chris Tate f61e5dc938 Add readline's terminal mode, cursor helpers and emitKeypressEvents 2026-10-17 02:10:27 -05:00
Chris Tate 3e83b4a5c7 Lower util.promisify(stream.finished) and util.promisify(stream.pipeline)
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.
2026-10-17 01:51:57 -05:00
Chris Tate d7a5568726 Revert "Lower child_process.exec's callback forms" (#1161) 2026-10-16 22:05:36 -05:00
Chris Tate dfac9a950c Merge main 2026-10-16 21:33:56 -05:00
Chris Tate 53f22f7054 Merge main 2026-10-16 21:31:59 -05:00
Chris Tate db94b2d257 Add fs.cpSync, fs.cp and fs/promises cp
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.
2026-10-16 21:06:22 -05:00
Chris Tate d8c2b0f96d Lower child_process.exec's callback forms (#1161) 2026-10-16 20:57:36 -05:00
Chris Tate e2c34f381b Add the plan and timeout test options 2026-10-16 20:55:26 -05:00
Chris Tate 2ed22d38a4 Merge main 2026-10-16 20:32:43 -05:00
Chris Tate d53c1b8ba0 Merge main 2026-10-16 20:30:22 -05:00
Chris Tate 9681454556 Lower util.promisify(setTimeout) and util.promisify(setImmediate)
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.
2026-10-16 20:22:19 -05:00
Chris Tate cef618b553 Lower child_process.exec's callback forms
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.
2026-10-16 20:14:33 -05:00
Chris Tate 85439ae670 Merge main 2026-10-16 19:54:32 -05:00
Chris Tate 2e52dcd35c Add Readable.from over Sets, iterators and class iterables (#1074) 2026-10-16 19:30:21 -05:00
Chris Tate 5a091f050f Run the zlib one-shot forms on the stream engine (#1139) 2026-10-16 19:11:18 -05:00
Chris Tate d533fd7ce9 Merge main 2026-10-16 09:33:08 -05:00
Chris Tate 6f43e566a4 Merge main 2026-10-16 09:31:00 -05:00
Chris Tate ed5ab6796b Run the zlib one-shot forms on the stream engine
deflateSync, inflateSync, gzipSync, gunzipSync, deflateRawSync,
inflateRawSync and unzipSync, and their callback forms, accept Node's
options and run Node's processChunkSync over the zlib stream engine.

- Options (windowBits, level, memLevel, strategy, dictionary, chunkSize,
  finishFlush, maxOutputLength, rejectGarbageAfterEnd) are validated at
  run time with Node's ladder, shared with the stream classes; info is
  refused at run time.
- Errors carry Node's errno and code; maxOutputLength reports
  ERR_BUFFER_TOO_LARGE and rejectGarbageAfterEnd
  ERR_TRAILING_JUNK_AFTER_STREAM_END. The default-options decoders also
  read every gzip member.
- Untyped JavaScript inputs may be strings, TypedArrays, DataViews or
  ArrayBuffers; other values throw Node's ERR_INVALID_ARG_TYPE after the
  options are checked.
- Results are Buffers, as in Node: they convert to their text.

Corpus programs 5378, 5379 and 5415 cover the sync, callback and untyped
input forms.
2026-10-16 09:18:32 -05:00
Chris Tate daa5cc256f Lower util.promisify over the fs callback functions
promisify(fs.X) over a named or namespace import of node:fs lowers as
the fs/promises member of the same name for access, appendFile, chmod,
chown, copyFile, lchown, link, lstat, lutimes, mkdir, mkdtemp, readFile,
readdir, readlink, realpath, rename, rm, rmdir, stat, statfs, symlink,
truncate, unlink, utimes and writeFile. For these, Node's promisified
callback form settles with the callback's single result and rejects
with the same errors as the fs/promises call. Calls through the bound
function accept the forms the fs/promises lowering accepts and refuse
the others by name.

fs.open (an fd number where fs/promises.open gives a FileHandle),
fs.exists (a custom promisify) and fs.read/fs.write (multi-value
callbacks) remain refused.
2026-10-16 09:14:35 -05:00
Chris Tate 451e925b12 Merge main 2026-10-16 08:32:21 -05:00
Chris Tate a497705c34 Merge main 2026-10-16 08:31:51 -05:00
Chris Tate a5da46267b Merge main 2026-10-16 08:31:20 -05:00
Chris Tate a09990192e Add tlsSocket.getProtocol, getCipher, alpnProtocol and tls.DEFAULT_MIN_VERSION/MAX_VERSION (#1054) 2026-10-16 08:23:42 -05:00
Chris Tate ae1367b448 Support createHash and createHmac values as streams
Hash and Hmac values are Transforms, as in Node: they can be written
and ended, piped into, used as pipeline() and stream/promises pipeline
stages, read, and listened to, and setEncoding() applies to the digest
they push. Each value gets one native Transform on its first stream use.
The stream refers to its value without a reference; a pipe destination
or pipeline stage holds the value until the stream flushes or is
destroyed, and a destroyed stream is released so that listeners that
capture the value do not keep it alive.

After the stream flushes, a Hash answers one more digest() with the
same digest and then throws ERR_CRYPTO_HASH_FINALIZED, and update()
throws ERR_CRYPTO_HASH_UPDATE_FAILED; an Hmac answers digest() with an
empty digest. A stream write after digest() reports
ERR_CRYPTO_HASH_UPDATE_FAILED through 'error'. copy() of a Hash whose
stream flushed, and stream use of a value after its stream closed,
throw a coded SC2020 error.

Writing a checked-dynamic value that is not null, a string or a view to
any stream now throws ERR_INVALID_ARG_TYPE; it used to report the
null-values error.

Corpus 5760-5762 cover the piping, pipeline, setEncoding and event
forms, the states after a flush, and the JavaScript forms, and
errors.test.ts covers the two SC2020 errors.
2026-10-16 07:33:13 -05:00
Chris Tate 60623086a3 Lower util.promisify(child_process.exec)
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.
2026-10-16 07:32:06 -05:00
Chris Tate d28ec3e31e Merge main 2026-10-16 04:34:48 -05:00
Chris Tate 2e8b836a79 Merge main 2026-10-16 04:33:13 -05:00
Chris Tate 18e69b81c5 Merge main 2026-10-16 04:30:20 -05:00
Chris Tate 21b88b8d66 Add MessageChannel, receiveMessageOnPort and MessagePort transfer
new MessageChannel() from node:worker_threads makes two entangled
MessagePorts. They share the handle model of parentPort: postMessage
with the existing message clone, 'message' and 'close' events,
on/once, start, close, ref, unref and hasRef. A port's 'close' reaches
the other port after its queued messages. receiveMessageOnPort(port)
answers the next queued message as { message }, or undefined.

A channel is a thread-neutral record (scr_mailbox.c): each end has its
queue and the inbox of the thread that owns its port. A port listed in a
transfer list, given as postMessage's array or { transfer }, or as the
Worker constructor's transferList, travels with the message, and the
decoding thread adopts it. The sending port becomes unusable. A port in a
message without that listing throws DataCloneError, and so does a port
listing itself.

Port events follow Node's loop phases. Each port's queued messages
arrive as one batch, ports in creation order, before setImmediate
callbacks, with nextTicks and microtasks after each message (a new loop
hook keeps the pass going until the batch is delivered). A port's own
'close' runs in the following close phase, the latest close() first. The
other port's 'close' arrives on a later poll, in the order the notices
were sent, and only while the loop keeps running.
2026-10-16 04:27:08 -05:00
Chris Tate b487571539 Merge main 2026-10-16 03:33:30 -05:00
Chris Tate 4858a24420 Merge main 2026-10-16 03:30:56 -05:00
Chris Tate 0d59abe143 Pool small Buffers as Node does
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.
2026-10-16 01:39:10 -05:00
Chris Tate 2c04163350 Merge main 2026-10-16 01:32:17 -05:00
Chris Tate ba72bb6420 Merge main 2026-10-16 00:50:23 -05:00
Chris Tate 00bb57ac0a Merge main 2026-10-16 00:37:00 -05:00
Chris Tate 42954a3266 Add mock.fn and t.mock.fn with Node's calls log and implementation controls
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.
2026-10-16 00:22:22 -05:00