With SCRIPTC_CHECKER_SERVER=1 on Linux and macOS, the native compiler's
checker connections go to a shared `tsc-ts --api --listen` server, which
keeps the files it parsed for the next build. When the variable is unset,
nothing changes.
checkerServerName names the server by a digest of the tsc-ts executable's
identity, the working directory, the file system callbacks, the timing
flag and the TSC_TS_* environment. scriptc_ts7_attach takes the server's
lock file with a non-blocking flock, connects to its socket in this
user's private server directory ($XDG_RUNTIME_DIR/scriptc or
/tmp/scriptc-<uid>), and starts the server when none answers: in its own
process group, with its standard streams on /dev/null and no other
descriptor of the build, with --idle-exit 300 and TSC_TS_LISTEN_MEMORY=2048
unless the environment sets it. A build that finds the server in use,
cannot start or reach it, or gets no answer to a ping checks with its own
tsc-ts process, as without the variable; so does a tsc-ts without
--listen, which exits as it starts. The syntax-only source parser, which
reads no libraries, always uses its own process and leaves the server to
the program's checker.
Preflight admits a createRequire loader's require.resolve call with a
computed request inside a try block, but lowering refused the call, so
in JavaScript source the whole statement became a runtime fence. The
fence threw before the request expression ran, and before anything else
in the statement: `try { where = load.resolve(candidate(name)); }`
skipped candidate()'s side effects, so the catch clause saw a different
state than in Node.
Such calls (a computed request, another argument shape, or
runtime-computed options, on require.resolve and require.resolve.paths)
now evaluate their arguments in order and then throw the fence, as a
computed require call does. The addon-probe fixture checks that a
computed candidate path runs before each failed lookup.
zValidator's default failure hook serializes zod 4's ZodError with
`name` after `message` natively, and with the first `name` zod's factory
assigns ("$ZodError") when the second assignment took the typed slot.
ZodError instances are views of a JavaScript class extending Error: each
view rebuilds `message` and `name` from the error slots at fixed
positions (after `stack`, and after every other property), and the
property bag that holds the dynamically added properties copied its
committed, possibly stale, values over them.
A JavaScript Error subclass with a property bag now marks its class
(errorBagOrder), and its view calls scr_error_dyn_follow_bag after the
bag is copied: `name` and `message` move to follow the property they
followed when the bag was committed, and data values come from the
slots. TypeScript classes keep their views.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zod 4's z.record rejected every plain object natively ("expected record,
received object"): its isPlainObject reads o.constructor.prototype, which
was undefined, and asks whether that prototype has its own isPrototypeOf,
which Object.prototype answered false.
- The Object, Array and Error constructors held as values have their
builtin prototype as a non-writable, non-enumerable, non-configurable
own `prototype`; reading Function's constructor value's prototype
throws SC2020 instead of answering undefined.
- hasOwnProperty / Object.hasOwn on Object.prototype answer its builtin
members, and on Array.prototype its constructor;
Object.getOwnPropertyNames lists them in Node's order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
existsSync and the fs.promises functions accept a file: URL wherever
they accept a path. They convert it after their arguments are evaluated,
and a URL that does not convert gives false from existsSync and a promise
rejected with fileURLToPath's error from fs.promises, as in Node.
copyFileSync and renameSync check a string source for null bytes before
they convert a URL destination, and readFileSync checks a runtime
encoding before it converts a URL path, so the error Node reports first
is the one thrown.
Refs #1616
Co-authored-by: jlucaso1 <55464917+jlucaso1@users.noreply.github.com>
The declaration-typed boundary already reaches zod 4 schemas and hono's
c.req.valid(): zod 4's z.infer and hono's validator Input generics resolve
to plain object types in the declaration world. Three gaps remained:
- Boolean literal types printed as `false` (the TS7 facade leaves
intrinsicName unset), so `{ ok: true } | { ok: false }` lost its true
arm and `boolean | undefined` became `undefined | false`. The printer
reads the literal's value.
- `r.data` after `const r = S.safeParse(x); if (r.success)` never
crossed: zod 4's result union is not data (its error arm is a class),
and reads on projectable receivers were skipped on the assumption that
something else types the receiver. Such reads now cross when the
receiver is a package call whose type does not project, or a const
bound to one.
- Reads through bindings annotated with a projected type no longer get a
redundant cast, and aliases are named only for casts that survive.
New npm cases zod4-boundary and hono-zod-validator run real zod 4.6.7,
hono 4.13.13 and @hono/zod-validator 0.9.1 (vendored file closures) under
Node and natively; decl-boundary.test.ts checks that c.req.valid() is a
checked cast with a path TypeError.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
X25519 KeyObjects come from generateKeyPair(Sync)("x25519"), which keeps
the private key clamped as OpenSSL does, and from createPrivateKey and
createPublicKey of PKCS#8, SubjectPublicKeyInfo, JWK (OKP, X25519) and
raw keys. They export in the same forms. sign, verify, createSign and
createVerify throw OpenSSL's "operation not supported for this keytype",
with its library, reason and code properties. X25519 uses mbedTLS's
Curve25519 group.
crypto.diffieHellman({ privateKey, publicKey }[, callback]) computes the
X25519 and ECDH secrets of two keys of one type, given as KeyObjects or
in any form createPrivateKey and createPublicKey read. The checks run in
Node's order: the options, the callback, both keys present, the key types
when both are KeyObjects ("Incompatible key types for Diffie-Hellman"),
then the public and private key inputs. Mismatched curves, a small-order
X25519 point and key types other than EC and X25519 throw OpenSSL's
errors. The callback form passes the secret, or the error without the
OpenSSL properties, in a later macrotask, as Node's DHBitsJob does.
createPublicKey also reads a private key's DER encodings (pkcs8 and
sec1), which Node accepts there.
Packages such as pi-tui look for their prebuilt Node-API addons at run
time: a createRequire require call with a computed path, and the
loader's resolve passed as a value, each inside a try block whose catch
takes a fallback. Preflight refused these packages.
Preflight now classifies each use of a createRequire loader. Inside the
try block of a try statement in the same function, a call with a
computed specifier or another argument shape, and resolve or
resolve.paths read as a value, no longer refuse the package. Outside a
try block they keep it refused, with a reason that names the computed
specifier. The loader itself as a value, writes and optional calls stay
refused.
In JavaScript source, such a call evaluates its arguments and then
throws a runtime fence, and the resolve value is a function that throws
one when called, so `(options.resolve ?? require.resolve)(id)` still
calls the option. Both are listed in coverage as deferred to runtime.
TypeScript source keeps the compile-time refusals.
Refs #1777
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.
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.
The declaration boundary projected a user-declared interface as plain
data even when it extends a package interface. effect's Request.tagged
constructors create such values (interface Double extends
Request.Request<number>) with a prototype, so the boundary cast threw
"expected a plain object (this value has a prototype)". A user
interface now projects only when every interface it extends is
user-declared too; other values keep their JavaScript-world typing, so
the package finds its prototype members when the value goes back to it.
- With 'readable' listeners the stream stays paused, as in Node: 'data'
listeners, resume() and a parked for-await read no longer keep the loop
reading once the held input reaches the 64 KiB mark (or a larger
read(size)); reading resumes when a read drains it.
- Held input is a list of chunks with a running length, so an arrival
appends without copying what is already held, and read() joins only the
bytes it returns.
- New event-loop case: 'readable', 'data' and resume() with no read() for
400 ms while 1 MiB arrives.
- TypedArray indexOf, lastIndexOf and includes on every element kind, typed
and checked-dynamic: strict equality (SameValueZero for includes) and
fromIndex as ToIntegerOrInfinity. A Buffer reached through a Uint8Array
value keeps Buffer's search.
- process.stdin 'readable' with read() and read(size), including sizes
above the read-ahead mark and input a once('readable') listener leaves
for a later 'data' listener. for await over process.stdin yields Buffers.
- Module bindings holding `new F()` of a constructor function can be read
and reassigned from other functions.
A package function declared to return its own type parameter, which also
types one of its parameters (<T extends object>(obj: T) => T), hands back
the argument or something standing in for it, and the package may track
that object's identity. Effect's Equal.byReference returns a proxy it
keeps in a WeakSet. The declaration-typed boundary cast such a result to
the argument's type and built a separate native record, so
Equal.equals(a, b) on two by-reference objects compared them by value and
printed true where Node prints false. The result's type comes from the
caller's argument, not from data the package declares, so the projection
no longer casts these calls.
The package re-export coverage test now expects three dynamic statements:
since the boundary, readRows's declared number result keeps its type, so
count - 1 in 7004-package-declared-type-reexports is native arithmetic.
- A bare arrow parameter after `async` (`async x => ...`) gets parentheses
with its projected type, as `x => ...` does. Without them the rewritten
file failed its check and lost its projection (SC6102).
- A cast that starts an unbraced loop or `if` body gets no `;` guard. Only
a statement in a statement list can continue the previous line, and in
a body the `;` became the body, which moved the statement after the
loop.
- Adoption walks a tuple's elements through the live element capsules of
the tuple's view, so references package code holds to them see the
native values.
- scr_runtime.h declares scr_dyn_decl_adopt (in scr_dyn.h), which the
backend emits, so llvm-runtime-abi.test.ts finds its prototype.
- npm.test.ts gives zod3-adoption and zod3-boundary the long per-case
timeout of the other zod 3 cases; their sanitized builds compile zod's
types module too.
`"closeAllConnections" in server` (the feature test servers use before
calling it) was refused for an http.Server and the other native handles.
A member Node's declarations give the class is on its prototype, so the
test now answers true after evaluating the receiver. Undeclared names
keep the refusal, since internal fields can still be present.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A response whose client disconnects while its body streams now cancels the
body's stream when the response closes, as the package does, so the
producer stops instead of being read until it ends. A response that fails
closes too and is cancelled the same way.
Response.json(data, { headers }) with init headers is written from its
Headers, as the package's Response.json passes them on as a Headers object
with the JSON content type added when they lack one. Before, the cached
path wrote only the init record and sent no Content-Type.
The hono-node-server case covers both: Response.json with and without an
init content type, and a client that destroys the response after the first
chunk of an endless stream.
@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>
A published bundle's JSDoc can name a type that only its .d.ts declares:
hono's bearerAuth carries `@template E` and `@returns {MiddlewareHandler<E>}`
without importing MiddlewareHandler. The checker types that result as its
error type, and a call of such a generic JavaScript function was refused
with SC2001 ("values of type 'MiddlewareHandler<E>' cannot be compiled").
The unresolved annotation is documentation, not a contract, so the call's
result is now checked-dynamic, as an `any` result of a TypeScript generic
already is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In a compiled program an opted-in package is typed from its JavaScript, so a
type computed from members only its declarations have (zod's
z.infer<typeof Schema>) is any. An interface extending it mapped to a record
of its own declared fields, and reads of inherited fields and spreads into it
were refused. It is now checked-dynamic, like the base.
The zod3-infer npm case uses z.infer, z.input and z.output across objects,
interfaces extending inferred types, spreads, optional and nullable fields,
unions, discriminated unions, transforms, defaults, .extend and arrays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>