Commit Graph
804 Commits
Author SHA1 Message Date
Chris Tate 9a2d378fba Run completion callbacks in their call's AsyncLocalStorage context (#1804) 2026-10-23 15:29:35 -05:00
Chris Tate 850710fbea Box TextEncoder and TextDecoder records as checked-dynamic handles (#2022) 2026-10-23 15:03:43 -05:00
Chris Tate 96a5954940 Merge main 2026-10-23 14:09:13 -05:00
Chris Tate 693e33742a Support the raw-public and raw-private key formats
EC and Ed25519 keys read and write Node 24's raw formats:

- generateKeyPair(Sync) encodings: raw-public gives the EC point,
  uncompressed or, with type "compressed", compressed, or the Ed25519
  public key; raw-private gives the EC scalar at the curve's size or the
  Ed25519 seed.
- KeyObject.export({ format: "raw-public" | "raw-private" | "raw-seed" }),
  with Node's check of the point type, and Node's error for a passphrase
  with any format other than PEM and DER, which now also covers JWK.
- createPublicKey and createPrivateKey of { key, format,
  asymmetricKeyType, namedCurve }, in Node's argument order, with
  compressed EC points, and createPublicKey of a raw private key.

As in Node, raw-seed and every raw format of an RSA, RSA-PSS, DSA or DH
key throw ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS, which generateKeyPair
passes to its callback. Invalid key data throws ERR_INVALID_ARG_VALUE.
Raw X25519, X448, Ed448 and post-quantum keys, EC scalars of 0 or at
least the curve order, and the EC point at infinity, which Node accepts,
throw a coded SC2020 error.
2026-10-23 13:59:28 -05:00
Chris Tate 08f282e1b6 Merge main and run finish, close and pipeline callbacks in their AsyncLocalStorage context 2026-10-23 13:57:55 -05:00
Chris Tate 47636f2fa5 Implement the identity TransformStream natively (#2009) 2026-10-23 13:51:52 -05:00
Malte UblandClaude Opus 5.5 f14997776b Box TextEncoder and TextDecoder records as checked-dynamic handles
A codec stored in untyped storage (hono's StreamingApi keeps
`this.encoder = new TextEncoder()` in a JavaScript class field) had no
dynamic representation and threw SC1101 at run time. The codec records
now cross 'unknown' slots as handles of two new tags that hold the record
itself, so identity and a decoder's streaming state survive the round
trip, and checked casts unbox the same record.

The handles answer encode, encodeInto and decode (with options objects),
the encoding, fatal and ignoreBOM properties, method values with Node's
ERR_INVALID_THIS, instanceof, String() tags and JSON's {}. The unit
scr_text_codec_dyn.c links only into programs that convert a codec to or
from an untyped value (the textCodecDyn runtime feature, found with the
dgram carrier walk, now shared).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:33:06 -07:00
Chris Tate 4d70630229 Keep a keyboard-focused badge's tooltip open when the pointer leaves
Hover and keyboard focus now open the tooltip separately, as the
CSS-only tooltip's hover and focus-visible rules did, so moving the
pointer off a focused badge no longer unmounts its tooltip or drops
aria-describedby. Escape still dismisses it.
2026-10-23 12:30:31 -05:00
Chris Tateandjonbaldie efa797382e Render the compatibility matrix's tooltips on demand
The matrix mounted every badge's tooltip up front, hidden by CSS, and
re-rendered every row on each keystroke. Tooltips now mount while their
badge is hovered or has keyboard focus, are named by aria-describedby
and close on Escape. Badges and rows are memoized, each row's search
text is built once, the filter makes one pass for its rows and its
count, and the table follows deferred search and status values, with
expand all and collapse all as transitions, so input stays responsive.

The tree and filter passes move to src/lib/compatibility-tree.ts, with
unit tests that also render the badges on the server (pnpm test:unit,
part of pnpm check).

Closes #601

Co-authored-by: jonbaldie <8376953+jonbaldie@users.noreply.github.com>
2026-10-23 12:20:28 -05:00
Malte UblandClaude Opus 5.5 3c2911ccad Implement the identity TransformStream natively
'new TransformStream()' had no lowering, so hono's stream() and
streamSSE() helpers failed to compile their checked-dynamic paths. The
constructor without arguments now builds a native handle: its readable
side is a native ReadableStream with a zero high-water mark, and its
writable side runs the streams spec's WritableStream state machine
(getWriter, abort, close, locked; the writer's write, close, abort,
releaseLock, ready, closed and desiredSize). A write completes when the
readable side reads its chunk, close/abort/cancel propagate across the
pair, and deferred reactions take as many microtask turns as in Node.
Web ReadableStreams can also be weak collection keys (hono keys a
WeakMap by the response stream). A transformer, queuing strategies and
the WritableStream constructor keep the SC2020 refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:47:47 -07:00
Chris Tate 885a829959 Compile minified npm packages such as marked (#1989) 2026-10-23 09:07:41 -05:00
Chris Tate 1221104388 Merge main and fix CI 2026-10-23 07:50:09 -05:00
Chris Tate ba5d73e90b Recognize esbuild's __commonJS helper under a minified name
Refs #1777

An identifier-minified esbuild bundle renames the __commonJS helper
(var j=(u,t)=>()=>(t||u((t={exports:{}}).exports,t),t.exports)). The
recognizer already compared the helper's shape independently of its
parameter names, but only used a renamed match to refuse the package.
A stable top-level declaration with the helper's shape now splits the
bundle's chunks like the named helper, so lru-cache's CommonJS build is
no longer refused.
2026-10-23 06:20:02 -05:00
Chris Tate 967f2ec85d Compile minified npm entries and untyped regex arguments
Refs #1777

npm packages are no longer refused up front for minified JavaScript. A
bundle whose interop helpers lost their names is still refused by the
bundle recognizers, and a package whose attempt fails is still refused on
its own.

A `var` read in a later declarator of the statement that stores it
(`var a = {...}, b = {...a}`) runs after the store. A read in a function
body is now located at the body's own statement instead of the body, so
JavaScript `var` bindings read after their store in a function keep their
type instead of gaining undefined.

`replace` and `replaceAll` with an untyped search value and a callback
call the regex callback replacement for a RegExp and the string-search
helper otherwise. That helper now accepts untyped callback parameters,
passes undefined to parameters after the subject, and converts number,
boolean and untyped results with ToString.

In JavaScript, `||` and `&&` over primitives of different types whose
result tsc types `any` evaluate in the checked domain.
2026-10-23 05:55:39 -05:00
Chris Tate 8d17fe4e57 Support crypto.getCipherInfo, AES-CCM and AES key wrap (#1873) 2026-10-23 05:36:21 -05:00
Chris Tate 19e0fe8bcd Merge main and read cipher options through getters and prototypes 2026-10-23 05:33:14 -05:00
Chris Tate 63fd3cecb7 Build @hono/node-server 2.x into scriptc (#1886) 2026-10-23 05:25:14 -05:00
Chris Tate 952e01d2ed Stream request bodies in the Node server adapter 2026-10-23 05:03:12 -05:00
Chris Tate 945a519036 Match Node for typed pipeline callbacks and rl.cursor on non-terminal interfaces 2026-10-23 03:49:51 -05:00
Chris Tate 91e15f3426 Run child_process and http2 events in Node's AsyncLocalStorage contexts
child_process and http2 events ran with no store, or with the context of
whichever socket dispatch delivered them. Node runs them in the context
of the handle or request that produces them.

child_process: a child, its piped stdout, stderr and stdin, and its IPC
channel keep the context of the spawn() or fork() call. 'spawn',
'error', 'exit', 'close', stdout's and stderr's 'data' and 'end', and the
channel's 'message' and 'disconnect' run in it, in the POSIX and the
Windows implementations. stdin's 'drain' and 'error' run in the context
of the latest write() call, its 'finish' in the context of its end()
call, and a send() callback in the context of its send() call.

http2: a session keeps the context it was created in, the
http2.connect() call for a client and the server's listen() context for
a server session, and the session's socket callbacks and deferred emits
run in it. A client stream keeps the context of its request() call, and
the frames that drive a stream's events (DATA, WINDOW_UPDATE, RST_STREAM,
and a client's HEADERS) and its deferred emits run in the stream's
context. A session remembers the stream its last lookup by id found, so
the context lookup and the frame's own handling share one search.
A server stream and its compatibility request and response run with no
store, as Node's do. An accepted h2c socket's own events run with no
store, as an accepted net socket's do, so a server.close() that waits for
its sessions calls back with no store; a TLS server's sockets keep the
listen() context, as before.

Corpus 7385 to 7389.
2026-10-23 03:40:52 -05:00
Chris Tate 8bb9ee8c57 Check generateKeyPair arguments and RSA parameters as Node does 2026-10-23 03:38:18 -05:00
Chris Tate b6d6eb60ae Replace a scheduled _construct hook, and keep runtime option values in order
Node's constructNT reads _construct on the construction tick, so an
assignment after the constructor and before that tick replaces a scheduled
construct hook. The assignment was dropped, so the original hook ran. It
now lowers to stream.replaceConstruct, which swaps the hook while its tick
has not run and has no effect after it or on a stream without a hook.

Runtime decodeStrings and defaultEncoding values are read into locals
before the constructor evaluates the other option members, while Node
evaluates the members in order. A runtime value after a member whose value
has effects (a call or an assignment) now refuses instead of reading a
value that member could still change.
2026-10-23 01:34:19 -05:00
Chris Tate 7dfc96728d Merge main 2026-10-23 01:19:49 -05:00
Chris Tate da289bb593 Support writev on Transform and PassThrough, and runtime stream option values 2026-10-22 23:13:49 -05:00
Chris Tate fc31dd5060 Enumerate each record in the key order its object literal wrote 2026-10-22 22:56:12 -05:00
Chris Tate f030e5ad44 Check with tsc-ts 0.0.8 on every platform
tsc-ts 0.0.8's API server serves on the named pipe the Node host
connects over on Windows (tsc-ts --api --pipe), and the native compiler
already starts it over its standard streams there, so tsc-ts is the
default checker on every platform. SCRIPTC_CHECKER=tsgo still selects
tsgo.

The compiler depends on tsc-ts 0.0.8, which the release-age exclusions
admit with its platform packages and @scriptc/threads 0.4.2.

The Windows CLI smoke test runs with both checkers, and
verify-native-cli.mjs --run checks that a distribution's tsc-ts runs on
its platform (tsc-ts.exe on Windows).
2026-10-22 22:36:11 -05:00
Malte UblandClaude Opus 5.5 9bf76af974 Build @hono/node-server 2.x into scriptc
@hono/node-server replaces the global Request and Response with
lightweight objects and reparents prototypes, tricks that only pay off
under V8 and that native compilation cannot follow. scriptc now compiles
the package from a built-in implementation (shims/hono-node-server/v2.ts)
over node:http and the native Request/Response/Headers: serve,
createAdaptorServer, getRequestListener and RequestError.

Responses are written the way the package writes them, byte for byte on
the wire: the general path (header record in Headers order, Set-Cookie
gathered, default content type, content length for bodies that finish
within the first reads, streamed bodies, error responses) and the
package's cached-response path for a Response that is still exactly its
constructor arguments. The runtime tracks that lightweight state on each
constructed Response (header form, raw plain-record headers, body length;
cleared by body and metadata reads, turned into Headers form by a headers
read) and exposes it to the shim through scriptcResponseLight.

The declaration entry of a shimmed package may now be a .d.mts/.d.cts
file. The npm case hono-node-server drives buffered, streamed, cookie,
header-form, HEAD, error-handler and URL cases against the real package
under Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:32:49 -07:00
Chris Tate 1660531c7b Support FinalizationRegistry with bookkeeping and no cleanup callbacks (#1854) 2026-10-22 20:03:31 -05:00
Chris Tate 636ffa21be Merge main 2026-10-22 19:59:51 -05:00
Chris Tate 3024e57eeb Revert "Enumerate each record in the key order its object literal wrote (#1802)" 2026-10-22 19:02:41 -05:00
Chris Tate 53c43dd140 Merge main 2026-10-22 18:44:10 -05:00
Chris Tate f132cf5f39 Compile Iterable<T> values (#1875) 2026-10-22 18:30:45 -05:00
Chris Tate 81891d55f7 Fix boxed record field hooks and moving key writes, and merge main 2026-10-22 18:19:14 -05:00
Malte UblandClaude Opus 5.5 089ec6739b Compile Iterable<T> values
Iterable<T> maps to a union of the sources that keep their own
representation (T[], Set<T>, Map<K, V> for [K, V] entries, string) and a
generator over T that holds every iterator. An array stays the same
multi-pass object; a generator stays single-pass. for-of gives the array
arm its own array loop, so iterating an Iterable that holds an array runs
at array speed. Spread, Array.from, the Set/Map constructors and
Object.fromEntries consume it; optional Iterables narrow into it;
generator functions may declare an Iterable return. Class iterables and
typed arrays are refused at the conversion with a named diagnostic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:00:41 -07:00
Chris Tate 5372c69459 Pass { signal } to stream operator callbacks and Duplex.from bodies 2026-10-22 17:50:29 -05:00
Chris Tate 98712c364a Merge main 2026-10-22 17:49:58 -05:00
Chris Tate 846552b0e2 Support crypto.getCipherInfo, AES-CCM and the AES key wrap ciphers
getCipherInfo(nameOrNid[, options]) runs in a new runtime unit,
scr_cipher_info.c, over a table that scripts/gen-cipher-info.mjs generates
from Node 24.22's getCipherInfo. The table covers every cipher OpenSSL
names, including the ones without a native implementation. Lookups match
names case-insensitively and accept OpenSSL's nids. The unit applies
cipher.js's argument checks and GetCipherInfo's keyLength and ivLength
rules, and returns the info object in Node's key order. CipherInfo maps
to a checked-dynamic value, so TypeScript and JavaScript code receive
Node's object. Reading the nid Node leaves out for the SIV and SM4 GCM,
CCM and XTS ciphers, where the declared type gives number, throws a
TypeError.

createCipheriv and createDecipheriv support aes-128/192/256-ccm (and the
id-aesN-ccm names) over OpenSSL's ccm128 steps. They follow Node's tag and
IV lengths, the plaintextLength requirement, the message-length caps and
the one-update rule, and report a failed authentication from final().

They also support the RFC 3394 and RFC 5649 key wrap ciphers (aes-N-wrap,
aesN-wrap, id-aesN-wrap, the -pad forms and the -inv ciphers) over
wrap128.c's routines, with Node's lengths, integrity failures and
one-update rule.

The new names follow the existing ones in the cipher table, so the lookup
cost of the existing ciphers is unchanged.
2026-10-22 17:18:54 -05:00
Chris Tate 1d8d19e634 Run stream events and callbacks in Node's AsyncLocalStorage contexts
Stream events and callbacks ran with no store. Node's streams run each
step in the context of the code that triggers it: a step deferred with
process.nextTick (resume, emitReadable, endReadable, afterWrite, the
destroy and finish steps) in the context that scheduled it, and other
events in the context of the push(), write(), end(), destroy() or pipe()
call that emits them. Native-backed streams run their events in the
context of their handle.

The runtime now captures the active context where it defers a stream
step and enters it around the step's dispatch:
- the stream tick queue keeps each tick's scheduling context;
- the runtime's own process.nextTick jobs (zlib completions, pipeline and
  finished steps, readline) run in the context they were queued in, as
  user ticks already did;
- fs read and write stream requests keep the context of the call that
  started them, so a stream's open, reads, writes and close follow its
  creation;
- zlib streams keep their creation context and run each engine step's
  completion in it.

In programs that use AsyncLocalStorage, stream.finished and
stream.pipeline callbacks are bound to the context of their call, like
the other completion callbacks. Programs that do not use
AsyncLocalStorage compile to the same IR.

child_process and http2 events are unchanged.

Corpus 7383 and 7384.
2026-10-22 17:05:08 -05:00
Chris Tate 57c10fc767 Build Sets, Maps and weak collections from checked-dynamic iterables (#1856) 2026-10-22 16:53:29 -05:00
Chris Tate d7717ff1e9 Type empty array literals by their slots, and report package diagnostics accurately (#1799) 2026-10-22 16:40:40 -05:00
Chris Tate ee865bcb0d Support the legacy createCipheriv modes and crypto.checkPrime/generatePrime (#1832) 2026-10-22 16:32:12 -05:00
Chris Tate 5c29845909 Support Readable.fromWeb over web ReadableStreams 2026-10-22 15:23:28 -05:00
Chris Tate 18c2900937 Read web ReadableStreams as async iterable stream sources 2026-10-22 14:20:01 -05:00
Chris Tate d83c676705 Store compact values of string-key maps in 4-byte slots on Linux 2026-10-22 14:08:00 -05:00
Chris Tate d8d75dfa18 Support Duplex.from over streams, pairs, promises and async functions 2026-10-22 13:32:25 -05:00
Chris Tate 8f08cbbec5 Build Sets, Maps and weak collections from checked-dynamic iterables
`new Set(x)`, `new Map(x)`, `new WeakSet(x)`, `new WeakMap(x)` and
`Object.fromEntries(x)` now accept any checked-dynamic iterable, as in
Node: a native iterator (`map.keys()`, `set.values()`), a compiled
generator, an object with a Symbol.iterator method, or a Set. Before, a
Set seed that was not an array or a string threw "is not iterable", and a
Map, weak collection or Object.fromEntries seed that was a generator or a
collection other than a Map refused with SC2020.

The runtime drains native iterators, packs arrays, strings and
collections, and steps every other iterable through its iterator
(scr_dyn_seed_values). A program that builds these from an untyped seed
registers the compiled generators' next and return methods with the
runtime, as destructuring rest already did, so a generator seed can be
stepped.

The seed's values are all taken before the first one is added, so when an
entry of a Map seed is not an object the TypeError comes after the
iterable is exhausted; Node closes the iterator at that entry. The
limitations page says so.
2026-10-22 13:09:01 -05:00
Chris Tate 83d426f9be Support FinalizationRegistry with bookkeeping and no cleanup callbacks
wasm-bindgen output constructs a FinalizationRegistry per exported class
when its module loads, so every wasm-bindgen package stopped at load with
SC2020. `new FinalizationRegistry(cleanup)` now creates a native registry:
- register(target, held, token) and unregister(token) check their
  arguments and answer as Node does, with Node's TypeError messages;
- the unregister tokens are keys of an internal weak map, so the registry
  keeps no token alive;
- the cleanup callback never runs, which ECMAScript allows, so the
  registry keeps neither the callback nor the held values.
The global answers typeof "function", instanceof, Object.prototype.toString
and util.inspect as Node does. Symbol targets and tokens are refused with
SC2020.

`"encodeInto" in encoder` on a TextEncoder, which wasm-bindgen's loaders
test at load, answered false because the encoder is a brand record without
fields for its methods. `in` over such a builtin brand record now answers
from the members the standard library declares, plus Object.prototype's.

The limitations page says that memory a package frees only from a cleanup
callback, such as the WebAssembly memory behind wasm-bindgen wrappers the
program drops without free(), is not reclaimed until the program exits.

Fixes #1678.
2026-10-22 12:14:23 -05:00
Chris Tate a6a1eb2b95 Support Duplex.from, compose stages, readable.compose and pipeline function destinations 2026-10-22 10:57:53 -05:00
Chris Tate 4d7de03054 Store arrays of ProgramLifetime instances in 4-byte slots on Linux 2026-10-22 10:46:16 -05:00
Chris Tate de6dd13370 Run net, tls, http and https events in Node's AsyncLocalStorage contexts
The runtime fired every socket, server, request and response event from
the event loop with no store. Node runs them in the context of the
resource or request that produced them, so getStore() answered
undefined in listeners where Node answers the caller's store.

Handles now keep the context Node uses and the runtime enters it around
each dispatch:
- a socket the context of its creation (net.connect, tls.connect, an
  http request's socket), for 'connect', 'data', 'end', 'close',
  'error' and 'timeout';
- a server the contexts of its listen() call ('listening', 'connection',
  listen errors) and its close() call ('close' and close callbacks when
  no connection is open; a close that waits for the last connection
  settles in that connection's dispatch, as in Node);
- socket write() callbacks and end() callbacks with 'finish' the
  contexts of their calls;
- http server requests and responses, which are created in their
  connection's dispatch, the server's listen() context, and client
  requests and their responses the http.request() context, including a
  keep-alive socket an agent hands to a later request.

An accepted net socket runs its own events with no store, as in Node;
the sockets of an HTTP or TLS server run in the server's listen()
context. Events and callbacks of other streams are unchanged.

The compiler is unchanged. Programs without net or http link the same
runtime code; net and http programs gain the capture and dispatch
helpers (about 1.7 KB of text in a net program, 2.2 KB in an http
server), and bench:runtime http-api is neutral.

Corpus 7119 and 7380.
2026-10-22 10:45:31 -05:00