---
title: Limitations
url: "https://scriptc.dev/docs/limitations"
docs_index: /llms.txt
lastUpdated: 2026-11-13
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

scriptc supports a subset of JavaScript, TypeScript, and the Node.js APIs.
This page is a reference for the remaining restrictions and for runtime behavior that differs from Node.js.
You don't need to read it front to back: run [`scriptc coverage`](/docs/coverage) on your program, and come here when a diagnostic or a behavior difference needs more context.
The [Node.js compatibility reference](/compatibility) lists support API by API.

Some entries mention **checked values**.
When the type of a value is only known at runtime, such as `unknown`, `any`, the result of `JSON.parse`, or a value passed through untyped JavaScript, scriptc stores it together with its runtime type and checks it when the program converts it back to a specific type.
Entries that say a value "retains identity through checked storage" mean the same object comes back out, rather than a copy.

<span id="static-compilation" />

## Language and API restrictions

The following sections describe what compiles and what doesn't, area by area.
They are not exhaustive; the compiler's diagnostics are the authority for a specific program.

### Language features

- **Error causes:** Accessor constructor options are unsupported.
  Subclasses may redeclare `cause` as `unknown` or `object` (for example `cause?: unknown`), which defines the own property after `super()` returns as Node does; redeclarations with other types, parameter properties, and accessors named `cause` are unsupported.
  `Error`, `TypeError`, `RangeError`, and `SyntaxError` otherwise accept `{ cause }`, including through inherited constructors.
  Reads, writes, deletion, and `"cause" in error` distinguish an absent cause from an explicitly undefined cause.
  Constructor causes are non-enumerable; assignment to an absent cause creates an enumerable property.
  Property descriptors and checked-value aliases share the cause, including getters and setters installed after construction.
- **Class reflection:** `JSON.stringify` and `Object.keys` require every declared field to have a representation that can pass through runtime-checked storage.
  `JSON.stringify` writes Date fields as their ISO text and Map and Set fields as `{}`, as Node.js does, and refuses classes with RegExp or Promise fields with `SC1090`.
  `Object.keys` raises an error for classes with opaque fields such as native Maps.
  Class fields, inherited accessors, and added properties retain identity through untyped values.
- **Class members:** A method, an accessor, or a static method of a TypeScript class that cannot be compiled is refused by itself: the class, its fields, and its other members compile.
  Using the refused member where the compiler can see it is a compile error at the use, which names the member, and the member's own diagnostic is reported once.
  A call that reaches the member another way throws an error that carries the diagnostic's code at run time: through an untyped value, through an interface, or through a base-class receiver when the member is an override.
  A refused field, constructor, base class, decorator, or index signature still rejects the whole class, as does a member that overrides an async, generator, or generic method of its base class.
  An override may declare a narrower parameter than the method it overrides: one arm or some arms of the base's union, a required parameter where the base's is optional, or a subclass of the base's class.
  A call through the override's own class is direct; a call through the base class, an interface, or an untyped value checks each such argument and throws a `TypeError` naming the method and the parameter when the value is outside the type the override declares.
  Node.js runs the override with that value; a compiled override reads its parameter as the declared type, so the call is refused instead of running with a value the type does not describe.
  An override may also declare fewer parameters than its base: the arguments past its last parameter are dropped, as in Node.js.
  A call through an untyped value must still pass the base's arguments, and a shorter override that a subclass overrides again is callable through its own class only when the dropped parameters are optional.
  An override with fewer parameters whose body reads `arguments`, or with a tuple where the base declares an array, is refused as a member.
- **Method values:** Borrowing a class method onto an unrelated object is unsupported.
  Detached methods that use `this`, builtin method values, abstract declarations, unspecialized generic methods, and incompatible override signatures produce runtime errors or compilation diagnostics.
  Supported class methods can be stored and passed as unbound functions.
  A method of a TypeScript class that mentions `this` or `super` checks its receiver each time it is called through a value, and throws a `TypeError` unless the receiver is an instance of the class.
  A method that tests its receiver (`this === undefined`, `!this`, `this instanceof C`) therefore throws that `TypeError` when it is called without an instance, where Node.js runs the method and takes the method's own branch.
  The same holds when the call supplies another object as the receiver, where Node.js answers the test for that object.
  A method that never mentions `this` runs without a receiver.
  A member called through parentheses, a non-null assertion, or a type assertion (`(obj.method)()`, `obj.method!()`, `(obj.method as F)()`) is called on its object, as in Node.js; an element access in parentheses (`(obj["method"])()`), a parenthesized optional chain, and an optional method declared without a body and called with `!` lose the receiver.
  A field call supplies its containing instance as `this`; `call`, `apply`, and `bind` work when the signature supports runtime-checked conversion.
  Extracted overrides retain the selected method, and receiver validation accepts the declaring class and its subclasses.
- **Loose equality:** Object-to-primitive `==` and `!=` comparisons are unsupported because `valueOf` and `toString` can execute user code.
  Convert the object explicitly before comparing.
  Comparisons between represented primitives and primitive unions support number, string, boolean, and BigInt coercions, including `x == null` and `x != null`.
- **Resource disposal:** Top-level and switch-clause `using` declarations, and `AsyncDisposableStack`, are unsupported.
  Block- and function-scoped `using` and `await using` support compiled classes with zero-parameter disposal methods, object literals and other `Disposable`- or `AsyncDisposable`-typed values, and supported FileHandle, timer, immediate, child-process, and readline handles.
  A class instance reached through a `Disposable`- or `unknown`-typed value throws an SC2020 error when its declaration runs; declare the variable with the class type.
  Array `for (using ... of ...)` loops also compile.
  `SuppressedError` values expose `error` and `suppressed`; `extends SuppressedError` and casting `unknown` to `SuppressedError` are unsupported (narrow with `instanceof`).
  `DisposableStack` supports `use`, `adopt`, `defer`, `move`, `dispose`, `disposed` and `[Symbol.dispose]`, and works with `using`; `use` accepts class instances, object literals and other `Disposable`-typed values, and refuses unions that include class instances (narrow the value first).
  Disposal runs in reverse order and raises Node's `SuppressedError` chain when more than one callback throws.
- **Async iteration:** Async `yield*` delegation, stored Web or Node iterator handles, and general structural async-iterator objects are unsupported.
  Typed async generators support lazy execution, `await`, direct `yield`, queued `.next()`/`.return()`/`.throw()` requests, and `for await`.
  `for await` also accepts Node `Readable` streams, Web `ReadableStream` values, and compiled class iterators whose zero-parameter `next()` returns a promise of a `{ value, done? }` record.
  A class instance whose `[Symbol.asyncIterator]()` returns an object literal, or that has only `[Symbol.iterator]()`, iterates through the checked-dynamic async protocol, and an early exit awaits the iterator's `return()`.
  Abrupt completion runs cleanup, including literal `destroyOnReturn` and `preventCancel` options.
  The `node:timers/promises` `setInterval(delay, value)` iterator compiles without AbortSignal options.
  `Array.fromAsync` accepts arrays of values or of promises and typed async generators, with an optional mapping function; a `thisArg` and arrays that mix values and promises are unsupported.
- **Iterable values:** A value typed `Iterable<T>` can hold an array, a Set, a Map (for `Iterable<[K, V]>`), a string (for `Iterable<string>`), a generator, or a Map, Set or array iterator.
  Class instances with their own `[Symbol.iterator]` method and typed arrays can't be stored in an `Iterable<T>` and are reported at the conversion.
- **Generics:** Generic functions declared inside other functions, generic class expressions, and generic methods requiring dynamic dispatch are unsupported.
  Generic classes can extend generic bases when each specialization has a concrete layout.
  Independent concrete uses have no specialization-count limit; recursively expanding demands and type layouts produce diagnostics.
  Other generic calls compile when the target resolves statically.
  Generic generator methods, sync and async, compile like other generic methods.
  A generic async generator method called through an untyped value, or through an interface whose receiver's class the compiler does not know, throws a coded SC2020 error.
  Generic function values require a concrete signature at use and a binding that is never reassigned.
  Inside a generic instance whose binding holds values of several record layouts, a statement that takes the binding's value as the one record type its constraint names (an argument for a parameter of that type, a function literal's parameter, or a destructuring pattern) builds with warning `SC6015` and throws a `TypeError` when it runs with a value of another layout, where Node completes it.
- **Variadic wrappers:** A generic higher-order wrapper is specialized to each wrapped function's exact signature.
  `Parameters<F>`, `ReturnType<F>`, and rest parameters typed as fixed tuples become that signature's parameter list.
  A function expression with no parameters that reads `arguments` compiles only when its value is asserted to, or contextually typed as, one fixed-arity signature; `arguments` is then that signature's tuple.
  Forwarding it with `fn.apply(this, arguments)` passes `this` through.
  Reading `fn.name` on a wrapped function requires its name to be known where the wrapper is called, such as a function declaration or a `const`-bound function.
  `Parameters<F>` over a signature with optional or array-typed rest parameters is unsupported, because that tuple has no fixed length.
- **Function values:** Immutable aliases of overloaded program functions retain per-call overload resolution and identity.
  A function stored in a slot with a different signature remains the same function for equality, array searches, `length`, and own properties.
  A method value's `length` is the length of the method its object's class declares, whatever type the receiver is read through.
  The `length` of a function declaration, a static method, or a method of a JavaScript class counts a parameter with a default, where Node.js stops counting at the first one.
  Typed rest-parameter functions can be stored and called indirectly, including through a slot with fixed parameters, where the slot's arguments arrive in the rest array.
  That store is refused with `SC1090` when the program hands a function of the slot's type to a caller that passes more arguments than the type names, because the rest array would not receive them.
  Builtin function values have API-specific restrictions; see [Builtin function values](#builtin-function-values).
  A function called as a member of an object, a tuple, or an array (`holder.fn()`, `holder["fn"]()`, `holder.fn?.()`, `list[0]()`) receives that object as `this`, as in Node.js, and writes through `this` reach it.
  A timer calls its callback without a `this`, where Node.js passes the timer object.
  In a TypeScript source, a callback whose own body reads `this`, or whose signature declares a `this` parameter, is refused with `SC1090` when it is passed to a timer, a `process` listener, a `node:crypto` or `node:zlib` callback, or a stream option as a function value, where Node.js passes an object of its own.
  A function stored under a type that declares no `this` parameter is called without a `this` by a timer, an event emitter, a `process` listener, and a stream, where Node.js passes the timer, the emitter, `process`, or the stream.
- **JavaScript parameters:** In a JavaScript file TypeScript does not check, a prototype method (`C.prototype.m = function (...) {}`) takes whatever values its callers pass; its JSDoc types document the usual values without constraining them.
  A sloppy-mode function with named parameters may read `arguments` only where the aliasing of its parameters cannot show: it reads `arguments.length`, or reads elements and never writes a parameter or an element.
  Other uses of `arguments` in such a function are refused with `SC1090`; ES modules, `'use strict'` code, classes, and functions with default, rest, or destructured parameters have no such restriction.
  The `arguments` object is the list of the call's arguments, an array, where Node.js has an object that is not one.
  For the name itself or a constant that holds it, `Array.isArray` answers `false` as in Node.js, and the `concat` of an array or a string, or `JSON.stringify`, over it is refused with `SC1090`.
  `instanceof Array` and `Object.prototype.toString.call` answer as in Node.js in a function that only reads the object.
  These answers are the built-in functions' only: a method named `concat`, `isArray`, `stringify`, `toString`, `call`, or `apply` that the program defines, a binding that shadows `Array`, `Object`, or `JSON`, and a method called through an untyped receiver are called with the array.
  Converted to a string, printed, read for a member such as `constructor` or `slice`, tested with `instanceof` or for its tag in a function that also writes to it or passes it on, or tested by the code it was passed to, it is the array.
  A sloppy-mode function called with `undefined` or `null` as its receiver gets the global object as `this`, as in Node.
  Called with a primitive receiver, its `this` is the primitive itself, where Node passes the primitive's wrapper object (`typeof this` is `"object"` there).
- **Argument spreads:** Non-empty fixed tuples can spread into fixed signatures; arrays, Sets, and compiled class iterables can spread into typed rest parameters.
  Runtime-length spreads into fixed or rest parameters require types that support checked `unknown` conversion.
  Other JavaScript calls use the checked-value path; unsupported static forms produce a diagnostic.
  Calls with no native parameter slot are unsupported for runtime-length spreads.
  Arguments evaluate once, in source order, before defaults run, including surplus arguments.
- **Dynamic imports:** A specifier the build can bound to a finite set compiles to one load per value; the bound covers a string literal, a `const` or never-reassigned binding, string concatenation and template parts, `?:`/`??`/`||` branches, the pure String methods `replace`, `replaceAll`, `slice`, `toLowerCase`, `toUpperCase`, `trim` and `concat` over bounded values, and a parameter of a module-local function whose every call passes one of these.
  A specifier the build cannot bound is refused with `SC1090`, naming the source that stopped it.
  A value naming a supported Node builtin loads it natively.
  A value naming one of the program's own modules compiles that module and answers the one instance, however many static and dynamic edges reach it; a value that names no file rejects at run time with Node's `ERR_MODULE_NOT_FOUND`, so a missing `.js` variant never falls back to the `.ts` beside it.
  Import attributes, CommonJS namespace synthesis, and imports of shipped JavaScript packages are unsupported unless the target is a compiled ESM module.
  Literal `import()` of compiled ESM and TypeScript modules and of supported Node builtins compiles.
  A literal `import()` of a package that nothing imports statically loads the package in a Node island at the call; with `--node-islands=off` it is a build error that names the package.
  Evaluation is lazy and microtask-delayed, top-level `await` is honored, repeated imports share namespace identity, exported mutable bindings stay live, and namespace keys use Node's sorted order.
- **CommonJS `require`:** Literal `require()` calls of compiled modules, supported builtins, and JSON documents compile.
  Reading a top-level `const` or `let` require binding before its `require` runs throws the same `ReferenceError` as Node.js.
  A `var` binding that code above its `require` can reach is unsupported, and so is a read that compiles to no checkable expression, such as exporting the binding before the `require`.
  A `require()` of a relative path that names no file when the program is built throws the same `MODULE_NOT_FOUND` error as Node.js, where the call runs.
  The error has Node's `code` and `message` and no `requireStack` property.
  JSON documents are parsed at build time, and every `require()` of a document returns the same object.
  Destructuring the exports of a module supports plain and renamed properties, plus nested patterns for builtin sub-modules such as `util.types`.
  Computed keys work when they fold to a constant, such as a string literal or a `const` binding.
  A computed key whose value is only known at run time is unsupported.
  Any pattern works on a JSON document.
  Defaults, rest elements, and nested patterns over other modules are unsupported.
  A require cycle compiles when each of its modules assigns all of its exports before its first `require` and uses the other modules of the cycle only inside functions that its top-level code can't call.
  Other require cycles are rejected with `SC1016`.
  A top-level `return` ends the module's evaluation.
  It is unsupported when the module assigns exports after it, or when a function that exists before it runs reads a binding declared after it, such as a callback created above the `return` or a function declaration.
- **JSX:** JSX in `.tsx`, `.jsx` and JavaScript files compiles with the project's `jsx` setting: `"react"` with `jsxFactory`, `jsxFragmentFactory` or `reactNamespace`, and `"react-jsx"` or `"react-jsxdev"` with `jsxImportSource`, as well as the `@jsx`, `@jsxFrag`, `@jsxImportSource` and `@jsxRuntime` pragmas.
  The checker types JSX with these settings, and each element becomes the call TypeScript emits for it, importing the automatic runtime's functions from `<jsxImportSource>/jsx-runtime` or `jsx-dev-runtime`.
  `"jsx": "preserve"` and `"react-native"`, which keep JSX in the output, are refused.
  The call an element becomes must type-check: a factory or runtime function whose parameters do not accept what an element passes is reported at the element.
  With `"react-jsxdev"`, `jsxDEV` receives `undefined` as `self` where `this` has no declared type (in a function that is not a method), which is the value such a function observes when called without a receiver.

### Types and class definitions

- **BigInt:** BigInt filesystem stats are unsupported.
  Typed code cannot construct `BigInt64Array` or `BigUint64Array`; JavaScript that names either one gets the checked-dynamic element kinds, where elements are BigInts.
  Arbitrary-precision BigInts otherwise support literals, operators, conversions, arrays, records, classes, closures, unions, promises, typed Map keys and Set elements, Buffer 64-bit reads/writes, and DataView 64-bit access.
  Native `unknown` and JavaScript `any` storage preserve BigInts, including checked Map keys and Set elements.
  JSON serialization throws a `TypeError` unless a replacer converts the value.
- **Interfaces:** Interfaces explicitly implemented by compiled classes retain live object identity through checked native values.
  Method-signature interfaces and callable-property interfaces structurally implemented by class methods use the same representation.
  A call to a method signature of such an interface calls the class's method directly when the receiver is an instance of a compiled class, and a read of a property signature reads the class's field directly; object literals, accessors, and methods the program replaces on an instance or prototype use dynamic property lookup.
  Arrays of such interface values are native arrays whose elements keep their identity; methods without a native form for them run on the array's dynamic view, which shares its elements.
  Other data interfaces retain native record layouts and their copy-coercion rules.
  Generic methods require a statically resolved body; a `const` interface binding initialized directly with `new` can keep the concrete class for specialization.
  Unsupported member representations produce diagnostics.
- **Record shapes:** Records use exact native layouts.
  Passing `{ a, b }` where `{ a }` is expected can produce `SC2002`.
  Accepted flows into a strict field-subset shape copy the record; see [Record identity and property order](#record-identity-and-property-order).
  A type that reaches a graph of types so large or so entangled that one mapping of it takes more than 500,000 steps ends the build with `SC2016`, which names the type and points at its declaration.
  This holds under every tier, because the checked-dynamic form of the same value maps the same types.
  Declaring the value with a narrower type, or with `unknown` where the graph of types closes, compiles.
- **Unions:** Narrow a union before an operation whose members require incompatible native representations, such as reading `u.length` on `string | string[]`.
  Some union-to-union widening and unions combining function and data members are unsupported.
  Same-representation methods on class unions, `map` and `forEach` on array unions, and shared or joinable field reads compile.
  `console.log(u)` supports printing a whole union.
  A union may hold one promise beside other members, such as `T | Promise<T>` or `Promise<R | void> | R | void`: `await`, an async function's `return`, `Promise.resolve`, and `instanceof Promise` act on the member it holds, with Node's microtask timing.
  `instanceof` against one of the program's classes answers for the member the union holds as well, and is false for the promise.
  A promise member beside a member that declares `then`, two promise members in one union, and `console.log` of a union holding a promise are unsupported.
- **Dynamic slots:** A field, parameter, return value, variable or expression whose declared type has no native representation compiles as a checked dynamic slot instead of failing the build.
  The parts of the type that compile keep their native layout, and only the failing parts hold checked-dynamic values.
  Operations on those values run in the dynamic tier, and reading one into typed code checks it against the type that reads it, so a value that contradicts its declaration throws a `TypeError` when typed code first uses it rather than when it is stored.
  Each slot is an `SC6104` warning; `scriptc coverage --fail-on=dynamic-slots` fails on them, and `--tier=static-only` turns them back into build errors.
  A slot declared with the library's `PromiseLike<T>` is such a slot: it holds a promise or an object of the program with a `then` method, and `await`, `Promise.resolve`, `Promise.all`, `Promise.race`, and a call of its `then` ask the value as Node.js does.
  What a thenable hands over is checked against `T` where typed code reads it.
  A class whose `then` method has type parameters cannot be called through such a slot and throws `SC2020` when it is awaited; a `then` without type parameters, or an object literal, can.
  A promise given to one of the program's own types with a `then` method is refused with `SC2002`; declare the slot `PromiseLike<T>` instead.
  A member that the program adds to the library `Promise` type, such as `Promise<T> & { status?: string }`, cannot be written on a promise the program made: the write throws a `TypeError` when it runs, and the build reports it as an `SC6013` warning.
  Keep such state beside the promise, in a wrapper record or in a `WeakMap` keyed by the promise.
  Values that scriptc cannot produce at all, such as `BigInt64Array` instances, still fail where they are created.
  A promise typed with extra members, such as `Promise<T> & { settle(value: T): void }`, is such a slot: a promise that a Node island hands over keeps its members, while a promise the program made holds none of its own.
  Assigning a member to a promise the program made, as in `const p = new Promise(...) as Promise<T> & { settle(value: T): void }; p.settle = finish`, builds and throws when the statement runs: an `Error` that names the write with `SC1090` where the compiler sees how the promise was made, and a `TypeError` otherwise.
  Return the members beside the promise instead, such as `{ promise, settle }`.
- **Null and optional values:** `undefined` and `null` compile as standalone values and union members.
  Optional record and class fields and optional, default, and rest parameters also compile, including rest parameters typed `unknown[]` or `any[]`, whose functions take any number of arguments.
  A function value whose fixed parameters are all `unknown` or `any`, or that has none, can be stored where such a variadic function type without fixed parameters is expected: each call passes the arguments in order, `undefined` for the ones the caller left out.
  Other function values with fixed parameters cannot be stored there.
  A function value of such a variadic type can be stored where the program declares a function type with fixed parameters (a typed binding, parameter, result, field, member, or element): each call passes the declared arguments, a `void` result is discarded, and any other result is checked.
  That store is refused when the expected type's last parameter is optional, is `unknown` or `any`, or includes `undefined`, because the function would receive `undefined` where the call passes nothing.
  It is also refused with `SC1090` when the program hands a function of the expected type to a caller that can pass more arguments than the type names: a function type with more parameters, or a library function whose callback takes more.
  The stored function would not receive those arguments.
  Untyped code that receives the stored function calls the original, with every argument it passes.
  A variadic function passed directly to a library function that calls its callback with a fixed argument list is refused with `SC1090`.
  Event listeners, promise executors, timers, and `process.nextTick` pass such a function every argument, as do the methods of arrays whose elements are `unknown`.
  In a function expression written where a callback or listener is expected, `arguments.length` is the number of parameters that slot's function type declares.
  Handed where no function type is expected (an `any` or `unknown` slot, a JavaScript module's function, `Reflect.apply`), such a function expression is refused with `SC1090`.
- **Runtime-optional storage:** A binding, field, or parameter whose value comes from a read that can miss — an array element, a record key, or a read asserted with `!` — keeps `undefined` in its storage, and a call site that passes such a read adds that arm to the callee parameter's storage, so the callee receives every value the call site can produce.
  The checker's narrowing does not remove the stored arm: inside a `typeof` or `=== undefined` guard, lowering extracts the stored value through a checked narrowing.
  A parameter widened this way keeps the arm even when the call site's checker type has no `undefined`, so a construct that needs the declared type can refuse while an arm the program never wrote is still stored; the refusal's hint names both the narrowed type and the stored arms that block it.
  Bind the guarded value to a local declared as the narrowed type and pass that local when a construct needs the declared type.
  `scriptc coverage --explain-dynamic` lists each widened slot and the reads where the checker's type is narrower than its storage.
- **Static field updates:** Numeric static fields support `++` and `--` through the declaring class or an immutable alias.
  Updates through a subclass name are unsupported because JavaScript creates a separate property on the subclass.
- **Array subclasses:** A class that extends `Array` compiles: its instances are arrays, and `map`, `filter`, `slice` and the other methods that create arrays return instances of the subclass, as in Node.js.
  A generic subclass (`class Bag<T> extends Array<T>`), a class that extends such a subclass, and a subclass that declares `Symbol.species` are unsupported.
  A method whose result the subclass's constructor cannot construct throws a `TypeError`: the constructor's parameters must take the one length argument, and a `map` to another element type needs a constructor that adds no elements.
- **Local class expressions:** Static fields and static blocks in TypeScript files, decorators, private brands, and generic members are unsupported.
  Static methods read the enclosing function's bindings and are inherited by subclasses.
  A named class expression's methods and static members can refer to the class by its name.
  Supported expressions preserve fresh constructor identity and shared captures across constructors, methods, accessors, and arrow fields.
  Local inheritance requires a compiled base class.
  Constructors retain identity, `typeof`, `name`, and `length` through `unknown`; cast back to their exact type before construction.
  Construction and static-member access directly through `unknown` are unsupported.
- **Constructor-function inheritance:** Module-level JavaScript classes can extend compiled ordinary constructor functions.
  Constructors that return replacement objects, spread `super` arguments, and reflective static inheritance are unsupported.
  Supported `super` calls preserve receiver and field initialization order and capture the base prototype at class evaluation.
- **Symbol fields:** Instance fields require a stable module-level `Symbol()` or `Symbol.for()` binding with a literal description or constant string binding.
  A `[Symbol.toStringTag]` field or getter with a string value sets the tag that `String()`, `Object.prototype.toString` and `util.inspect` print; fields keyed by other well-known symbols are unsupported.
  Reassigned keys, runtime-computed descriptions, distinct symbols with the same description in one class, and copying symbol fields with rest bindings or `Object.assign` are unsupported.
  JavaScript object spreads copy represented own symbol fields.
  Equal registry keys share a field across modules.
  Uninitialized JavaScript fields start as `undefined`; TypeScript fields require an initializer or unconditional constructor assignment.
  Represented symbol fields and methods, including inherited members, remain accessible through checked values.
- **Unassigned fields:** A field that a method reads before the constructor has assigned it holds `undefined`, as in Node.js, when construction calls that method through `this`.
  This covers a method the constructor calls before the assignment, the override a derived class supplies for a method its base constructor calls, and a callback written in either that captures `this`.
  `typeof`, comparisons with `undefined` or `null`, truthiness tests, `??`, string conversion and `console.log` see the `undefined`.
  Arithmetic on an unassigned number field gives `NaN`, and reading a member of an unassigned object, array or string field throws a `TypeError`, as in Node.js.
  Any other use that needs the declared type throws a `TypeError` for an object, array, string or bigint field, and reads `NaN` for a number field and `false` for a boolean field, where Node.js passes `undefined` on.
  When a constructor hands the instance to other code before it has assigned a field, such as `register(this)`, a read of that field in that code does not see `undefined`: a number reads `0`, a boolean reads `false`, and using an object, array or string field stops the program.
  Assign the fields first, or include `undefined` in the field's type.
- **Class index signatures:** A class may declare `[key: string]: any` or `[key: string]: unknown`.
  The signature takes no storage: a name the class does not declare is an own property of the instance, absent until something assigns it, as in Node.js.
  Reads, assignments, computed keys, `in`, `Object.keys`, `JSON.stringify` and untyped views of the instance see the same property.
  An instance lists its declared fields before the added properties, as Node.js does when every added property is written after the fields exist.
  A class that could write one earlier is refused: a field initializer of the class or of a base class that mentions `this` or calls a function, or a base class constructor that calls a function or hands the instance out while a class below it declares fields.
  Assign such a field in the constructor instead.
  A signature with another value type or a number key is refused.
  A subclass of such a class cannot declare instance fields of its own; declare them in the class that has the signature, or assign them through it.
  Assigning a declared field through a computed key checks the value against the field's type and throws a `TypeError` on a mismatch, where Node.js stores the value.
  Compound assignment, `++`, `--`, `delete` and method calls on a name the signature admits, and object spread of the instance, are refused.

`Promise.all([work(1), work(2)])` infers a tuple.
Some tuple operations, such as `.at()` and `.concat()`, are unsupported.
If you need array operations on the result, declare the input as an array:

```ts
const jobs: Promise<number>[] = [work(1), work(2), work(3)];
const results = await Promise.all(jobs); // number[]
```

`Promise.allSettled` and `Promise.any` take the same `Promise<T>[]` input and return `PromiseSettledResult<T>[]` and `T`.

### Builtin APIs

A declaration in the standard library does not guarantee that an API compiles.
Unsupported declared APIs produce `SC2020`, with alternatives where available.
Check the [Node.js compatibility reference](/compatibility) for API-level support.

- **Text encoding:** `TextEncoder` and `TextDecoder` can be stored in fields, arguments, return values, arrays, and closures.
  Encoding supports UTF-8, including `encodeInto` into a Uint8Array or Buffer view.
  `new TextEncoder(...)` evaluates its arguments and ignores them, as Node does, so `new TextEncoder("utf-8")` is a UTF-8 encoder.
  Codec properties expose the canonical encoding and decoder flags.
  Codecs held in `any` or `unknown` storage keep their identity and streaming state, and dynamic code can call `encode`, `encodeInto` and `decode`, with options objects, and read their properties.
  Decoding accepts recognized WHATWG labels, including runtime labels and omitted or undefined encodings.
  Literal constructor options support `fatal` and `ignoreBOM`; literal decode options support streaming UTF-8, UTF-16, and single-byte encodings.
  Other legacy streaming encodings, runtime options objects, inspection, JSON conversion, and module-scope declarations using `var` or lacking an initializer are unsupported.
  Use initialized `let` or `const` bindings for stored codecs.
- **EventEmitter:** Literal event names use fixed argument tuples.
  Number, `null`, `undefined`, and boolean names register under `String(name)`, as in Node.
  Computed string names with a constant prefix or suffix that excludes `error`, meta events, and stream-internal events pass arguments of the exact arity with checked conversion.
  Other names reach an EventEmitter as through an untyped value: a name computed at run time converts with `String(name)`, then the name is matched against the program's literal event names when the call runs.
  The name expression is evaluated before it converts; a name whose static type is a string or number literal is a compile-time key only when reading it runs no program code (a literal, a `null`, `undefined`, `true` or `false` keyword, a binding read, a stored field or record entry read), and a call or an accessor lowers as a run-time name.
  On streams, those names and names that may reach internal events are unsupported.
  Symbol event names are supported: a symbol keys the registry by identity, so two `Symbol("s")` names are different events and a `Symbol.for()` name is one event at every call site.
  `eventNames()` lists symbol names after every string name, and the max-listeners warning renders a symbol name as `Symbol(description)`.
  Registering or removing a listener under a symbol name while a `newListener` or `removeListener` listener is registered is unsupported, as is `eventNames()` on an emitter with a registered `errorMonitor` listener.
  A stream's event names are strings: a symbol name is a type error at a typed call site and throws through an untyped value.
  Once the program registers a listener under an unrestricted name, typed `listeners()`/`rawListeners()` results are unsupported for events that literal names register with typed listeners.
  An emitter reached through an untyped value shares one listener list with its typed uses, so untyped registrations and emits reach typed listeners and typed emits reach untyped listeners.
  A `pipe` or `unpipe` emission reaches such a listener with a live view of the stream (its methods and properties), not the same object identity.
  An untyped `emit` of an event that has a listener with typed parameters converts its arguments to the event's typed tuple before any listener runs, and throws a `TypeError` when they do not convert.
  Through an untyped value, `listeners()` and `rawListeners()` of an event with a listener that has typed parameters are unsupported, and `emit('error')` of an object that is not an Error with no `error` listener is unsupported.
  A `newListener` or `removeListener` listener registered through an untyped value receives `undefined` in place of a listener registered with typed parameters.
  A typed `error` or `errorMonitor` listener throws a `TypeError` when an emitted payload that is not an Error does not convert to its parameter.
  Reading EventEmitter methods as properties through an untyped value (`typeof emitter.listeners`) answers `undefined`.
  `EventEmitter.prototype.<method>.call(receiver, ...)` runs the method typed when `receiver` is statically an emitter instance; a receiver of type `any` runs the untyped method over its JavaScript `_events`, which does not reach a compiled emitter's listeners.
  The method is read from `EventEmitter.prototype` or from an emitter reached through a binding, a stored field, or a record entry; an accessor or a call on that path is unsupported, since skipping it would drop its effects.
  `events.once` takes any options expression and validates it as Node does: `options` must be an object, only its `signal` property is read, and a non-AbortSignal value or an aborted signal rejects the promise.
  An EventEmitter takes a literal, computed, or symbol event name and a stream takes a literal one, including `data`; a computed name on a stream and the `errorMonitor` event are unsupported.
  Every other receiver takes the runtime path: an EventTarget resolves with the dispatched event and any other value rejects as Node does.
  `EventEmitter.captureRejectionSymbol` and `events.captureRejectionSymbol` read as Node's `Symbol.for('nodejs.rejection')` value and work as an event name; the `captureRejections` option and the rejection protocol behind it are unsupported.
  `events.on` lowers over an EventEmitter with a literal, computed, or symbol event name and over any other receiver through a checked-dynamic value, with the iterator's `close` names, `signal` aborts, 'error' handling and watermarks; a stream needs a literal event name and the `errorMonitor` event is unsupported.
  Reading `defaultMaxListeners` through a CommonJS destructured or member binding (`const { defaultMaxListeners } = require("events")`) is unsupported.
- **AsyncLocalStorage:** timer, interval, `setImmediate`, `process.nextTick` and `queueMicrotask` callbacks run with the stores that were active when they were scheduled, as in Node.
  The completion callbacks of `fs`, `crypto` and `zlib` callback APIs, `dns.lookup`, `child_process.execFile`, `server.listen`, `net.connect`, `http.request`, `http.get` and `dgram` run with the stores that were active at the call, as in Node.
  Events of `net`, `tls`, `http` and `https` sockets, servers, requests and responses run with the stores Node runs them with.
  A socket's or client request's events see the context of the call that created it, and a server's events and its requests' and responses' events see the context of its `listen()` call.
  Socket `write` and `end` callbacks, `'finish'` and `server.close(callback)` see the context of their call.
  Stream events and callbacks run with the stores of the code that triggers them, as in Node.
  Stream steps that Node defers with `process.nextTick` see the context that scheduled them, and `stream.finished` and `stream.pipeline` callbacks see the context of their call.
  `fs` read and write streams and `zlib` streams run their events and callbacks in the context that created them.
  Events of `child_process` children, their standard streams and their IPC channels see the context of the `spawn()` or `fork()` call; stdin's `'finish'` and `send()` callbacks see the context of their call.
  A client `http2` session's events see the context of its `http2.connect()` call, and a client stream's events the context of its `request()` call.
  An `http2` server's `'session'` and `'stream'` events and the compatibility API's request handler see the context of its `listen()` call; a server stream's own events, and its compatibility request's and response's, run with no store, as in Node.
  `AsyncResource` has no lowering yet.
- **Streams and readline:** Stream chunks are Buffers or strings, and on an object-mode side any value that converts to a checked-dynamic value.
  `NodeJS.ReadableStream` and `NodeJS.WritableStream`, and `stream.Readable` and `stream.Writable` under `@types/node`, accept every stream: when a program passes file, zlib, or `node:stream` streams through them, child-process pipes and `process.stdout` and `process.stderr` join those streams as `Readable` and `Writable` instances.
  `readable.pipe(process.stdout)` and `readable.pipe(process.stderr)` compile and, as in Node, do not end the process stream.
  When an untyped value reaches `NodeJS.ReadableStream` or `NodeJS.WritableStream`, those two interfaces hold any value, so JavaScript stream objects (constructor-function streams, plain objects with `on` and `pipe`) pass through TypeScript functions typed with them and come back to JavaScript as the same objects.
  Member calls on such a value use the native stream when it is one and call the object's own members otherwise; `readable.pipe()` into a JavaScript destination follows Node's pipe protocol.
  The first `pipeline` stage may arrive as an untyped value that is a native stream, a `Cipheriv` or `Decipheriv` value, or an iterable: a string, an array, a generator object, or any value with a `Symbol.asyncIterator` or `Symbol.iterator` method, which the pipeline pumps into the next stage as Node does.
  It may also be a source function, which the pipeline calls with `{ signal }` and whose iterable result it pumps, or a `{ readable, writable }` pair, whose readable side it pumps.
  Later `pipeline` stages that arrive as untyped values may be native streams, `Cipheriv` and `Decipheriv` values, functions, and iterables.
  A function is called with the previous stage and `{ signal }` as a function stage: before the last stage it must return an async iterable, and as the last stage it may return a promise, whose value resolves the pipeline, or an async iterable, which feeds the PassThrough that `pipeline()` returns.
  An untyped source function, function stage, or destination that returns a stream is unsupported.
  An iterable becomes `Duplex.from` of it, which the next stage reads from while the stages before it are not read, as in Node.
  A pipeline branches at run time on whether each of its first three untyped stages after the first is a function; a function in a later untyped stage, an untyped web stream, and a `{ readable, writable }` pair after the first stage throw a not-supported error.
  `for await` sources that arrive as untyped values must be native streams, or `Cipheriv` and `Decipheriv` values, at run time; other values throw a not-supported error.
  Native streams expose no `emit` to JavaScript, so a JavaScript stream whose own `pipe()` emits on its destination (the legacy `Stream` pipe) cannot pipe into one.
  `Readable.from` accepts strings, Buffers, arrays, Sets, Maps, Map and Set iterators, generators, async generators, and class iterables and async iterables, and in JavaScript any iterable or async iterable.
  In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported.
  The `objectMode` option, and the `readableObjectMode` and `writableObjectMode` options of a `Duplex`, `Transform`, or `PassThrough`, take `true` or `false` literals in an options object literal; options passed as a value are read when the stream is built.
  In typed code, a `write` or `transform` callback, or a `_write` or `_transform` method, may declare its chunk with any type that converts to a checked-dynamic value: the chunk converts when the callback runs, and a value of another type throws a TypeError there.
  An object-mode write passes its encoding argument to `write` and `transform` as Node does (`undefined` when absent, `null` when a callback took its place); a parameter declared `string` receives `""` for `undefined` and `null`, and one declared `string | null | undefined` receives Node's value.
  `readable.read()` on an object-mode stream answers any value it holds.
  `node:stream/consumers` and readline interfaces read object-mode streams of strings and Buffers; other values throw a not-supported error.
  `pipeline` accepts function stages anywhere among its streams: async generator functions (`async function* (source)`, iterating `source` with `for await`), an async generator source function (`async function* ({ signal })`), an async generator function as the destination, and an async function destination whose value resolves the promise form and reaches the callback's second argument.
  A stage is written inline, or named by a function declaration or a `const` bound to a function expression that nothing reassigns.
  The source may also be an array, a string, a `Set`, a `Map`, a generator or async generator object, or a synchronous generator function without parameters; their elements reach the next stage on the same ticks as in Node.
  Stages may take the `{ signal }` argument.
  With a function destination, `pipeline()` returns an object-mode PassThrough that receives an async generator destination's values, or an async function destination's value unless it is `null` or `undefined`.
  `@types/node` types that result as `NodeJS.WritableStream`, so `for await` over it needs the `PassThrough` type (`pipeline(...) as PassThrough`, or a JSDoc `@type`).
  In typed code, a pipeline callback with an async function or async generator destination receives `null` for no error when its error parameter cannot hold `undefined` (@types/node declares it `NodeJS.ErrnoException | null`), and `undefined` when it can, as in Node.
  `Readable.from` accepts `objectMode` and `highWaterMark` options.
  The `Readable` operators (`map`, `filter`, `forEach`, `toArray`, `some`, `find`, `every`, `flatMap`, `drop`, `take`, and `reduce`) accept `concurrency`, `highWaterMark`, and `signal` options, written inline or passed as a value.
  Their callbacks may take the `{ signal }` argument, `map` may return any value, and `flatMap` may return any iterable, async iterable, or stream.
  `Duplex.from` accepts an async generator function, which reads what is written to the Duplex, a `Duplex`, `Transform` or `PassThrough`, which it returns, and the values `Readable.from` accepts, which give a Duplex that is not writable.
  It also accepts a lone `Readable` or `Writable` and a `{ readable, writable }` pair, which the Duplex reads and writes through, a promise, whose value it reads, and an async function, which reads what is written and must resolve to `null` or `undefined`.
  A `Duplex.from` body function may take the `{ signal }` argument, which is aborted with the error the Duplex is destroyed with.
  `stream.compose` accepts streams and async generator functions in any position, an async function as the last stage, and an array, a string, or a generator as the first stage; one stage is `Duplex.from` of it.
  A web `ReadableStream` is read as an async iterable by `Readable.from` and as the first stage of `compose` or `pipeline`, and through its reader by `Readable.fromWeb` (with `objectMode`, `highWaterMark`, `encoding`, and `signal` in an options object literal) and `Duplex.from`.
  In TypeScript, `new TransformStream()` without arguments builds the identity transform: its readable side is a web `ReadableStream`, its writable side supports `getWriter`, `abort`, `close`, and `locked`, and the writer supports `write`, `close`, `abort`, `releaseLock`, `ready`, `closed`, and `desiredSize`, with Node's backpressure (a write completes when the readable side reads its chunk).
  In TypeScript, `tee`, `pipeTo`, and `pipeThrough`, the `WritableStream` constructor with an underlying sink, and a `TransformStream` with a transformer or queuing strategies compile: a program that uses one of them builds its `ReadableStream`, `WritableStream`, and `TransformStream` values with the same classes as JavaScript, and a `Response` or `Request` body has those members too.
  A TypeScript program that uses only `getReader`, `cancel`, `locked`, and the identity `TransformStream` keeps the smaller native streams.
  A web stream in a later compose stage is unsupported.
  `stream.isDestroyed`, `isErrored`, `isDisturbed`, `isReadable`, and `isWritable`, and `Readable.isDisturbed`, answer for the `node:stream` classes and their subclasses, for file and zlib streams, for the web stream classes and their readers and writers (including a `Response` body), for an untyped value that holds one of those, and for primitives.
  For any other value, such as a plain object or an array, they answer by the state members Node's duck typing reads, and for a child-process stream they answer only about the side the stream has.
  The `nodejs.stream.*` symbol properties a userland stream may carry are not read; its state members answer instead.
  `readableAborted`, `readableDidRead`, `readableEncoding`, and `writableAborted` read as in Node, on a typed stream and through an untyped value.
  `writableAborted` is `undefined` on a `Duplex`, `Transform`, or `PassThrough`, as in Node, so a `boolean` slot refuses that read with a TypeError.
  `stream[Symbol.asyncDispose]()` and `await using` work on a value typed as a stream class; on an untyped value they are unsupported.
  `stream.setDefaultHighWaterMark`, `stream.duplexPair`, `readable.wrap`, `Readable.toWeb`, `Writable.toWeb`, `Writable.fromWeb`, `Duplex.fromWeb`, and `Duplex.toWeb` are unsupported.
  The `ERR_INVALID_ARG_VALUE` message for a stream that is no longer readable or writable names the stream's class instead of printing the stream.
  `readable.compose(stage, { signal })` is `stream.compose(readable, stage)` with the signal added; the options are an object literal whose `signal` is a variable or a property read.
  `pipeline` from `node:stream/promises` accepts a trailing `{ signal, end }` object, with or without function stages, and `finished` accepts `readable`, `writable`, `error`, `cleanup`, and `signal` options, with boolean literals for the flags.
  Stream constructors accept a `signal` option in an options object literal, and `stream.addAbortSignal` accepts any stream.
  The `construct` option and `_construct` method, `decodeStrings`, and `defaultEncoding` work on every stream class, and the `writev` option and `_writev` method on every class with a writable side.
  A `decodeStrings` or `defaultEncoding` value known only at run time is unsupported after an option member whose value has effects (a call or an assignment).
  In typed code, a `writev` entry's `encoding` declared as a string receives `""` where Node passes `undefined` or `null` (an object-mode write).
  `setDefaultEncoding()` takes any encoding Node knows.
  `node:readline` and `node:readline/promises` (also reached as `readline.promises`) interfaces, from `createInterface(options)` or `createInterface(input, output, completer, terminal)`, read `process.stdin` or a readable stream: `question`, `prompt`, `write` (with a key object literal of `name`, `ctrl`, `meta`, and `shift`), `pause`, `resume`, `'line'`/`'close'`/`'pause'`/`'resume'`/`'error'`/`'history'`/`'SIGINT'`/`'SIGTSTP'`/`'SIGCONT'` listeners, and `for await` compile.
  `on`, `once`, and `addListener` return the interface, so registrations chain.
  The `history`, `historySize`, `removeHistoryDuplicates`, `completer`, `tabSize`, and `escapeCodeTimeout` options are validated as in Node.
  With `terminal: true`, or with no `terminal` option and `process.stdout` as the output when it is a TTY, the interface runs Node's terminal mode: the editing keys, the kill ring, undo and redo, history recording and recall, Tab completion through the completer, Ctrl+C, Ctrl+D, and Ctrl+Z, `TERM=dumb`, redrawing the prompt and line when the output's `'resize'` event arrives, and the same escape sequences written to the output.
  The cursor never stops between the two UTF-16 units of a supplementary character: where Node's Up or Down movement through a multiline line would, the cursor moves to the character's start or end.
  A `'history'` listener receives the interface's history array itself: the same object `rl.history` answers, in typed and JavaScript code alike.
  A change the listener makes lands in that array, so an entry it removes never reaches recall or the later history.
  `rl.history`, `historySize`, `historyIndex`, `crlfDelay`, `tabSize`, `escapeCodeTimeout`, `line`, `cursor`, and `terminal` read as in Node; `rl.cursor` is `undefined` on an interface that is not a terminal.
  `rl[Symbol.dispose]()` closes the interface, and `instanceof` with `readline.Interface` or `readlinePromises.Interface` answers whether the interface came from that module, as in Node.
  In JavaScript, the input and output can be any expression that evaluates to a stream, such as a function's result or an object's member; a value that is not a stream is unsupported.
  `readline.cursorTo`, `moveCursor`, `clearLine`, and `clearScreenDown` write to `process.stdout`, `process.stderr`, a writable stream, or nothing for `null` and `undefined`; their callbacks take no parameters.
  The `readline/promises` `Readline` class writes to the same outputs.
  `readline.emitKeypressEvents` emits `'keypress'` events on `process.stdin` or a readable stream, shared with terminal interfaces on that stream as in Node; a listener's first parameter must admit `undefined`, which it receives for escape sequences.
  Chained listener registration is unsupported.
  `node:stream/web` exports the same classes as the globals; in TypeScript, `ReadableStream`, `WritableStream`, and `TransformStream` have lowerings.
  In JavaScript, `ReadableStream`, `WritableStream`, and `TransformStream` with their default readers, writers, and controllers, `CountQueuingStrategy`, `ByteLengthQueuingStrategy`, `TextEncoderStream`, and `TextDecoderStream`, as globals and as `node:stream/web` exports, are constructor values with Node's prototypes, brand checks, and errors.
  Their underlying sources, sinks, and transformers, queuing strategies with a `size` function, readers and writers, `tee`, `pipeTo` and `pipeThrough` with their options and an abort signal, `ReadableStream.from`, and async iteration settle promises in Node's order, and the streams take own properties.
  `TextDecoderStream` decodes UTF-8; its other encodings are unsupported.
  A JavaScript `ReadableStream` given as a `Response` or `Request` body, or as `fetch`'s body, is read through a stream of its own: `response.body` is that stream rather than the one passed in, and the stream passed in is locked from the start.
  In JavaScript, a `Response` or `Request` body has the members of a `ReadableStream`, including `tee`, `pipeTo`, `pipeThrough`, `values`, and `for await`, and its lock and `bodyUsed` follow them.
  After one of those members is used, a body given to a new `Response` or `Request` is locked from the start, as a JavaScript `ReadableStream` is.
  In a program that names none of the web stream classes, those members are available when the program's JavaScript names one of them or iterates a body with `for await`; reached only through a computed key, they are not.
  In JavaScript, a byte stream (`type: 'bytes'`), a BYOB reader, a class that extends one of these classes, `Object.getPrototypeOf` on these values, their use as `WeakMap` and `WeakSet` keys, and `ReadableStream.from` of a function that is not iterable are unsupported.
- **HTTP/2:** Server push is not implemented: `pushStream`, `createPushResponse`, and the `'push'` event are refused.
  A client says so in its settings (`ENABLE_PUSH = 0`), so its `localSettings.enablePush` and the server's `remoteSettings.enablePush` for it are `false` where Node reports `true`.
  On the compatibility response, `'finish'` listeners run when `end()` returns rather than when the stream closes, so they run before the request's `'close'` and before an `end()` callback, and a response that never ended emits no `'finish'`.
  Trailers, `goaway()`, timeouts, `respondWithFile`, and the stream and session `state` records are refused; on a session or stream that reached untyped code they throw instead of being ignored.
  A `ping()` callback declares its duration and payload optional, because a cancelled ping calls back with the error alone, as in Node.
  A settings record with `customSettings` is refused, in `session.settings()` and in `getPackedSettings()` (where Node 24 answers `undefined`).
- **CommonJS metadata:** The module graph is fixed at build time.
  Cache deletion or reloading, metadata writes, `module.paths` mutation, and `require.extensions` are unsupported.
  Read-only access to `module.id`, `filename`, `path`, `paths`, `loaded`, `isPreloading`, `parent`, `children`, `require.main`, and `require.cache` lookup/enumeration compiles.
- **ESM metadata:** Native `import.meta.url`, `filename`, `dirname`, and `main` support dot access, statically known keys, and destructuring selected fields.
  Storing or mutating the metadata object, rest bindings, runtime keys, and stored `resolve` callbacks are unsupported.
  Native `import.meta.resolve()` requires a literal specifier.
  Module instances distinguished by URL query strings or fragments are unsupported.
- **Date:** Spread constructor arguments are unsupported; the locale formatters take the locales and options that `Intl.DateTimeFormat` takes (see Intl.NumberFormat and Intl.DateTimeFormat).
  Dates are objects with identity: `===` and `==` compare identity, `new Date(date)` makes a new Date, setters change the one Date every reference holds, and Dates compile in arrays (`includes`, `indexOf`, `lastIndexOf`, `join`, and default `sort` by `toString()` text), in unions with other types, as Map and Set keys and values, and in records, tuples, class fields, closures, promises and generators.
  Thrown and rejected Dates keep their identity; `structuredClone` and `postMessage` copy them.
  Zero-argument, single number/string, and local-time year/month field constructors, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `toLocaleString()`, `toLocaleDateString()` and `toLocaleTimeString()` (without arguments: the default locale's ICU patterns over the host time zone, as Node prints them), the local and UTC calendar getters and setters, `setTime`, `getTimezoneOffset`, and single-string `Date.parse` compile.
  Unary `+`, `-` and `Number(date)` read the time value; `String(date)`, template literals and `+` with a string read the `toString()` text; `date[Symbol.toPrimitive](hint)` compiles with a literal hint.
  A Date converted to `unknown` is the same Date object.
  See [Dates and timezones](#dates-and-timezones) for timezone data.
- **URL:** Absolute inputs and relative inputs with runtime string or URL bases, including optional bases, compile.
  `URL.canParse`, `URL.parse`, `toString()`, and `toJSON()` compile; the factories use the same parser as construction, returning `false` or `null` for rejected inputs while preserving conversion errors.
  `href`, `protocol`, `username`, `password`, `host`, `hostname`, `port`, `pathname`, `search`, and `hash` support assignment in native code.
  Invalid component writes are ignored except for invalid `href` values, which throw; credentials are percent-encoded and ports accept valid leading digits.
  Native URL assignments require statically known property keys; `+=` accepts primitive operands, while other assignment operators and object operands for `+=` are refused.
  `origin` is read-only.
  `searchParams` is a live view whose identity survives query and URL replacement.
- **Legacy URL API:** `url.parse()`, `url.format()`, `url.resolve()`, `url.domainToASCII()`, and `url.domainToUnicode()` compile, including Punycode hostnames and the `DEP0169`/`DEP0170` warnings.
  A parsed `Url` object supports reads, writes, and its `format()` method; its `parse()`, `resolve()`, `resolveObject()`, and `parseHost()` methods throw `SC2020`, and `url.Url` and `url.resolveObject()` are unsupported.
  `ERR_INVALID_URL` errors have no `input` property.
- **Deprecated `punycode` and `constants` modules:** `punycode.encode()`, `decode()`, `toASCII()`, `toUnicode()`, `ucs2.decode()`, `ucs2.encode()`, and `version` compile, with the `DEP0040` warning Node.js reports when the module loads.
  A `require("punycode")` made from inside `node_modules` doesn't warn; a `require()` used inside a larger expression, such as `require("punycode").toASCII(name)`, doesn't warn either.
  The `punycode.ucs2` object itself is unsupported as a value.
  The `constants` module is the frozen object Node.js builds from `os.constants` and `fs.constants`, plus the `crypto.constants` of Node.js 24.22.1.
- **Filesystem flags:** `fs.openSync` accepts a string flag name, an inline bitwise OR of `O_RDONLY`, `O_WRONLY`, `O_RDWR`, `O_CREAT`, `O_EXCL`, `O_NOFOLLOW`, `O_NONBLOCK`, `O_TRUNC` and `O_APPEND`, or a number the program computes from `fs.constants`, which are the target's own `open(2)` bits.
  The creation mode is a number or an octal string, and the `mode` option of `fs.writeFileSync` and `fs.appendFileSync` takes the same forms.
  The `flag` option of those two writes takes the same flag name, `open(2)` bits or computed number.
  `fstatSync`, `fchmodSync`, `fsyncSync`, and `linkSync` support direct calls.
  On Windows, numeric opens with `O_NOFOLLOW` and calls to `fchmodSync` throw `ENOSYS`; use a POSIX target if you require these operations.
- **Filesystem URL paths:** `fs` and `fs.promises` functions accept `file:` URL paths and convert them as `fileURLToPath` does; `existsSync` answers `false` and the `fs.promises` functions reject for a URL that does not convert.
  A URL path with options that are not literals is refused where Node checks those options before it converts the URL, except a `readFileSync` encoding.
  `fs.promises` functions also refuse a string path before a URL path; pass `fileURLToPath(url)` instead.
  In JavaScript, an untyped path of a synchronous `fs` function is checked as Node.js checks it: a string, a `file:` URL or a `Uint8Array` is the path, and any other value throws Node's `ERR_INVALID_ARG_TYPE`.
  `readFileSync`, `writeFileSync` and `appendFileSync` take a file descriptor only as a statically typed number; a number passed through an untyped value throws `SC2020`.
  A `Uint8Array` path must hold UTF-8 text; other bytes, which Node.js hands to the system unchanged, throw `SC2020`.
  `existsSync`, `realpathSync`, `truncateSync`, the callback functions and `fs.promises` report an untyped path that is not a string with a `TypeError` that has no `code`.
- **Copy:** `fs.cpSync`, `fs.cp`, and `fs/promises` `cp` support every option and follow Node's two implementations: `cpSync` reports the native binding's errors, and the callback and promise forms report `SystemError`s.
  Files, directories, and symbolic links are copied in directory order, as in Node.
  The callback and promise forms complete the copy before the callback runs or the promise settles.
  An async `filter` must return a promise that is already settled; a pending one throws `SC2020`.
  On Windows, the three functions throw `SC2020`.
- **Glob:** `fs.globSync`, `fs.glob` and `fs/promises` `glob` support every option.
  `withFileTypes` must be a literal `true` or `false`, and `exclude` functions cannot be combined with `withFileTypes`.
  The `fs/promises` iterator is supported only as the source of a `for await` loop, and its walk completes when the loop starts.
  `fs.glob` callbacks receive file names, not Dirents.
- **FileHandle and descriptor I/O:** Static reads and writes accept Buffer and Uint8Array windows, numeric positions, and UTF-8 strings.
  FileHandle reads also accept options records and allocate a buffer when omitted.
  Vector I/O, truncation, syncing, permission changes, descriptor ownership changes and timestamp updates compile.
  Bigint Stats remain unsupported; FileHandle streams and `readLines` are covered under File streams.
  Descriptor I/O accepts every ArrayBufferView and Buffer encoding when its arguments are only known at run time.
  A `readSync` position may be a bigint up to `2n ** 63n - 1n` less the read length.
  File operations complete synchronously before their callbacks or promise continuations.
  Descriptor permission changes on Windows throw `ENOSYS`.
- **File streams:** `fs.createReadStream`, `fs.createWriteStream`, `new fs.ReadStream` and `new fs.WriteStream` (in JavaScript sources), and FileHandle `createReadStream`, `createWriteStream` and `readLines` return Readable and Writable streams.
  Options are checked when the stream is created, with Node's errors and order; `flags` and `mode` are checked when the file opens, so their errors arrive as `'error'` events.
  `bytesRead`, `bytesWritten`, `path`, `pending`, `close()`, `close(callback)` with a `() => void` callback, and the `'open'` and `'ready'` events compile.
  The `signal` option destroys the stream with an `AbortError` when the signal aborts, as `stream.addAbortSignal` does.
  As in Node, a signal that is already aborted destroys the stream when it is created and the file is still opened afterwards, so `'open'` and `'ready'` follow `'close'` and that descriptor stays open.
  A program links that support when the options of one of its file streams write `signal` or have a type that can hold it, `any` and `unknown` included; in any other program, a signal that still reaches a file stream inside a wider object, behind an options type without `signal`, throws when the stream is created.
  Numeric `flags`, the `fs` and `windowsHandle` options, write streams with an encoding other than UTF-8, and `instanceof fs.ReadStream` or `fs.WriteStream` are unsupported.
- **File watching:** `fs.watch` returns an `FSWatcher` whose `on`, `once`, `off`, and `close` take `change`, `rename`, `error`, and `close` listeners.
  A listener may take the event name and the entry name, but the name arrives as a string (Node's default encoding), so a listener annotated with the `Buffer` arm of `@types/node`'s `change` overload has no lowering.
  Watching a directory on macOS with a listener that reads the entry name is refused with `SC2020` because FSEvents is not implemented yet; file watches and listeners that ignore the name work on every target.
- **Stat polling:** `fs.watchFile` polls the resolved path every `interval` milliseconds (default 5007) and calls its listener with `(current, previous)` `Stats`; `fs.unwatchFile` removes the last listener registered with that callback, or every listener when called without one.
  The call must be a statement of its own, because the `StatWatcher` it returns in Node.js is not lowered, and `bigint: true` is refused.
- **Crypto:** Hashing, HMAC, PBKDF2, and HKDF support every digest `crypto.getHashes()` lists in Node 24, with OpenSSL's aliases and object identifiers.
  Hash and Hmac accept every Buffer encoding and any TypedArray or DataView, `createHash` and `hash.copy` take `outputLength`, and `crypto.hash` takes its options object; HMAC, PBKDF2, and HKDF reject the SHAKE and KMAC digests with Node's errors.
  Hash and Hmac values are also Transforms: they can be written, piped into, used as `pipeline()` stages, read, and listened to, and they report Node's errors after the stream flushes.
  `copy()` of a Hash whose stream flushed, and stream use of a value after its stream closed, throw a coded SC2020 error.
  Through `unknown` and `any` slots and untyped JavaScript, Hash and Hmac values keep their identity and support `update()`, `digest()`, and Hash's `copy()`, called or read as values; their stream and event members there throw a coded SC2020 error.
  `timingSafeEqual` compares any view or ArrayBuffer.
  Secret KeyObjects (`createSecretKey`, `generateKey` and `generateKeySync` for `hmac` and `aes`) support `type`, `symmetricKeySize`, `export()`, `equals()`, and `instanceof KeyObject`, and createHmac, hkdf, createCipheriv, and createDecipheriv accept them; the ciphers and createHmac also take a WebCrypto secret `CryptoKey` and supply its bytes, reporting Node's DEP0203 deprecation once per process, and refuse a public or private `CryptoKey` with Node's key-type error; `KeyObject.from()` and `toCryptoKey()` are not available.
  `randomFill`, `randomFillSync` (every view kind and ArrayBuffer), `randomInt`, `randomUUID`, and `randomUUIDv7` follow Node 24.22, including callback timing; a `randomInt` callback whose error parameter is typed `Error | null` receives `null` where Node passes `undefined`.
  Scrypt supports its default parameters, numeric options, aliases, and memory limit.
  Both KDFs accept strings, Buffers, numeric typed arrays, DataViews, and fixed-length ArrayBuffers.
  `createCipheriv` and `createDecipheriv` support aes-128, aes-192, and aes-256 in CBC, CTR, GCM, CCM, ECB, CFB, CFB8, CFB1, and OFB modes and as key wrap (the wrap and wrap-pad ciphers and their -inv forms), des-ede3 in CBC, ECB, CFB, CFB8, CFB1, and OFB modes, des-ede in CBC, ECB, CFB, and OFB modes, chacha20, and chacha20-poly1305, on native targets, through `update()`, `final()`, `setAAD()`, `setAutoPadding()`, `getAuthTag()`, and `setAuthTag()`; `setAAD()` takes an empty value at any point before `final()`, including after the data, and refuses a non-empty one after data and any call after `final()` with Node's invalid-state error; other ciphers and wasm32-wasi builds are refused.
  `getCipherInfo` answers Node's info for every cipher OpenSSL names, including the ones without a native implementation.
  `getCipherInfo` options whose class defines `keyLength` or `ivLength` throw a coded SC2020 error.
  The result is Node's object; the SIV ciphers and the SM4 GCM, CCM, and XTS ciphers have no `nid`, and reading it where the declared `CipherInfo` type gives `number` throws a TypeError.
  `crypto.constants` members and their module-level copies read as Node's values, and the constants object itself is one runtime value whose keys, key order, and identity match Node's.
  Cipheriv and Decipheriv values are also Transforms: they can be written, piped into, used as `pipeline()` stages, read, iterated with `for await`, and listened to, and their stream properties read as in Node; written chunks go through `update()`, and the end pushes `final()` or reports its error through `'error'`.
  Through `unknown` and `any` slots and untyped JavaScript, in a program that uses streams, their stream properties and their `on`, `once`, `off`, `pipe`, `unpipe`, `read`, `pause`, `resume`, `setEncoding`, `write`, `end`, `destroy`, `cork`, and `uncork` members work; `write` and `end` take no encoding or callback there, and the other stream members throw a coded SC2020 error.
  Hash, Hmac, Cipheriv, and Decipheriv streams count a written string's bytes in `writableLength`, where Node counts the string's length.
  Decrypting chacha20-poly1305 without an authentication tag fails in `final()`; Node 24.22 returns unverified plaintext.
  After `update()` fails, for example past the AES-GCM limit of 2^36 - 32 bytes per message, every later call on that cipher throws; Node still lets `final()` run.
  An output encoding that is a function or an instance of a class defining `toString` throws a coded SC2020 error.
  `createECDH` and `ECDH.convertKey` support prime192v1, secp224r1, prime256v1, secp384r1, secp521r1, secp192k1, secp224k1, secp256k1, brainpoolP256r1, brainpoolP384r1, and brainpoolP512r1, on native targets, through `generateKeys()`, `computeSecret()`, `getPublicKey()`, `getPrivateKey()`, `setPrivateKey()`, and `setPublicKey()`, in compressed, uncompressed, and hybrid forms; the other curves `crypto.getCurves()` lists and wasm32-wasi builds are refused.
  A call on an ECDH value whose arguments match none of the overloads the program's declarations give it (`getPublicKey("HEX")`) is refused at compile time, and so is `computeSecret(view, encoding)` in a program typed by @types/node: Node reads that encoding as the key's input encoding and answers a Buffer where @types/node declares a string.
  The bundled Node declarations, which type every program without @types/node, follow Node's reading of each encoding argument of `generateKeys`, `computeSecret`, `getPublicKey`, `getPrivateKey`, and `ECDH.convertKey`: a falsy encoding or `"buffer"` answers a Buffer, any other encoding name a string, and an encoding held in a variable of a union type answers `Buffer | string`.
  ECDH property reads and writes throw a coded SC2020 error.
  `createDiffieHellman`, `getDiffieHellman`, and `createDiffieHellmanGroup` support the modp1, modp2, modp5, and modp14 through modp18 groups, explicit primes and generators, and generated safe primes, on native targets, through `generateKeys()`, `computeSecret()`, `getPrime()`, `getGenerator()`, `getPublicKey()`, `getPrivateKey()`, `setPublicKey()`, `setPrivateKey()`, and `verifyError`, with Node's private key lengths, DH\_check results, and errors; wasm32-wasi builds are refused.
  Primes longer than 8192 bits throw a coded SC2020 error; Node accepts primes up to 10000 bits for key generation and secrets.
  `setPublicKey()` and `setPrivateKey()` keys longer than 80000 bytes, not counting leading zero bytes, throw a coded SC2020 error, as does a generator that long with a prime below 512 bits; Node accepts them.
  A call on a DiffieHellman value whose arguments match none of the overloads the program's declarations give it, or whose output encoding is typed `any`, is refused at compile time, as are its `Object.prototype` methods; other property reads, and property writes, throw a coded SC2020 error.
  The bundled Node declarations follow Node's reading of each encoding argument of its `generateKeys`, `computeSecret`, `getPrime`, `getGenerator`, `getPublicKey`, and `getPrivateKey`: a falsy encoding or `"buffer"` answers a Buffer, any other encoding name a string, and an encoding held in a variable of a union type answers `Buffer | string`.
  `Object.keys()` and `in` do not see a DiffieHellman's own `verifyError` property, and `JSON.stringify()` and `instanceof` are refused.
  After `createDiffieHellman(primeLength)` throws `Invalid DH parameters`, the next OpenSSL error Node throws can list the failed generation's errors in `opensslErrorStack`; errors in a compiled binary list only their own.
  The error for key material that no decoder accepts has Node's `library`, `reason`, and `opensslErrorStack`, whose last entry is the reported error.
  When a PEM body begins with a valid structure and fails further in, the entries OpenSSL lists before that last one are missing, and a malformed public key or DER input is reported as `ERR_OSSL_UNSUPPORTED` where Node reports the ASN.1 parser's own code.
  `crypto.diffieHellman` computes ECDH and X25519 secrets from EC and X25519 keys, given as KeyObjects or in any form `createPrivateKey` and `createPublicKey` read, with Node's errors; DH keys have no KeyObjects here, and the `DiffieHellman` and `DiffieHellmanGroup` constructors are not available.
  `checkPrime`, `checkPrimeSync`, `generatePrime`, and `generatePrimeSync` support bigint, ArrayBuffer, typed-array, Buffer, and DataView values and the `checks`, `safe`, `add`, `rem`, and `bigint` options on native targets, with Node's errors; the callback forms compute before they return and call back in a later macrotask.
  Candidates and sizes longer than 262144 bits throw a coded SC2020 error, and wasm32-wasi builds are refused.
  Node calls their callbacks with `undefined` as the error, which a callback whose error parameter is typed `Error | null`, as @types/node declares it, receives as `null`.
  RSA, RSA-PSS, EC, Ed25519, and X25519 private and public KeyObjects come from `generateKeyPairSync`, `generateKeyPair`, `createPrivateKey`, and `createPublicKey`, on native targets.
  `generateKeyPairSync` and `generateKeyPair` check their arguments in Node's order, with Node's errors, including the OpenSSL errors for RSA modulus lengths below 512 or above 2147483647 bits and for public exponents of 0, 1, or an even number, which `generateKeyPair` passes to its callback.
  RSA keys generate with modulus lengths from 512 to 8192 bits, an odd length from 2048 bits yielding one bit less as in Node, and odd public exponents up to 4294967295; longer moduli that Node accepts, and `paramEncoding: 'explicit'`, throw a coded SC2020 error.
  The EC curves are prime256v1 (P-256), secp384r1 (P-384), secp521r1 (P-521), and secp256k1.
  Keys read and write PEM and DER as PKCS#1, SEC1, PKCS#8, and SubjectPublicKeyInfo, and JWK (Ed25519 and X25519 keys use PKCS#8, SubjectPublicKeyInfo, and JWK); `createPublicKey` also takes a private key or an X.509 certificate.
  EC, Ed25519, and X25519 keys also read and write the `raw-public` and `raw-private` formats, including compressed EC points.
  Raw 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.
  They support `type`, `asymmetricKeyType`, `asymmetricKeyDetails`, `export()`, `equals()`, and `instanceof KeyObject`.
  `crypto.sign`, `crypto.verify`, `createSign`, and `createVerify` support RSA PKCS#1 v1.5 and PSS padding and ECDSA with DER and IEEE P1363 encodings over SHA-1 and SHA-2 digests, and Ed25519 through `crypto.sign` and `crypto.verify` with a `null` or `undefined` algorithm.
  RSA-PSS keys follow their RSASSA-PSS-params as Node does: PSS padding only, the key's digest and MGF1 digest, and salts of at least the key's length, with OpenSSL's errors; their details name the parameters, they export as PKCS#8 and SubjectPublicKeyInfo only, and digests other than SHA-1 and SHA-2 in their options throw a coded SC2020 error.
  Ed25519 signing, verification, and private keys link when a string literal in the program names `ed25519` (in any case); without one they throw a coded SC2020 error, while Ed25519 public keys still import and export.
  X509Certificate supports `publicKey`, `verify()`, `checkIssued()`, `checkPrivateKey()`, `checkHost()`, `checkEmail()`, `checkIP()`, `toLegacyObject()`, `toString()`, and `toJSON()`, and `String()`, template literals, and `+` give its PEM text as `toString()` does.
  `publicKey`, `verify()`, and `checkPrivateKey()` are refused in wasm32-wasi builds, and an Ed25519 certificate's `verify()` needs the `ed25519` literal like the other Ed25519 operations.
  `issuerCertificate` is `undefined` for every certificate a program builds, as Node answers for one made from PEM or DER: scriptc's certificates carry no issuer chain and the read is typed `X509Certificate | undefined`.
  Since Node keeps every certificate member on the prototype, `in` finds the declared fields, `issuerCertificate`, and the methods, while `Object.hasOwn()` finds none of them.
  Own-key walks over a certificate (`Object.keys()`, `Object.entries()`, spread, `for...in`, and `hasOwnProperty`) are refused, since Node keeps its fields as prototype getters.
  `JSON.stringify()` and `console.log()` of a certificate, a certificate stored in an `unknown` or `any` slot, and its methods on a union-typed value are also refused.
  `Certificate` (the SPKAC class) supports `verifySpkac()`, `exportChallenge()`, and `exportPublicKey()` as statics and instance methods over a string or the bytes of a Buffer, typed array, DataView, or ArrayBuffer, and `new Certificate()` answers the stateless instance Node's does.
  Empty input and text no spkac decoder accepts answer an empty Buffer from the two exports and `false` from `verifySpkac()`, where Node answers an empty string.
  The signature is checked as RSA with the spkac's own public key, the form `openssl spkac` writes.
  `console.log()` of an instance prints `{}` where Node prints `Certificate {}`.
  Sign and Verify values are Writable streams: `write()` accumulates data as `update()` does, `end()` finishes the stream, and `pipe()` can pipe into one; they have no Readable side, as in Node.
  Private keys export encrypted (`cipher`, `passphrase`) with aes-128, aes-192, and aes-256 in CBC and ECB modes and with des-ede3-cbc, PKCS#8 as PBES2 with PBKDF2-HMAC-SHA256 and PKCS#1 and SEC1 as traditional PEM, and the ciphers OpenSSL refuses for a form throw its errors; a cipher option can also be an OID; other ciphers Node encrypts with throw a coded SC2020 error.
  Encrypted private keys import with a `passphrase` from PBES2 with HMAC-SHA PRFs and the CBC and ECB ciphers, and from traditional PEM with the CBC ciphers, with Node's errors for a missing or wrong passphrase; other schemes and ciphers throw a coded SC2020 error once a passphrase is given.
  The other key types (`dsa`, `ed448`, `x448`, and the rest) and wasm32-wasi builds throw a coded SC2020 error or are refused.
  Comparing keys of different types with `equals()` answers false; Node also leaves an OpenSSL error queued, which a later key operation can report.
  `publicEncrypt`/`privateDecrypt` remain unsupported.
  The WebCrypto `crypto` global, which node:crypto also exports as `webcrypto`, supports `randomUUID()` and `getRandomValues()`, also read as values from untyped JavaScript.
  Its `subtle` object, also node:crypto's `subtle` export, supports `generateKey()` and `exportKey()` for HMAC and AES keys, `importKey()` for HMAC, AES, PBKDF2, and HKDF keys, `encrypt()` and `decrypt()` with AES-GCM, AES-CBC, and AES-CTR and `wrapKey()` and `unwrapKey()` of secret keys with AES-KW or those ciphers on native targets, `deriveBits()` and `deriveKey()` with PBKDF2 and HKDF, HMAC `sign()` and `verify()`, and SHA-1 and SHA-2 `digest()`.
  Secret keys import and export in the `raw`, `raw-secret`, and `jwk` formats.
  `generateKey()`, `importKey()`, `exportKey()`, `sign()`, and `verify()` also support ECDSA (P-256, P-384, and P-521), Ed25519, RSASSA-PKCS1-v1\_5, and RSA-PSS, with the `spki`, `pkcs8`, and `jwk` formats and `raw` for ECDSA and Ed25519 public keys; ECDSA, Ed25519, and RSA are refused in wasm32-wasi builds.
  ECDSA, Ed25519, and RSA link when a string literal in the program names the algorithm; a name built only at run time throws a coded SC2020 error.
  `wrapKey()` and `unwrapKey()` of ECDSA, Ed25519, and RSA keys throw a coded SC2020 error.
  Other SubtleCrypto methods, algorithms, and key formats are refused or throw a coded SC2020 error.
  Native derivations finish before their deferred callbacks, so computation blocks the caller.
- **Assertions:** Assertion failures throw `assert.AssertionError` with Node's `message`, `generatedMessage`, `code`, `actual`, `expected`, `operator`, and `diff` properties.
  `new AssertionError(options)`, `instanceof`, and `assert.throws(fn, AssertionError)` compile.
  When `strictEqual` compares function values, `actual` and `expected` read as `undefined`; when `assert.throws` or `assert.rejects` reports an error that is not an instance of the expected class, or that does not match an expected object, `expected` reads as `undefined`.
  Values whose type can be cyclic, such as recursive records, also read as `undefined`.
  `assert.AssertionError` used as a value outside `instanceof` throws `SC2020`.
  The comparison methods, `ok`, `fail` and `ifError` are function values that assert as the methods do; called through a stored value, `ok` without a message reports `value == true`, where Node quotes the call, and `fail` takes at most one argument.
  `throws`, `rejects`, `doesNotThrow`, `doesNotReject`, `match` and `doesNotMatch` as values throw `SC2020`.
  `strictEqual` and `notStrictEqual` compare a number, string or boolean that can be `null` or `undefined`, such as `child.signalCode`, with any number, string, boolean, `null` or `undefined`.
  `notStrictEqual` of another value that can be `null` or `undefined`, such as `child.stdin`, against `null` or `undefined` compiles; `strictEqual` of one throws `SC2020`.
  `assert.deepEqual` and `assert.notDeepEqual` follow Node's loose comparison, including Maps, Sets, Dates, regular expressions, typed arrays, and errors; operands whose type can be cyclic or holds functions inside a structure are refused.
  `assert.partialDeepStrictEqual` follows Node's partial comparison over the same values, with the same refusals.
  Checked-dynamic values compared deeply have no cycle detection.
  Errors in failure messages print as `[Name: message]`, where Node prints the stack when only one side is an error.
  `new Assert(options)` compiles when it initializes a `const` with literal `diff`, `strict` and `skipPrototype` options, or none, and the binding is only used to call assertion methods.
  Other instances, such as ones passed as values, destructured, bound with `let`, or built from options that are not a literal, throw `SC2020`.
  With `skipPrototype`, `deepStrictEqual` and `notDeepStrictEqual` compare class instances, and operands of different static types, as checked-dynamic values.
- **Concurrent tests:** with `{ concurrency }` above 1, subtests run on their own fibers, and results are reported in declaration order, as in Node.
  Concurrent bodies resume in Node's order around `beforeEach` hooks and nested suites.
  Other runner steps, such as `afterEach` and `after` hooks, take no microtask turns between bodies, so code that observes the interleaving of concurrent bodies across those steps can see a different order than Node.
- **Snapshots:** `t.assert.snapshot` and `t.assert.fileSnapshot` compile, with Node's default serializer, `JSON.stringify(value, null, 2)`, or an `options.serializers` array literal.
  A compiled test reads the snapshot file Node writes beside the entry file, `<entry file>.snapshot`, at the entry file's path when the program was compiled.
  Running the binary with Node's flag `--test-update-snapshots` as its first argument writes the snapshot files instead; as in Node, the flag is kept out of `process.argv` and appears in `process.execArgv`.
  Snapshot files are read in the form Node writes them, ``exports[`key`] = `value`;`` statements.
  The `snapshot.setDefaultSnapshotSerializers` and `snapshot.setResolveSnapshotPath` module functions throw `SC2020`.
- **Reporters:** the binary writes the spec report, so `--test-reporter=spec` is the only reporter it takes, and any other name ends the run before the tests start with `scriptc: unknown test reporter '<name>': only the spec reporter is supported`.
  `--test-reporter-destination=stdout`, its default, sends the report to stdout, `stderr` sends it to stderr, and any other value names the file the binary writes the report to; the program's own output stays on stdout in every case.
  As in Node, both options are leading arguments, in any order, kept out of `process.argv` and listed in `process.execArgv`.
  A file destination that cannot be opened ends the run the same way, with `scriptc: cannot open test reporter destination '<path>': <reason>`.
- **TestContext values:** `t.filePath` is the entry file's path when the program was compiled.
  `t.attempt` is `0`, since a test runs once.
  `t.passed` is `false` while the body runs and the test's outcome in its `afterEach` and `t.after` hooks.
  `t.workerId` is `Number(process.env.NODE_TEST_WORKER_ID) || undefined`, as in Node.
  `t.tags` is `[]`, since the `tags` test option throws `SC2020`; the array is not frozen as Node's is.
- **Suite and hook contexts:** a `describe` body may take its `SuiteContext`, which has `name`, `fullName`, `filePath`, `attempt`, `passed` and `diagnostic`.
  Hooks may take their context, as in Node: the suite's for `before` and `after`, the test's for `beforeEach`, `afterEach`, `t.before` and `t.after`.
  The `SuiteContext` `signal` and `log` members, and hook `done` callbacks, throw `SC2020`.
- **Module-level hooks:** `before` hooks outside any suite run at their `before()` call, and the first top-level test waits for the async ones; `after` hooks outside any suite run after the last top-level test.
  Both get the root's context, named `<root>`.
  As in Node, a throwing root `before` fails each top-level test with its error, without running its body, and cancels the tests and suites inside each top-level suite.
  A throwing root `after` reports the entry file as a failed result, and the exit code is unchanged.
- **Timeouts:** when a test's `{ timeout }` elapses, its running subtests are cancelled, as in Node.
  Subtests do not take their parent's `timeout` as their own, where Node's do, and the subtests of a timed-out test with `{ concurrency }` above 1 run to completion before the test reports.
  A body that keeps running after its timeout and calls `t.test` again runs that subtest, where Node reports it as not started.
- **Thrown values:** a test that throws a value that is not an `Error` reports it in the failing section as `util.inspect` renders it, as Node does, for strings, numbers, booleans, objects, arrays, `null` and `undefined`.
  A thrown class instance that is not an `Error`, such as `throw new Point(1, 2)`, reports `[object Object]`, where Node prints `Point { x: 1, y: 2 }`.
- **Test names:** a test without a name, or with an empty one, takes its function's name or `<anonymous>`, as in Node, when the function's name is known at compile time: a function expression, a function declaration, or a `const` bound to an arrow or anonymous function.
  A name computed at run time, with a function the program received as a parameter, is used as given, so an empty one stays empty where Node uses the function's name.
  `test(fn)` and `test("", fn)` with such a function throw `SC2020`.
- **Current context:** `getTestContext()` returns the context of the test, suite or hook whose code is running, as in Node: `undefined` at module top level, the root's context in module-level hooks, the suite's in a suite body and in the suite's hooks, and the test's in its body, in the code it awaits, and in the timers, immediates, `process.nextTick` and `queueMicrotask` callbacks it schedules.
  Node returns, for a suite, a `TestContext` that is a different object from the `SuiteContext` passed to the suite body and its `before` and `after` hooks; in a compiled program they are the same object, so comparing them with `===` gives `true`.
  Completion callbacks of callback-style I/O APIs, such as `fs.readFile`, get `undefined`, as AsyncLocalStorage stores do.
- **Test mocks:** `mock.fn`, `mock.method`, `mock.restoreAll` and `mock.reset` compile, on the module's `mock` and on `t.mock`.
  `mock.method` mocks a function-valued field of a record, such as an object literal or an interface-typed object.
  Class instances, builtin objects, and object literals whose methods use `this` throw `SC2020`.
  Calls through the object record it as `this`, including calls of a method read off the object and called on its own, where Node records `undefined`.
  `object.method === mock` is `false`, because the field holds a wrapper of the mock.
  `mock.fn()` without an original returns `undefined` and records the arguments of each call, as in Node; with a function type whose return type does not include `undefined`, such as `mock.fn<() => number>()`, it throws `SC2020`.
  A mock function's `mock` context is a non-enumerable own property of the returned function, so `typeof` is `"function"` and `Object.keys` is empty as in Node; `"mock" in mockFn` and `Object.getOwnPropertyNames` see the property, where Node's proxy hides it.
  `mock.getter`, `mock.setter`, `mock.property`, `mock.module` and mock options throw `SC2020`.
- **Mock timers:** `mock.timers.enable`, `tick`, `runAll`, `reset`, `setTime` and `[Symbol.dispose]()` compile, on the module's `mock` and on `t.mock`, for the `setTimeout`, `setInterval`, `setImmediate`, `Date`, `scheduler.wait` and `AbortSignal.timeout` APIs.
  `enable()` takes no options or an object literal whose `apis`, when given, is an array literal.
  Options and `apis` lists that are not literals, and the `MockTimers` object used as a value, throw `SC2020`.
  As in Node, the global timer functions, `Date.now()`, `new Date()` and `scheduler.wait` are mocked, and other calls through named and namespace imports of `node:timers` and `node:timers/promises` use real timers.
  As in Node, once a reset gives `scheduler.wait` back, calling it throws `ERR_INVALID_THIS`.
  While `scheduler.wait` is mocked, calling it with a `signal` option throws an `SC2020` error.
  A mock timer's handle converts to a negative number, where Node's mock `Timeout` converts to `NaN`.
  When two trackers have timers enabled, clearing a timer through the tracker that did not create it leaves the timer queued, where Node removes the entry at the same queue position in the clearing tracker.
- **Exit status:** Numeric `process.exitCode = value` writes in statement position set the implicit exit status, which `process.exit()` uses when called without an argument.
  `process.exit(code)` and `process.exitCode` throw a `RangeError` (`ERR_OUT_OF_RANGE`) for a code that is not a safe integer, and convert other codes to int32, as Node does.
  Reading `process.exitCode` answers the assigned code, or `undefined` before any assignment; `process.exit(code)` assigns it before the `'exit'` listeners run.
  Resets (`process.exitCode = undefined`) and assignments used as values are unsupported.
- **Process memory:** `process.memoryUsage()` reads `rss` from the operating system and `heapTotal` and `heapUsed` from the runtime's allocator, so the heap figures start near zero and grow as the program allocates.
  `arrayBuffers` is the storage of the ArrayBuffers, typed arrays, Buffers, DataViews and SharedArrayBuffers the program holds, and `external` is the same storage without SharedArrayBuffers, as in Node.
  Node also counts its own startup allocations in both, so a program that holds no such storage reports `0` where Node reports a few megabytes.
  In a program with worker threads, `external` and the non-shared part of `arrayBuffers` count the calling thread's storage.
  The storage comes from the same allocator, so it also counts in `heapTotal` and `heapUsed`.
- **V8 heap statistics:** `node:v8` can be imported.
  `v8.getHeapStatistics()` returns Node's fields in Node's order, computed from the runtime allocator like `process.memoryUsage()`: `total_heap_size` and `total_physical_size` are the bytes the heap holds, `used_heap_size` the bytes allocated, `external_memory` the same figure as `process.memoryUsage().external`, `heap_size_limit` the machine's memory (the heap has no fixed ceiling), and `total_available_size` the limit minus the used bytes.
  Fields that describe V8 itself (`total_heap_size_executable`, `malloced_memory`, `peak_malloced_memory`, the global handle sizes, and `does_zap_garbage`) are `0`, and `number_of_native_contexts` is `1`.
  The values are approximate and are not V8 heap figures.
  The other `node:v8` members observe the V8 engine, such as heap snapshots, profiles, flags, and serialization, and throw `SC2020` where they are used.
- **Performance entries:** `performance.mark()`, `measure()`, and the `getEntries` methods return entries with Node's fields, `toJSON()`, inspection, and identity.
  `Object.keys`, `for...in`, object spread, and `hasOwnProperty` on an entry are unsupported, and an entry stored in an `unknown` slot prints as a plain object.
- **Event-loop metrics:** `performance.eventLoopUtilization()` counts the main thread's CPU time since the event loop started as active time, so blocking system calls count as idle where Node counts them as active.
  `monitorEventLoopDelay()` supports `enable()`, `disable()`, `reset()`, `percentile()`, and the `min`, `max`, `mean`, `stddev`, `count`, and `exceeds` readings; the BigInt readings, `percentiles`, and non-literal option objects are unsupported.
- **Node timing milestones:** `performance.nodeTiming` answers the class's own fields, `toJSON()`, inspection, and the `JSON.stringify` that runs `toJSON()`.
  `nodeStart`, `v8Start`, `bootstrapComplete`, and `environment` are 0 because a compiled program has no V8 bootstrap, while `loopStart`, `loopExit`, `idleTime`, `duration`, and `uvMetricsInfo` are live.
  A synchronous program's exit listeners read `loopStart` and `loopExit` as set, because Node's loop starts and stops before them, and `uvMetricsInfo.loopCount` counts the loop turns the program took.
- **Builtin module lookup:** `process.getBuiltinModule()` with a string-literal id that `@types/node` types as a supported module answers that module, as an import of it does, also behind `?.`.
  With any other id it exposes native subsets of `path`, `path/posix`, `path/win32`, `os`, `util/types`, `node:sea`, and current-thread `worker_threads` metadata, and answers `undefined` for an id Node.js does not have.
  Stored module references and functions retain identity.
  Other modules and exports, including stored `Worker` constructors, throw `SC2020`.
  Direct `Worker` imports use the native worker support described below.
  `process.versions` is a shared dictionary containing `node` and `openssl`; other component versions are absent unless defined by the program.
- **Compile cache:** `module.enableCompileCache()` answers `{ status: module.constants.compileCacheStatus.DISABLED, message }` and creates no directory, because a compiled program loads no JavaScript whose compiled form could be cached.
  The `message` is Node.js's own when `NODE_DISABLE_COMPILE_CACHE` is set, and otherwise says that a compiled program has no compile cache.
  `module.getCompileCacheDir()` answers `undefined` and `module.flushCompileCache()` writes nothing.
  The calls validate their arguments as Node.js does, and they never start Node.js in a program that has a Node island.
- **Single executable applications:** a compiled program is not a Node.js single-executable application, so `node:sea` answers what Node.js answers outside one.
  `isSea()` is `false`, and `getAsset()`, `getRawAsset()`, `getAssetAsBlob()`, and `getAssetKeys()` check their arguments and then throw `ERR_NOT_IN_SINGLE_EXECUTABLE_APPLICATION`.
- **ArrayBuffers and typed arrays:** Fixed-length ArrayBuffers support shared typed-array and DataView storage.
  Resizing and transferring are unsupported.
  Typed code cannot construct `BigInt64Array` or `BigUint64Array`; JavaScript that names either one gets the checked-dynamic element kinds, where elements are BigInts.
  `Float16Array` stores IEEE 754 half-precision values, rounding each stored number to nearest-even as Node does; `Math.f16round`, `DataView.prototype.getFloat16` and `setFloat16`, and `util.types.isFloat16Array` compile.
  The typed-array callback methods (`forEach`, `map`, `filter`, `some`, `every`, `find`, `findIndex`, `findLast`, `findLastIndex`, `reduce`, `reduceRight`) compile; `map` and `filter` return the receiver's kind, a Buffer for a Buffer, and a `thisArg` argument is unsupported.
  Numeric typed arrays and Buffers preserve their kind and shared storage through `unknown`, including when nested in records and arrays.
  Numeric typed-array constructors can be stored and used with `new`, including constructors read from an array's `constructor` property.
  JavaScript numeric-array parameters and returns preserve typed-array storage.
  `Buffer.from`, `Buffer.allocUnsafe`, and `Buffer.concat` return views into a per-thread pool of `Buffer.poolSize` bytes (64 KiB by default) when the size is below `Buffer.poolSize >>> 1`, as in Node; a pool slab stays in memory while any of its Buffers does.
  Node's own startup code allocates from the first slab, so the `byteOffset` of Buffers in that slab differs from Node's.
  Buffers returned by other Node APIs, such as `fs.readFileSync` and `zlib`, own their `ArrayBuffer`, where Node allocates small ones from the pool.
- **Blob and File:** `new Blob()` and `new File()` accept arrays, typed arrays, `Set`, and `Map` as sources.
  Other iterables throw `SC2020`.
  `blob.stream()`, `blob.textStream()`, `URL.createObjectURL()`, and `stream/consumers` `blob()` are unsupported.
  Blobs cannot be sent to workers.
- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion.
  A module-level JavaScript class's prototype can become the prototype of other objects: `Object.create(C.prototype)`, `Object.setPrototypeOf(F.prototype, C.prototype)`, `Object.setPrototypeOf(object, C.prototype)`, and `util.inherits(F, C)` with an ordinary constructor `F`.
  The inherited methods and accessors run with the inheriting object as `this`.
  Methods that use `#private` members or `super` throw a TypeError for a receiver that is not an instance of the class.
  `C.prototype` also reads as a value for `Object.getPrototypeOf` and for `===` and `!==` comparisons.
  Changing the prototype of a class prototype (`Object.setPrototypeOf(C.prototype, X)`, `util.inherits(C, F)`, or `Object.setPrototypeOf` on a class prototype reached through an instance) is unsupported.
  Other bare prototype reflection, TypeScript class prototypes as values, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported.
  Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly.
  Named reads such as `value.valueOf` answer these inherited members; computed-key reads such as `value[key]` do not.
  In a TypeScript source, `instance.constructor` on an instance of a program class is the instance's own class, whatever class the static type names.
  Its `name`, comparisons with a class, `new (this.constructor as typeof C)(...)`, and statics read or called through `(this.constructor as typeof C).member` answer as in Node.
  The constructor of a generic class's instance answers its `name` only, and `constructor` is unsupported in a hierarchy that includes a function-local class extending another function-local class.
  Binding `this.constructor as typeof C` to a variable is unsupported when a subclass takes different constructor arguments, and so is `constructor` read through the static type of an array, Map, Set, function, or builtin error.
  Static accessors of TypeScript classes compile, including inherited ones and `super` in a static member; compound assignment and `++` on a static accessor are unsupported.
  `Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported.
  The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called.
  `new` through a stored `Object`, `Date`, `Map`, `Set`, `WeakMap`, or `WeakSet` builds the same value as the `new` expression.
  `new` through a stored `RegExp` or `Promise`, `new` of a stored `Object` with a primitive argument, and `instanceof` against these constructors are unsupported.
  `instanceof Object` and `instanceof Function` on untyped values follow the prototype chain as in Node, so an object built with `Object.create(null)` is not an `Object`.
- **Built-in members:** A program that replaces, deletes or shadows a member of one of the language's built-in objects and also uses that member natively is refused at build time, a deliberate difference from Node.js.
  Compiled code reaches a built-in such as `Array.prototype.push` or `Math.random` directly and never reads it from its object, so that use would keep running the built-in.
  The write is refused with `SC1092`, which names the write and the use: an assignment, `delete`, `Object.defineProperty`, `Object.defineProperties`, `Object.assign`, or a `Reflect` call whose target is a built-in constructor, its `prototype`, `Math`, `JSON`, `Reflect`, or the global object.
  A use is a call of the member through a value of the built-in, a call or a read through the built-in object, another built-in that reads it (`promise.catch()` reads `then`, `String(array)` reads `join`), or a language construct that reads it: a string or numeric conversion (`toString`, `valueOf`, `Symbol.toPrimitive`), an iteration (`Symbol.iterator`), an awaited or resolved value (`then`), or `JSON.stringify` (`toJSON`).
  The write and the use may be in different modules or packages, and the rule applies to TypeScript and JavaScript sources alike.
  Adding `then`, `toJSON`, `Symbol.iterator` or `Symbol.toPrimitive` to a built-in prototype that does not have it counts as such a write.
  A write whose member nothing in the program uses natively compiles as it did before this rule.
  The readers that do see a change are a value read of the member through `Array.prototype` or through an untyped value, `for…of`, array spread and `Array.from` over an untyped value, and the `Map`, `Set`, `WeakMap` and `WeakSet` constructors reading their adder.
  A use that runs before every write of the member, such as `const original = Date.now` at the top of the module that then replaces `Date.now`, is not counted.
  A polyfill guarded by a test of the member, such as `if (!Array.prototype.at) { ... }`, compiles: the member exists in Node 24, so the guarded code never runs, and a program that also changes the tested member is refused.
  Redefining a member with the same value to change its attributes, assigning `Error.stackTraceLimit`, and a write Node itself rejects, such as an assignment to `Math.PI`, are not refused.
  A write the build cannot see, where the object or the key arrives as a value, is not refused: `Math`, `JSON` and `Reflect` members throw at run time, and for other built-ins compiled code keeps calling the built-in.
  To change what a built-in does, for a test mock for instance, call a function of your own and replace that.
  A package that contains a refused write runs in a Node island under the default `--node-islands=auto`.
- **Intl.NumberFormat and Intl.DateTimeFormat:** Formatter objects compile, with their options and `format()`, over ICU 78 (the release Node.js 24 bundles).
  The options object is read when the program runs, by the steps ECMA-402 gives, so it can be a variable or hold computed values, and an invalid option throws the error Node.js throws.
  `Number.prototype.toLocaleString`, `Date.prototype.toLocaleString`, `toLocaleDateString` and `toLocaleTimeString` take a locale and options and build the same formatters.
  The locale is the default locale (no argument, or `undefined`), `"en-US"` or `"en"`; any other locale and a list of locales are refused, because which locale Node.js picks for a requested tag is not modelled.
  `formatToParts`, `formatRange`, `formatRangeToParts`, `resolvedOptions`, `supportedLocalesOf`, detached `format` methods, BigInt and numeric-string inputs, and the `calendar` and `numberingSystem` options of `Intl.DateTimeFormat` are unsupported.
  The other `Intl` constructors (`Collator`, `PluralRules`, `ListFormat`, `RelativeTimeFormat`, `DisplayNames`, `DurationFormat`, `Locale`) and `localeCompare` with a locale argument are unsupported.
  A program that uses these forms links ICU and needs the data file `icudt78l.dat` (18.7 MiB) beside the executable; `scriptc build` puts it there.
  Time zone rules and names are those of the ICU release, which can be older than the time zone data Node.js ships: Node.js 24.22 prints the offset for the names `EST5EDT`, `CST6CDT`, `MST7MDT` and `PST8PDT` and `UTC` for `GMT0` where scriptc prints the zone's name, and the two disagree about `Africa/Casablanca` and `Africa/El_Aaiun` from October 2026 on.
- **Intl.Segmenter:** Unicode grapheme segmentation, iteration, and `containing()` compile, with the default locale or one language tag of the form language\[-script]\[-region] written as a string literal (grapheme clusters do not depend on the locale).
  Word granularity compiles too, over ICU's UAX #29 word iterator at that locale; its segments carry `isWordLike` exactly as Node.js's do.
  In JavaScript dependencies, sentence segmenters construct, and their `segment()` throws an SC2020 error; in TypeScript sources the construction is refused.
  Locale negotiation over several locales, sentence segmentation, `resolvedOptions()`, and detached methods are unsupported.
- **Globals:** Stored `globalThis` references support identity and probes for absent capabilities.
  A name that Node.js 24 defines but scriptc does not implement, such as `WebSocket`, `FormData`, `BroadcastChannel`, or `URLPattern`, is not an absent capability: reading it (`typeof X`, `X`, `globalThis.X`, `globalThis["X"]`, or through a stored `globalThis`) answers Node's constructor rather than `undefined`, so the value carries Node's `name` and `length`, `typeof` answers `"function"`, and `x instanceof X` answers `false` for every value because native code never produces an instance.
  The value carries `name`, `length` and `prototype`; reading any other member, such as the class's static `WebSocket.OPEN` or a method on its prototype, raises `SC2020` naming the global, because native code does not provide that surface.
  Constructing or calling that value raises `SC2020` naming the global, with `host-global` as the Node island reason; in JavaScript the raise is the throw of the reached `new X(...)` or `X(...)` statement, so a module that reads the global at load time still runs.
  Probes for capabilities Node.js 24 also lacks, such as `Bun`, `Deno`, or `Temporal`, still answer `undefined`.
  In JavaScript, reading a builtin global through the global object by name (`globalThis[name]`, or a stored global object) answers what the global's own spelling reads, and builtin module members that are global objects in Node, such as `require("buffer").atob` and `require("timers").setTimeout`, are the same values.
  `WebAssembly` read this way, and builtin members read through a global object that untyped code received, can raise `SC2020`.
  `for (key in globalThis)` visits Node's enumerable globals in Node's order, then the program's own global properties.
  `key in globalThis` and `Reflect.has(globalThis, key)` answer for the names of Node's global object, the names it inherits from `Object.prototype`, and the program's own global properties.
  A global object that untyped code received answers `false` to `in` for every name.
  In JavaScript, a name no declaration binds reads the program's own global property of that name, as in Node: after `globalThis.X = class X {}` or `global.X = function () {}`, in the same module or another, `X`, `new X()`, `X()` and `typeof X` use that value; with no such property, a read throws `ReferenceError: X is not defined` and `typeof X` is `"undefined"`.
  A sloppy-mode function's `this.X = value` write lands on that global object when it runs with no receiver (a plain call, or `call`/`apply` with `undefined` or `null`), so the name reads the property afterwards too.
  `navigator` answers Node's `hardwareConcurrency`, `language`, `languages`, `platform`, and `userAgent`, with `language` derived from `LC_ALL`, `LC_MESSAGES`, or `LANG` as Node does.
  `navigator.locks` raises `SC2020`, as does `navigator.language` for a locale outside the `language_TERRITORY.codeset@modifier` form, or one whose language or territory needs ICU's tables (a three-letter territory, for example).
  `Temporal` is not defined, as in Node.js 24: `typeof Temporal` is `"undefined"`, `globalThis.Temporal` is `undefined`, and any other use throws `ReferenceError: Temporal is not defined`.
  Programs that name it type-check against TypeScript's `Temporal` declarations.
- **Map and Set:** Keys and elements support numbers and strings by value, and records, classes, arrays, functions, symbols, server handles, and unions of these reference types by identity.
  Map values may also be functions.
  `unknown` slots use native checked values, comparing supported primitives by value and retaining supported object references.
  Typed nested collections other than `Map<unknown, unknown>` and `Set<unknown>`, and promises requiring payload adapters, cannot pass into these slots.
  `Promise<unknown>` retains its native representation.
  Stored `keys()`, `values()`, and `entries()` iterators compile, retain element identity, and observe live collection mutations until exhausted.
  They support `.next()`, `for...of`, array spreads, and mapper-less `Array.from`.
  They also support the iterator helper methods, such as `.map()` and `.take()`.
  JavaScript sources compile a method value read from Set, Map, WeakMap, or WeakSet: calling it with `.call` applies the method to that receiver and throws Node's brand-check TypeError when the receiver is not the matching collection.
  Typed sources still refuse a method read as a value with SC1090.
  Constructing a Map, Set, WeakMap or WeakSet, or calling `Object.fromEntries`, with a checked-dynamic iterable (a native iterator, a generator, or an object with a `Symbol.iterator` method) takes all of its values before adding the first one.
  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 Set composition methods (`union`, `intersection`, `difference`, `symmetricDifference`, `isSubsetOf`, `isSupersetOf`, `isDisjointFrom`) accept a Set of the same element type or a Map keyed by it; other set-like arguments are unsupported.
- **util.types:** The type predicates answer the value's internal brand, as Node.js's do.
  `isArgumentsObject` answers true asked of `arguments` itself or of a constant holding it.
  An arguments object that has left for an untyped slot, including one reaching a stored predicate value, is the argument list, an array, and answers false where Node.js answers true.
  `isModuleNamespaceObject` answers true asked of an `import * as` binding, a constant holding one, an awaited `import()`, and a namespace that reached an untyped slot — a builtin's ES module namespace and a program module's namespace, whether the source is TypeScript or JavaScript.
  Builtin sub-module members reached through another module, such as `fs.promises` and `util.types`, answer false, as in Node.js.
- **Compression:** The one-shot `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `gzip`, `gunzip`, and `unzip` functions and their `Sync` forms accept Node's options, validated at run time, including `dictionary`, `maxOutputLength`, and `rejectGarbageAfterEnd`.
  Typed inputs are strings, Buffers, and Uint8Arrays; untyped JavaScript values may also be other TypedArrays, DataViews, and ArrayBuffers.
  A literal `info: true` in an options object answers Node's `{ buffer, engine }` result in the sync, callback and promisified forms, and an options value whose type does not carry the literal `true` still takes the Buffer form and refuses.
  The `Deflate`, `Inflate`, `Gzip`, `Gunzip`, `DeflateRaw`, `InflateRaw`, and `Unzip` streams, from `createGzip()` and the other factories or `new zlib.Gzip()`, accept Node's options and support writes, piping, `pipeline`, async iteration, `flush`, `params`, `close`, `reset`, and `bytesWritten`.
  Each output chunk is its own Buffer rather than a slice of a shared chunk-size buffer.
  `zlib.constants` members and their module-level copies read as Node's values.
  The Brotli one-shots `brotliCompress` and `brotliDecompress` and their `Sync` forms, and the `BrotliCompress` and `BrotliDecompress` streams, accept Node's options, including `params` and `dictionary`, and produce the same bytes as Node.
  The zstd one-shots `zstdCompress` and `zstdDecompress` and their `Sync` forms, and the `ZstdCompress` and `ZstdDecompress` streams, accept Node's options, including `params`, `dictionary`, `pledgedSrcSize`, and `rejectGarbageAfterEnd`, and produce the same frames as Node.
  `util.inspect` is unsupported.
- **Synchronous iteration:** Checked `for...of` follows live iterator methods, including compiled class methods and abrupt-completion cleanup.
  Mapper-less checked `Array.from` accepts those iterators and plain array-like objects.
  Sparse checked arrays and general borrowed iterator factories are unsupported.
  Direct statically typed class loops accept iterators that are class instances or object literals; a loop that exits early calls the iterator's zero-parameter `return()`, as Node's IteratorClose does.
- **Iterator helpers:** `map`, `filter`, `take`, `drop`, `flatMap`, `toArray`, `forEach`, `reduce`, `some`, `every` and `find` compile on generators, on array, typed array, string, Map and Set iterators, and on the objects the helpers return.
  Helper objects support `.next()`, `.return()`, `for...of`, spreads, `Array.from` and `yield*`, pull their source lazily, and close it (run its `return()`) at the same points as Node: when `take` reaches its count, when a short-circuiting method finds its answer, when a callback throws, and when the loop consuming the helper exits early.
  In TypeScript, `h.return!()` and `h.return?.()` compile when `h` is a helper call or a `const` initialized with one.
  `flatMap` callbacks return arrays, generators, Sets or Maps; a string result throws Node's TypeError.
  `Iterator.from` accepts arrays, strings, typed arrays, Sets, Maps and iterator objects such as generators.
  In JavaScript, untyped generators and array, string, Map and Set iterators answer the same methods, and array destructuring of untyped values steps generators, iterators, Maps and Sets the way Node does.
  Helper methods on untyped async generators are unsupported.
  `console.log`, `util.inspect` and `Object.prototype.toString` print iterator objects as Node does: `Object [Iterator Helper] {}`, `Object [Generator] {}`, `Object [Array Iterator] {}`, and Map and Set iterators with the entries they have left.
  `Iterator.from` over an object with its own `next` method or over an untyped value, the `Iterator` global as a value, and `String()` and template literals of typed iterator objects are unsupported.
- **Regular expressions:** Native checked storage preserves identity, `source`, `flags`, flag properties, and stateful `test`, and the flag getters (`global`, `unicodeSets`, and the rest) compile on statically typed regexes.
  Static `exec`, global `match`, and numeric `lastIndex` access use the native matcher.
  Runtime constructors accept checked strings and native regex copies; `replace` and `replaceAll` accept native regex values with string templates or callbacks, including captures, named groups, and UTF-16 offsets.
  Named groups (`groups`) read on matches of a statically known regex: from `exec` and `match`, through optional chains (`m?.groups?.name`), in a `for...of` over `matchAll`, and on the element parameter of an array-method callback over `matchAll` rows.
  The groups object has a null prototype, as in Node.
  Non-numeric `lastIndex`, detached methods, and `exec` through an untyped receiver are unsupported.
  The `v` (unicode sets) flag supports class set operations (`--`, `&&`), nested classes, `\q{...}` string literals, and properties of strings such as `\p{RGI_Emoji}`.
  `new RegExp` with an invalid pattern throws a `SyntaxError` whose message comes from the native engine and can differ from Node's.
  The `d` (match indices) flag is unsupported: regex literals and `new RegExp` calls with a literal flag string containing it fail to compile with SC1120, and `new RegExp` with a computed flag string containing it throws an error naming the flag.
- **Object freezing:** `Object.freeze` and `Object.isFrozen` support plain checked dictionaries and primitives.
  Freezing is shallow and preserves accessors.
  Aliased typed records, arrays, proxies, and other native objects cannot be frozen.
  Class instances support `Object.freeze`, `Object.seal`, `Object.preventExtensions`, `Object.isFrozen`, `Object.isSealed`, and `Object.isExtensible` when the static type of the argument names the class.
  A write to a read-only field throws Node's `TypeError` in strict mode code and leaves the field unchanged in sloppy mode code.
  `Object.defineProperty` with a string-literal key changes the value, `writable`, `enumerable`, and `configurable` attributes of a declared field; an object-literal accessor descriptor (`get` or `set`) for a declared field is refused at compile time with `SC2020`, and another accessor descriptor for a declared field throws `SC2020` when the call runs.
  `Object.freeze`, `Object.seal`, and `Object.preventExtensions` of a class instance held in an untyped value change the instance itself, so writes through its typed references are checked the same way.
  A typed record checked from a frozen untyped object (an object literal frozen in a JavaScript file, or a frozen `JSON.parse` result) is frozen too, and writes through its typed references are checked the same way.
  The `TypeError` for a write to a read-only property that was added to a class instance through an untyped value names `#<Object>` where Node names the class.
  Functions support `Object.freeze`, `Object.seal`, `Object.preventExtensions`, `Object.isFrozen`, `Object.isSealed`, and `Object.isExtensible`; the operations apply to the function's own properties (`name`, `length`, `prototype`, and the ones a program assigns).
  Class constructors are not supported.
  A write to a read-only property of a checked dictionary or a function throws a `TypeError` whose message omits Node's `'x' of object '#<Object>'` (or `of function '…'`) suffix.
  A JavaScript class field that is first created after its instance became non-extensible is added instead of throwing.

### Builtin function values

Some builtins can be stored and passed as functions:

- Lowered `node:path` functions across the bare, POSIX, and win32 modules, including optional `basename` and variadic `join`/`resolve`.
  Called through an untyped value, they validate their arguments with Node's `ERR_INVALID_ARG_TYPE`.
  In JavaScript, a direct call whose arguments are not strings, `join` and `resolve` included, validates them the same way.
  A path module held by untyped code is a plain object with Node's members in Node's order.
- Supported zero-argument `node:os` functions and `querystring.escape`/`unescape`.
- `util.isDeepStrictEqual`, `styleText`, `stripVTControlCharacters`, `toUSVString`, `inherits`, `deprecate`, `callbackify`, `debuglog`, and `debug`.
- `fs.existsSync`, `unlinkSync`, `chmodSync`, `chownSync`, `renameSync`, and `closeSync`, and `fs/promises.unlink`, `chmod`, and `rename`.

`String`, `Number`, `Boolean`, and `BigInt` are function values wherever they are stored, in TypeScript and JavaScript: an option table can hold them as type tags (`{ "--port": Number, "--tag": [String] }`), beside strings and arrays.
A held constructor is the same function everywhere in the program, so `type === Boolean` holds across modules, through `unknown`, and as a `Map` key or `Set` member.
Called through the value, it converts whatever arguments it is given, as the builtin does.
A call through a held constructor converts its arguments to untyped values first; a direct call such as `Number(x)`, and a constructor written as the callback of an array method (`xs.map(Number)`), keep their typed conversion.
A held constructor converts to a function type that has a first parameter (`(value: string, name: string) => unknown`), whoever calls it.
Under a function type with no parameters it is called with no argument, and the store is refused when the program hands a function of that type to a caller that passes arguments, as for any function that reads its argument list.
Through a function type whose first parameter is optional or accepts `undefined`, a call that passes `undefined` there, or omits the argument, throws a `TypeError`: a constructor answers a missing argument differently from `undefined` (`String()` is `""`), and the function type does not say which one the call passed.
The `prototype` of a held constructor and a `Map` or `Set` keyed by a union of several constructor types are refused.
`new` through a held constructor builds with warning `SC6015`, which names the statement, and the statement throws `SC1090` when it runs.
In TypeScript, `util.format("%o", f)` of a function value and `Object.getPrototypeOf` of a held constructor build with the same warning and throw when they run.
A constructor passed into a Node island is the island's own constructor there, and the island's comes back as the program's.
Converted to a string (`String(c)`, a template, `+`, `c.toString()`, `%s`), a held constructor is Node's `function String() { [native code] }`.
A program that hands a held constructor, or a table of them, to something the typed form has no lowering for (printing, `JSON.stringify`, `in`, key listing, an `any` slot, `bind`, an assertion) holds its constructors as untyped values instead, in the whole program, and builds as it did before constructors had a typed form.
`JSON.stringify` and `%j` write a `Map` or `Set` held by an object, an array, or a tuple as `{}`, as Node.js does, whatever it holds.

In JavaScript, `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`, `clearImmediate`, `queueMicrotask`, and `process.nextTick` are function values wherever they are stored, including conditional and logical expressions and assignments: `typeof` answers `"function"`, identity holds across spellings, and calls schedule as the direct calls do, returning the same numeric timer handles.
Calls through these values bypass `node:test` mock timers.
In JavaScript, `atob`, `btoa`, and `structuredClone` are function values the same way, and calls through them run what the direct calls run.
In JavaScript, `parseInt` and `parseFloat` are the `Number.parseInt` and `Number.parseFloat` function values, `isNaN`, `isFinite`, and `Symbol` (with `Symbol.for`, `Symbol.keyFor`, and the well-known symbols) are function values whose calls run what the direct calls run, and `Atomics` is an object whose integer operations run.
Calling `eval` through its value throws the `EvalError` its direct calls throw.
`escape`, `unescape`, and `AsyncDisposableStack` have function values with Node's `name` and `length` that throw `SC2020` when called or constructed.
`globalThis.require`, `module`, `exports`, `__dirname`, and `__filename` are `undefined`, as in Node.
In JavaScript, any other Node function or class stored as a value (a global such as `URL` or `Headers`, a builtin module member such as `module.register` or `dns.lookup`) is a function with Node's `name` and `length`: `typeof` answers `"function"`, identity holds across spellings, and calling or constructing it through that value throws `SC2020`.
`process` and `performance` stored in JavaScript are objects with Node's members: a function member is the function the spelled member reads (`held.nextTick === process.nextTick`), `argv`, `platform`, `arch`, `pid`, `execPath`, `execArgv`, `versions`, `debugPort`, `sourceMapsEnabled`, `performance.timeOrigin` and `performance.nodeTiming` read their values, and reading any other data member through the stored object throws `SC2020`.
Assigning a data member through the stored object (`held.exitCode = 1`) throws `SC2020`; the spelled `process.exitCode = 1` is not affected.
The stored object is frozen: redefining a member with `Object.defineProperty`, replacing a function member or adding a key through it throws a `TypeError`, where Node changes the process object, because the spelled members (`process.pid`, `process.nextTick(f)`) are compiled where they are written and would not follow the change.
Any other Node value that native code does not provide (`process.finalization`) throws `SC2020` where JavaScript reads it, and `typeof` of it answers Node's type without reading it.
A global that Node 24 does not define by default (`localStorage`, `sessionStorage`) is not defined: `typeof` answers `"undefined"`, the global object has no such property, and a read throws `ReferenceError`.
In a JavaScript ES module, `typeof` of `require`, `module`, `exports`, `__dirname`, and `__filename` answers `"undefined"`, as in Node.
An ES module namespace of a builtin module has only the module's exports, so `zlib.Z_NO_FLUSH` through `import * as zlib` is `undefined` in JavaScript, as in Node, while the default import and `require()` have it.
In JavaScript, `require("events")` and the `EventEmitter` class assigned to an untyped binding or stored in an object (`ReleaseEmitter = EventEmitter`) are the untyped `EventEmitter` constructor that constructor functions inherit from (see below): `new` through it builds such an emitter object, and identity and `instanceof` agree with the class.
A class can extend `require("events")` directly.

Other immutable aliases of table-backed Node builtins support direct calls, but cannot be passed elsewhere.
`util.promisify` supports compile-time conversions of `child_process.execFile`, `child_process.exec`, `setTimeout` and `setImmediate` (to the `timers/promises` functions, as in Node), `stream.finished` and `stream.pipeline` (to the `stream/promises` functions, as in Node), the `zlib` callback one-shots `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `gzip`, `gunzip`, `unzip`, `brotliCompress`, `brotliDecompress`, `zstdCompress` and `zstdDecompress` (with or without options), the `crypto` callback functions `randomBytes`, `pbkdf2`, `scrypt`, `hkdf`, `randomFill`, `randomInt` and `generateKey`, `dns.lookup` (settling with `{ address, family }`, or the address array with `all: true`) and `dns.lookupService` (settling with `{ hostname, service }`), and the `fs` callback functions that have an `fs/promises` counterpart (`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`); calls through the converted function lower as the `fs/promises` call.

`util.promisify` over an untyped function (a value typed `any`, a function whose parameters are untyped, or any other target in JavaScript) returns a native wrapper that follows Node's callback rules, carries the original's own property descriptors (`name`, `length`, `prototype`, and any members the program added), and throws Node's `ERR_INVALID_ARG_TYPE` for values that are not functions.
A truthy `util.promisify.custom` function on the original is what `util.promisify` answers, marked with the key, and a truthy value of another type throws Node's `ERR_INVALID_ARG_TYPE`; a falsy value leaves the wrapper.
The same protocol runs for a function of a statically typed callback-last signature whose arguments and promise result convert through the checked-dynamic layer, and its wrapper carries the original's own descriptors too; a signature that layer cannot carry keeps the wrapper without consulting the property.
The wrapper does not report Node's DEP0174 warning when the original returns a promise.

`util.styleText` supports standard formats and aliases, `#RGB` and `#RRGGBB` hex colors, arrays of formats, nested ANSI resets, and explicit color enabling with `{ validateStream: false }`.
Native calls accept standard process streams; custom native streams and mutations to `util.inspect.colors` remain unsupported.

`util.inherits` links the prototypes of constructor functions, and `util.deprecate`, `util.callbackify`, and `util.debuglog` return native function values that forward receivers and arguments.
In JavaScript, a constructor function can inherit from `EventEmitter`, the legacy `Stream` constructor, or a stream class through `util.inherits` or `Object.setPrototypeOf`.
`EventEmitter.call(this)` and `Stream.call(this)` initialize the object as Node does, and its listener methods keep their state in the object's own `_events`, `_eventsCount`, and `_maxListeners` properties.
The `captureRejections` option and `events.errorMonitor` listeners are not supported on such objects.
`Readable.call(this, options)` and the same call on `Writable`, `Duplex`, `Transform`, or `PassThrough` give the object a native stream.
The stream calls the object's `_read`, `_write`, `_final`, `_transform`, `_flush`, and `_destroy` with the object as `this`, and the stream prototypes' methods and accessors work on it.
Its listeners live in the native stream rather than in `_events`, and `_readableState` and `_writableState` are read-only views that answer the commonly read fields.
Typed code that receives such an object as a stream class uses its native stream, and piping a native stream into it works.
The options the native streams refuse (`signal` and others) are refused here too, as are `_writev` on a Transform, `_final` on a Transform, symbol event names, `listeners()`, `rawListeners()`, `wrap()`, and pipes into a destination that overrides `write()` or `end()`.
`_construct` and `_writev` are read when `Readable.call(this, options)` or `Writable.call(this, options)` runs, as Node's constructors read them.
Other class constructors passed to `util.inherits` or `util.deprecate` are refused.
Deprecation warnings are emitted on the next tick and follow assignments to `process.noDeprecation` and `process.throwDeprecation`; compiled binaries take no Node command-line flags such as `--no-deprecation` or `--trace-deprecation`, and `process.traceDeprecation` is unsupported.
A stored `util.deprecate` reports a `length` of 4, where Node reports 3.
In TypeScript, a `util.callbackify` result must reach a callback whose error parameter accepts `null`; the @types/node overloads that type it as non-null are refused.
`util.debuglog` reads `NODE_DEBUG` as the process started.
Its `%s` refuses functions and objects with symbol-keyed members, and object arguments are refused on a color terminal.

`util.isDeepStrictEqual` supports primitives, ordinary records and arrays, cycles, enumerable string and symbol properties, byte views and typed arrays, Dates, RegExps, Maps, Sets, URLs, URLSearchParams, ArrayBuffers and SharedArrayBuffers compared by their bytes, weak-collection identity, class instances, and Errors compared by message, name, `cause` and `errors`.
Its optional third argument skips prototype and constructor comparisons.
An own enumerable accessor property takes part by the value its getter answers, run once with the object as `this`, and a throwing getter's error reaches the caller.
A detached ArrayBuffer compared with a buffer that also reports length 0 throws a `TypeError`, as Node does.
Opaque proxies, own enumerable `constructor` values, an accessor `constructor`, and an accessor `Symbol.toStringTag` remain refused.

Synchronous and promise filesystem APIs with trailing arguments that affect behavior remain call-only.
This includes writers, open, directory creation/removal, metadata readers, and directory readers.
Passing them as values could discard modes, flags, encodings, or options during function-signature adaptation.

Native error-first filesystem callbacks can be stored and passed to platform adapters.
Reads support Buffer and UTF-8 results; writes preserve string flags and modes; copy follows `fs.cp`.
Reads and writes reject already-aborted signals, but native file operations complete synchronously before the deferred callback, so cancellation cannot interrupt I/O in progress.
Recursive directory reads and Dirent results report a native limitation.
`fs.glob` compiles only as a direct call; a stored `fs.glob` value throws `SC2020`.

### Native worker threads

`node:worker_threads` runs workers as native threads.
Worker entry points must resolve at build time: use a source-relative `new URL("./worker.ts", import.meta.url)`, the current module URL, an absolute path, or CommonJS `__filename`.
A computed entry the build can bound to a finite set of files (a `new URL` of bounded text against `import.meta.url`, the value a module-local function returns, a branch of `?:`, `??` or `||`) starts the compiled file its value names; a value outside that set throws `ERR_SCRIPTC_WORKER_ENTRY` when the constructor runs, where Node would start that file.
Each worker initializes its own module state, event loop, environment snapshot, and reference-counted heap.
Nested workers inherit their parent's environment snapshot.
Supported constructor options are `workerData`, `argv`, a `transferList` of MessagePorts, `name` (reported by `threadName` and `worker.threadName`), `stdout`, `stderr` and `stdin`, and `resourceLimits.stackSizeMb`, which sets the worker thread's stack size (at least 192 KiB; without it a worker's stack is 8 MiB, where Node's is 4 MiB).
The `resourceLimits` heap limits (`maxOldGenerationSizeMb`, `maxYoungGenerationSizeMb`, and `codeRangeSizeMb`), `worker.resourceLimits` while the worker runs, and `resourceLimits` read inside a worker are refused; in the main thread `resourceLimits` is `{}`, and after a worker exits `worker.resourceLimits` is `{}`, as in Node.
With the `stdout` or `stderr` option, the worker's writes to that stream reach `worker.stdout` or `worker.stderr`, a Readable that ends after the worker's `'exit'` event; its chunks arrive in order with the worker's other events.
Without the option a worker writes to the process's own output, and reading `worker.stdout` or `worker.stderr` is refused.
With the `stdin` option, `worker.stdin` is a Writable whose chunks the worker reads from `process.stdin`, in order with the worker's other events, and `process.stdin` ends when `worker.stdin` ends; without it `worker.stdin` is `null` and the worker's `process.stdin` ends without data, as in Node.
Runtime paths, `eval`, loaders, and shared environments are unsupported.
`setEnvironmentData` and `getEnvironmentData` keep a map per thread with SameValueZero keys; each new Worker starts from a clone of its parent's entries taken by the constructor, which throws `DataCloneError` when an entry cannot be cloned.
Worker argument strings containing null bytes are also unsupported.

Workers support `online`, `message`, `error`, and `exit` events, `postMessage`, `terminate`, `ref`, and `unref`.
`worker[Symbol.asyncDispose]()` terminates the worker and resolves with `undefined` after its `exit` event, so `await using` disposes a Worker at the end of its block.
A worker's uncaught exception reaches its `error` listeners as Node's error serializer sends it: an Error keeps the nearest of `Error` and its six native subclasses (an `AggregateError` arrives as an `Error` named `AggregateError`) along with its name, message, cause, and other data properties and their enumerability, and other thrown values clone as messages do.
An unhandled rejection whose reason is not error-like arrives as an `UnhandledPromiseRejection` Error with code `ERR_UNHANDLED_REJECTION` quoting the reason.
A thrown value that does not clone, such as a function, arrives as an `ERR_WORKER_UNSERIALIZABLE_ERROR` Error, where Node sends its `util.inspect` string.
`new MessageChannel()` from `node:worker_threads` makes two entangled MessagePorts, in one thread or across threads once a port is transferred, with `receiveMessageOnPort`.
Port messages arrive in Node's order: each port's queued messages as one batch before `setImmediate` callbacks, a port's own `'close'` after the poll phase, and the other port's `'close'` after its queued messages while the loop keeps running.
A worker's messages to its parent, and the parent's messages in the worker, also arrive as one batch before `setImmediate` callbacks, each followed by its microtasks, as in Node: the messages queued when delivery starts, and those that arrive meanwhile until the batch holds 1000.
`receiveMessageOnPort(parentPort)` in a worker takes the parent's next message.
A port's `onmessage` property and `addEventListener` are unsupported.
`BroadcastChannel` delivers each message to every other open channel with the same name, in any thread, as a `'message'` `MessageEvent` through `onmessage` and `addEventListener`; an open channel keeps the process alive until `close()` or `unref()`.
A `messageerror` event is never dispatched.
The parent port supports message listeners, `postMessage`, `close`, `start`, `ref`, `unref`, and `hasRef`.
Messages clone primitives, plain object graphs, arrays, errors, ArrayBuffers, typed arrays, and DataViews, preserving cycles and aliases.
Program class instances arrive as plain objects with their own data properties, without their prototype, methods, accessors, or private fields, as in Node.
`markAsUntransferable` marks objects for the current thread, and a marked ArrayBuffer or MessagePort in a transfer list throws `DataCloneError`, as does the ArrayBuffer of a pooled Buffer; marked objects other than ArrayBuffers stay alive until their thread exits.
`markAsUncloneable` marks a plain object, an error, or a class instance, and `structuredClone` and `postMessage` then throw `DataCloneError` for it, also where it is nested; as in Node, the mark has no effect on primitives, arrays, typed arrays, and ArrayBuffers.
A plain object that the compiler copies into a container with a fixed element type, such as an array literal whose elements all have one shape, is a separate object in the copy, which is not marked.
Marked objects stay alive until their thread exits.
A MessagePort travels with a message when the message's transfer list (an array or `{ transfer }`) names it, and the sending port is unusable afterwards; a port in a message without that listing throws `DataCloneError`.
Other transferable values, functions, proxies, and other native handles are refused.
SharedArrayBuffers share their backing storage while retaining separate wrappers in each thread.
Graphs made immutable with `@scriptc/threads` are passed by reference instead of cloned; see [Sharing Data Between Workers](/docs/threads).

SharedArrayBuffer storage is fixed-length.
Atomics supports integer `load`, `store`, `exchange`, `compareExchange`, `add`, `sub`, `and`, `or`, and `xor` over 8-, 16-, and 32-bit integer arrays.
`wait` and `notify` use Int32Array views; BigInt atomics, `waitAsync`, and growable buffers are unsupported.
Atomics operations run as sequentially consistent atomic instructions and take no lock.
Other reads and writes of shared storage access each element atomically; a copy, fill, or conversion that runs while another thread writes the same storage can observe some of those writes, as in Node.
`terminate` unwinds at runtime checkpoints, including loops and parked atomic waits, without running JavaScript catch or finally blocks.
Blocking native calls finish before the next checkpoint.

A worker that reaches a Node island posts each island operation to the thread that started the island, the main thread, and waits there for its answer, so a synchronous island call works from a worker.
The program has one instance of each island module for all its threads, where Node gives every worker an instance of its own: what an island module keeps between calls (a counter, a cache, a registered listener) is shared by the main thread and the workers.
The build warns at each load of an island module in a module that worker threads run (SC6011).
A program module that island code imports too is shared the same way: island code uses the main thread's instance of it whichever thread called the island, while a worker's own code uses that worker's instance, and the build warns for such a module as well (SC6011).
The main thread answers a worker's island operations from its event loop, so a worker's island call waits while the main thread runs synchronous code, and it is never answered when the main thread blocks until that worker is done, as with `Atomics.wait`.
An asynchronous island call from a worker rejects with `ERR_SCRIPTC_ISLAND_WORKER_ASYNC`, an island promise, symbol or byte value (a typed array, a Buffer or an ArrayBuffer) crossing into a worker throws `ERR_SCRIPTC_ISLAND_WORKER_VALUE`, and so does a worker's own symbol or byte value crossing into the island, and a native value the island passes by reference (a function, an object, an array, a Map, a Set, a promise, or an error) crossing between a worker and its island in either direction.
The error's message names the kind of value and the call, read, or write it was part of.
A promise that a worker resolves with an island thenable rejects with the same code, and one resolved with any other island object is fulfilled with it.
In a module that only worker threads run, an island call whose argument has a function, byte, promise, or symbol type is refused at build time (SC8012).
A worker entry must be a compiled module, so a Node island module as the worker's entry is refused at build time (SC8012).

Workers are unsupported in WASI, compiled libraries, and executables that use foreign-thread native callbacks.
Ordinary callbacks that run on their owning script thread remain supported.
Shared byte storage cannot expose unmanaged FFI pointers; copy it into an ordinary Buffer before passing it to native code.

### Child-process workers

`child_process.fork()` requires a worker path resolved at build time from `new URL("./worker.ts", import.meta.url)` or `fileURLToPath(new URL("./worker.ts", import.meta.url))`.
Never-reassigned `const` aliases and statically resolvable template parts are accepted.
The worker and its imports are embedded in the executable, which re-executes itself to start that module.
Worker arguments begin at `process.argv[2]`.

Supported options are default inherited stdio or an inline object with `cwd`, `env`, `silent`, `windowsHide`, or `[stdin, stdout, stderr, "ipc"]`.
`execArgv` is accepted but has no native effect.
Runtime worker paths, custom `execPath`, advanced serialization, transferred sockets or servers, uid/gid, timeout/signal options, and other stdio arrays are unsupported.

Parent and child channels support `send(message, callback?)`, `connected`, `disconnect()`, and `on`/`once` for `message` and `disconnect`.
Message listeners may return `void` or `Promise<void>`.
Messages use newline-delimited JSON, so their static types must be JSON-stringifiable or `unknown` backed by a checked JSON tree.
`send()` returns the channel's backpressure state and invokes its callback later with `null` or an `Error`.
`disconnect()` changes `connected` synchronously, before the later disconnect events.

A `'data'` listener on a child's `stdout` or `stderr` whose chunk is `unknown` or `any` receives strings when a `setEncoding()` statement on the same stream precedes it in its block or an enclosing block, and Buffers when the function never calls `setEncoding()` on that stream.
Any other placement of `setEncoding()` is refused at compile time, and a `setEncoding()` call that runs elsewhere afterwards makes the listener throw a TypeError instead of receiving strings.
`pause()`, `resume()`, `isPaused()` and `readableFlowing` on a child's `stdout` or `stderr` follow Node: a paused stream holds up to 64 KiB, then stops reading, which blocks the child once the pipe is full, and the child's exit resumes the stream.
Pausing the output of an `exec()` or `execFile()` child that has a callback holds back its `'data'` listeners, but the output is still read for the callback, so that child is not blocked.
The call's `maxBuffer` is counted while the stream is paused, so a child that writes past it is ended with the `maxBuffer` error at that point, where Node ends it once the stream is resumed.
A child's output arrives over a pipe where Node uses a socket pair, so the number of bytes a child can write to an unread or paused stream before it blocks differs from Node's.
A callback-form `exec()` or `execFile()` takes an inline options object with `encoding: "utf8"`, `maxBuffer`, `cwd`, `env` (which replaces the child environment), `timeout` (an unsigned-integer deadline that destroys the capture streams and sends `killSignal`, a signal name matched without case, SIGTERM when absent), `shell: true`, and the Windows-only members.
The remaining `exec` options — other encodings, `uid`, `gid`, `input`, `stdio`, `signal` — and a non-literal options value are refused at compile time.
A `spawn()` call takes `timeout`, `killSignal` and `signal` as well: the timeout kills the child with `killSignal` (a name matched without case, or a signal number; SIGTERM when absent) and leaves its streams open, an already-aborted `AbortSignal` still starts the child and then emits `'error'` with Node's `AbortError` while a later abort kills it with `killSignal` and emits that error once, and the child's exit ends the subscription.
A computed options value is read at run time but cannot carry an `AbortSignal`; that use is refused then.
An inline `'exit'` or `'close'` listener may declare `(code: number, signal: string)`, which TypeScript accepts through the catch-all `on` overload; it receives the `null` values Node passes, and a typed use of a `null` value throws a TypeError.
A function passed by name as an `'exit'` or `'close'` listener may declare its first parameter as `number`; when a signal ended the child, where Node calls it with `null`, it is not called and a TypeError is thrown instead, and a `string` signal parameter of such a function is refused at compile time.

### Untyped values

Supported uses of `any` use native checked values.
Operations without native lowering produce diagnostics such as `SC2011`.
Use `unknown` with a checked cast where an operation needs a concrete type.
Map and Set type arguments may use `any`, with the same checked storage as `unknown`.

Locals, parameters, and returns can carry untyped values; record and tuple fields can hold `unknown`.
JavaScript instance fields and TypeScript class fields declared `unknown` or `object` (also with `null` or `undefined`, or as arrays of those) can also hold native checked values.
Bare fields start as `undefined`, and redeclaring a field with the same checked-value type resets it in initialization order.
Explicit TypeScript `any` class fields, other checked-value static fields, statically typed array elements, and union members are unsupported.

Program class instances retain identity through `unknown` and casts back to their exact class or a base class, including classes with internal collections.
Inspecting dynamic properties throws a catchable `TypeError` if the class's fields cannot pass through checked storage.
Supported native handles also retain identity.
An instance of a class that declares `toString`, converted to a string where its class is not known (an untyped array's `join`, `String()` or a template over such an array, the default sort order), throws `SC2020` instead of running the method; convert the instance where its class is known.
JavaScript argument and return conversions preserve native array references; other JSON-shaped record and array conversions can copy values.

Supported operations on `unknown` include truthiness, `typeof` narrowing, property access, `+`, `switch`, and `throw`.
Cast before using other operations.

In JavaScript, a call whose untyped argument could select an overload with another result type returns a checked value, whichever overload the type checker picks, and so do the unannotated bindings and functions that hold or return it.
In TypeScript, an `any` argument keeps the overload the type checker picks, and a value that selects another overload at runtime throws a `TypeError` where the result is used.

### Type checking

scriptc checks programs against the `es2025` standard library, the ESNext declarations for `Array.fromAsync`, explicit resource management and `Error.isError`, and its own ambient declarations.
Some declarations differ from TypeScript's standard library or `@types/node`: `JSON.parse` returns `unknown`, and a Promise executor's reject reason is typed as `Error`.

A program that passes its project's TypeScript check may still use unsupported operations.
A second preflight using the project's declarations distinguishes those sites from ordinary type errors and reports scriptc diagnostics with rewrite hints.
Declared APIs without a compiler implementation, such as unsupported console members, produce `SC2020`.

From the nearest `tsconfig.json`, scriptc takes the strictness flags, `paths` and `baseUrl`, the JSX settings, the declaration files that `files` and `include` name, the packages that `types` names, `typeRoots`, and `verbatimModuleSyntax`, `isolatedModules` and `preserveConstEnums` for [import elision](/docs/dependencies#package-resolution).
A declaration file of the project, such as a shared `types.d.ts`, a workspace package of types or the typings it writes for an untyped package, declares data types the program builds, like the same types written in a `.ts` file.
A value that a package running in a Node island hands to the program stays that package's object under such a type: the program reads and writes it in place, and copies it only with `structuredClone`, a spread or JSON.
A project that sets `typeRoots` and no `types` has every package directory of a type root outside `node_modules` loaded, as tsc does through TypeScript 5.9.
A type root also answers a `/// <reference types>` directive, and an import of a module for which no `node_modules` directory holds declarations.
In a project that compiles under `moduleResolution: node10` (also spelled `node`, and tsc's choice through TypeScript 5.9 for `module: commonjs` when the setting is absent), a specifier that only the type checker reads (in a declaration file, an `import type` or an `export type`) can name a file that a package's `exports` map does not list, and a package's declarations are also found by path where the map names none, as that project's tsc resolves them.
A value import of a file the map does not list is refused, as Node.js refuses it at startup.
A project whose `target` is below ES2022, or that sets `useDefineForClassFields` to `false`, has its parameter properties assigned before its class field initializers run, as in tsc's output; a field declared without an initializer is still an own property that holds `undefined`.
The other settings keep one meaning.
`lib` does not change the library above, and no other library is loaded into the program that is compiled.
A library that `lib` names beside it, such as `dom`, decides only which names type-check.
A name that only such a library declares is an opaque type in compiled code, and a value of that type is checked dynamically.
As a value the name is absent at run time, as under Node.js: a read throws `ReferenceError`, `typeof` answers `"undefined"`, and `globalThis.document` is `undefined`.
A global that Node.js defines is never treated as absent.
A `/// <reference lib="dom" />` directive in a source file does load the library into the compiled program, where it conflicts with the declarations scriptc bundles for a project without `@types/node`; name the library in `lib` instead.
TypeScript files are ES modules or CommonJS by Node.js's rules (the file extension and the nearest `package.json`), whatever `module` and `moduleResolution` say, so `import x = require()` and `export =` are refused with `SC0016` in a project that sets `module` to `commonjs`.
Legacy decorators (`experimentalDecorators`) are refused with `SC0016`, except a class decorator of one parameter, which behaves the same under both decorator semantics.
A project that does not enable `strict` or `strictNullChecks` is refused with `SC0002`.

TypeScript keeps the type of an assignment across a call, so after `x = undefined` a read of `x` is typed `undefined` even when a function called in between assigns `x` again.
scriptc reads the stored value there.
A function without a declared return type that returns such a read is inferred to return `undefined`; scriptc refuses that return with `SC1090`, and a declared return type compiles.

### Contract types

[Contract types](/docs/contracts) are checked only where they are written as the annotation of a parameter, variable, class instance field or function result, `Acyclic` only on classes, interfaces and object type aliases, and `ProgramLifetime` only in the `implements` clause of a class declaration; other aliases, interface members and nested type positions are reported.
In programs that use worker threads, a surrounding `try`/`catch` does not discharge `NoThrow`.
`Int32` refuses parameters of functions used as values and fields of classes whose instances are stored in `any` or `unknown` values.
`Int32[]` refuses arrays that can come from or escape to code scriptc cannot see, follows callback results only for callbacks written at a `map` or `filter` call that scriptc runs inline, and checks a copy made by `slice` or a spread as if it were its source array.
Element reads that find a value are int32s, but a scalar `Int32` slot still needs `a[i] | 0`, because a read can miss and produce `undefined`.
`Immutable` refuses values stored in `any` or `unknown` or that another reference can still change.
`ProgramLifetime` applies to root classes of executables only; instances the program drops are never reclaimed unless they belong to a released [lifetime region](/docs/experimental#lifetimeregion).

### three.js

With `--npm-static=three`, native and WASI builds support published package imports and tested CPU workflows for math, bounds, procedural geometry, curves, shapes, extrusion, text geometry, scene graphs, cameras, lights, materials, textures, and render-target data.
Mesh workflows include instancing, batching, morph targets, skeletal deformation, LOD, and sprites.
Raycasting includes mesh intersections, sorted results, recursive traversal, layers, distance limits, camera rays, lines, and points.

Animation coverage includes tracks, clips, property binding, mixer playback, blending, and fading.
In-memory loading covers scene, material, geometry, and animation JSON, OBJ, STL, PLY, and self-contained glTF/GLB scenes with buffers, PBR material data, and animation.
This establishes the exercised workflows, not every loader extension or overload.

Tested CPU graph construction from `three/webgpu` and `three/tsl` includes scalar/vector nodes, mutable uniforms, `Fn` layouts and calls, and `MeshBasicNodeMaterial` data.

WebGL/WebGPU rendering, DOM controls, image and video resources, audio, and worker-based decoders require external host bindings.
A browser or native application can render geometry and material state produced by a compiled library and receive its resource lifecycle notifications.
Compressed model and texture decoding belongs to that host; shader execution and rendered output are outside the CPU coverage described here.

## Runtime differences from Node.js

Compiled code has runtime behavior that can differ from Node.js.
Account for the following differences when porting a program.

- **Event order across native code and islands:** Timers, worker output, and other macrotasks from the program and from JavaScript it runs in a Node island share one event loop under Node.js, so their order is fixed.
  A compiled program runs both on the event loop it hosts for Node.js, but it does not merge them into one order, so callbacks from the two sides can run in a different order than under Node.js, and the order can vary between runs.
  Only programs that depend on the ordering between native and island callbacks are affected.
- **Island packages that read the machine when they load:** The build keeps what an island package exports when it loads only when the load read nothing a run could answer differently.
  A load that reads a clock, a random number, the process id, the arguments, the working directory, the user, the host, a file outside the package, or the time zone of a machine whose `TZ` is unset runs when the program imports the package, which starts Node.js there.
  A package imported after any load that runs at its import runs at its import as well: Node.js is running by then, and the executable holds nothing the build computed after that load, such as a value the earlier load read and left in a global.
  A load that reads an environment variable, the default locale, a time zone that `TZ` names, or whether a standard stream is a terminal waits, and runs at the import in a run where that answer differs from the build's.
  A variable that has a value on the build machine keeps the load at the import unless the project lists it in `scriptc.nodeIslands.publicEnv`, so a build machine with `LANG` set does not defer a package that formats a number in the default locale.
  A package marked `"evaluate": "lazy"` waits whatever its load reads.
  The values it computed from the machine, the values of packages imported after it, and its members whose names do not appear in its files are then read from Node.js on first use.
  The build cannot see three things a load can depend on, and keeps the build machine's answer for them: memory the package allocated without initializing it, how deep the stack is, and a value computed from the package's own path that no longer contains the path.
- **Timers across native code and islands:** A timer or immediate the program creates is a number in JavaScript that runs in a Node island, as it is in compiled code.
  The island's `clearTimeout`, `clearInterval` and `clearImmediate` accept that number and clear the program's timer of that kind (`clearTimeout` of an immediate's number clears nothing, as in Node), but `typeof` answers `"number"` there and the value has no `ref`, `unref`, `hasRef`, `refresh` or `close` method.
  A timer the island creates stays the island's `Timeout` or `Immediate` object: the program clears it with the clear functions, calls its methods and passes it back to the island.
  In TypeScript, keep such a timer in a `const`; a variable or field typed as a timer holds only the program's own timers, and storing an island's timer in one is a compile-time error.
- **Abort listeners across native code and islands:** An `AbortSignal` shared between the program and JavaScript running in a Node island is one signal under Node.js, with one list of listeners that run in the order they were added.
  A compiled program keeps a signal on each side, and the two follow each other with the same `reason`; each side keeps its own list of listeners.
  When the signal aborts, the island's listeners run first, in the order they were added, and then the program's, in theirs.
  A signal the program made reports `aborted` on both sides before any listener runs; a signal made in the island reports it natively once the island's listeners have run.
  `getEventListeners` and `listenerCount` on either side count that side's listeners only.
  Only programs that depend on the order between a native and an island listener of one signal, or that count a signal's listeners across the two sides, are affected.
- **Compile cache in Node islands:** Node.js keeps a module compile cache only when `NODE_COMPILE_CACHE` is set, and a compiled program keeps one by default for the JavaScript it runs in a Node island.
  In island code `module.getCompileCacheDir()` therefore returns a directory where Node.js returns `undefined`, and `module.enableCompileCache()` reports that the cache is already enabled.
  The cache is in the user's cache directory, beside the island's extracted files, and the program uses it only when that directory belongs to the user and no one else can write to it.
  The program runs without the cache, and reports nothing, when the directory is not private in that way, cannot be created, or is on a file system with no free space, when the user has no home directory to keep it in, and when the process's effective user or group differs from its real one.
  `NODE_DISABLE_COMPILE_CACHE=1` turns the cache off.
  `NODE_COMPILE_CACHE=<directory>` makes Node.js use that directory as it does on its own, without those checks.
- **Child process event order:** A child's `stdout` and `stderr` chunks, and the `'spawn'`, `'exit'`, and `'close'` events of several children, arrive in the order Node.js delivers them on Linux and macOS.
  The events of one stream relative to the child's exit differ: when a child exits, the rest of its output and each stream's `'end'` are delivered before its `'exit'`, where Node.js fires `'exit'` first when the child's output and its exit arrive in the same turn of the event loop.
  A child that is spawned as the program's first act and writes to `stderr` before `stdout` is delivered in the order written, where Node.js usually delivers `stdout` first because the child finishes before Node.js starts its event loop.
  A child's output pipe does not hold the same amount as the socket Node.js gives it, so a child that fills one stream before it writes to the other can block at a different point and be delivered in a different order.
  On Windows, `stderr` is delivered before `stdout` when both hold output, and the events of several children fire newest child first.
- **Objects converted from Node islands:** An object or array made by JavaScript running in a Node island is one object under Node.js, whatever type the program gives it.
  A compiled program keeps it as the island's object while it is typed `unknown`, `any` or by the island package's own declarations.
  It keeps it too under a record type of the program that the value takes straight from the island: a declared result, an import, a member, a parameter of a callback the island calls, or a type the program asserts it to (`item() as Item`).
  Every value of such a record type then uses checked dynamic storage, which is slower than native record fields, including the records the program builds itself.
  Where the program converts the value to an array type, or to a record type that declares a string-keyed dictionary or a library object such as a `Date`, a `Map` or a `Set`, itself or in a member, the conversion reads the members that type declares and builds a native value from them.
  That value is a copy: a later change made by the island does not show through it, a write made through it does not reach the island, it is not `===` to the island's object, and it holds only the members the type declares.
  An untyped value that the program stores in a member, an array element, a reassigned binding or a container before it gives it a type is copied the same way where it is read back and given a record type that the program's own source files declare.
  A type that a declaration file declares stays a view of the island's object there.
  scriptc reports each conversion it can see, and each such store, as an `SC6007` warning.
  Assert the type where the island hands the value over or declare it in the island package's declarations to keep the island's object, or write the copy yourself (`structuredClone(value) as Item[]`) so that Node.js copies too.
- **Child processes from Node islands:** A `ChildProcess` that JavaScript in a Node island started, for example through a package that wraps `spawn`, and that the program uses at the `ChildProcess` type is the island's own process object: its events arrive in Node.js's order, and its properties, its streams and its listeners are the island object's.
  The following differs from Node.js.
  `once` for `spawn`, `exit`, `close` and `error` registers the listener for every emit of the event, which shows only when the event is emitted more than once.
  When the child has closed, the listeners the program registered on it, on its streams and on its `stdin` are released: they stay registered on the island's object but no longer run, and a listener the program registers afterwards is not registered at all.
  While the program listens to anything on the child, one more `close` listener is registered on the island's object.
  A `data` listener that takes `Buffer` chunks throws a `TypeError` when JavaScript in the island set an encoding on that stream, and one that takes strings throws when no encoding is set, where Node.js hands either listener whatever the stream delivers.
  A write to the child's `stdin` after the child closed reports `ERR_STREAM_DESTROYED` to the write's callback, as under Node.js, and also emits `error` on the stream.
  An `error` event's error has its `name`, `message` and `code`, and its `errno`, `syscall`, `path` and `spawnargs` read as `undefined`.
  A listener runs outside the `AsyncLocalStorage` store that was current when the child was started.

### Array bounds and runtime errors

Typed-array reads at an index that is not an integer in the array's range return `undefined`, as in Node.js, and invalid numeric indexed writes are ignored, while the assignment expression retains its original value.
A non-null assertion on such a read (`bytes[i]!`) yields `NaN` instead of `undefined`.

Ordinary arrays track holes separately from present `undefined` values.
Missing reads and empty `pop()`/`shift()` return `undefined`, indexed writes can grow the array, and length growth creates holes.

Keyed reads of a `Record` or an index signature return `undefined` for a missing key, as in Node.js, with or without `noUncheckedIndexedAccess`.
A missing array element or key that reaches storage that cannot hold `undefined`, such as a class field typed `number`, throws a `TypeError`.
An array converted to an array of another element type, such as an array of narrower records, is copied element by element, and a hole or stored `undefined` that the new element type cannot hold throws a `TypeError`.

Runtime traps cannot be caught.
User-thrown values and supported exceptions, including JSON parse, checked-cast, filesystem, regex, and `String.prototype.repeat` count errors, can be caught; hard traps such as out-of-range reads of statically typed ordinary arrays terminate the process.

Deep recursion in Linux and macOS executables throws Node's catchable `RangeError: Maximum call stack size exceeded`, on the main thread, in worker threads, and inside async functions and generators.
The depth at which it is thrown differs from Node: the main thread uses the process stack limit (usually 8 MiB), worker threads get an 8 MiB stack, async functions and generators that keep a fiber run on 512 KiB stacks, and stackless ones run on the stack that resumes them, so native code usually recurses deeper than Node on threads and somewhat less deeply inside fiber bodies.
Recursion inside runtime library operations, such as serializing deeply nested data, is not checked.
Windows executables, WASI builds and library builds do not check the stack, and exhausting it there terminates the process.

### Runtime type checks

Casts from values whose type is only known at runtime validate the value.
For example, `JSON.parse(s) as Config` throws a catchable error if the data does not match `Config`, identifying the offending path, such as `expected number at $.port, got string`.
A TypeScript assertion alone does not perform this validation when run under Node.js.
A program that catches that error without reading it therefore takes the handler's path for data that Node.js accepts, and prints nothing.
In the program below, the second line is valid JSON without `usage.total`: Node.js prints `message 1` for it, and the compiled program prints `skipped`.

```ts title="log.ts"
interface Usage {
  input: number;
  total: number;
}
interface Entry {
  type: "message";
  usage: Usage;
}
function parse(line: string): Entry | null {
  try {
    return JSON.parse(line) as Entry;
  } catch {
    return null;
  }
}
const lines = [
  '{"type":"message","usage":{"input":1,"total":2}}',
  '{"type":"message","usage":{"input":1}}',
  "not json",
];
for (const line of lines) {
  const entry = parse(line);
  console.log(entry === null ? "skipped" : entry.type + " " + entry.usage.input);
}
```

An executable built with `--optimization dev` and without `--strip` reports such failures when it runs with the environment variable `SCRIPTC_CONVERSION_TRACE` set to a value other than the empty string and `0`.
Each failed check then writes the error and the file, line, and column of the conversion to stderr at the moment it fails, whether or not the program catches the error:

```console
$ scriptc build log.ts --optimization dev -o log
/app/log
$ SCRIPTC_CONVERSION_TRACE=1 ./log > /dev/null
scriptc: checked conversion failed: TypeError: expected number at $.usage.total, got undefined
    at parse (/app/log.ts:11:12)
```

The line that is not JSON fails with a `SyntaxError`, which is not a failed check and is not reported.
The report changes nothing else about the run, a release build has none, and the variable is read only when a check fails.
A record or class instance cast from `unknown` to a dictionary type such as `Record<string, number>` keeps its identity, and the cast does not validate it.
Reading a value that does not match the dictionary's value type throws a `TypeError`, such as `expected number at $, got string`, where Node.js returns the value.

A cast from `unknown` to `undefined` or `void` checks that the value is `undefined`, and so does a cast to a type argument that is one of them, such as `value as T` in a generic function called with `<void>`.
Any other value throws a `TypeError`, such as `expected null | undefined at $, got object`, where Node.js keeps the value.

Casts between program classes are checked too: casting an instance to a class it does not extend throws a `TypeError`, where Node.js reads the members structurally.
`SC6004` reports calls that pass such an instance to a function that casts it.

A value typed by an interface that extends `Error` is an error object or a plain object that has the members.
Handing it to a slot typed by an `Error` class (a parameter, a binding, a return, a member or an element) checks that it is an error and throws a `TypeError` for the plain object, where Node.js reads the members.
`SC6004` reports those sites too.
Inside a generic function or class it reports a value whose type is a type parameter that a call or a `new` expression binds to such an interface, whether the parameter's type is the type parameter itself or holds it, as in `list: T[]`.

A Promise converted from `unknown` to a typed result validates its fulfilled value.
A mismatch rejects with a `TypeError`.
Rejection reasons and reference equality are preserved across conversions.

A rejection handler that declares its parameter's type, such as `(error: Error) => ...`, validates the rejection reason when it runs, whether it is written inline or passed as a function value.
A mismatch rejects the promise that `catch` or `then` returned with a `TypeError`, where Node.js calls the handler with the reason unchanged.

A non-null assertion (`value!`) checks the value when it extracts an object, array or other non-nullable member from a union.
If the value holds `null` or `undefined`, the assertion throws a `TypeError`, even when the result is never used; Node.js continues until a property of the value is read.

A callback of `filter`, `find` or `findLast` that is written as a type predicate, such as `(item): item is Tagged => "tags" in item` or a guard function passed by name, runs as an ordinary test.
The elements it accepts are checked against the predicate's type: an element that is not of that type throws a `TypeError`, where Node.js answers the element unchanged.

### Record identity and property order

A record converted to a record type with a different set of properties, such as a narrower shape or one that adds optional properties, is copied.
Mutations through the converted reference do not affect the original, and the copy is not `===` to it.
Empty record layouts are also snapshots because they have no property storage.
`scriptc coverage` and `scriptc build` report `SC6001` at each conversion that copies a value something else can still reach, in any position: an argument, a return value, a field, an array element, a collection value, a callback parameter.
The report is made when the program writes a field or element of either type, compares values of the converted type by identity (including `includes`, `indexOf`, and Set or Map keys), or passes either type to code scriptc cannot follow.
It is made from the compiled program, so a program that builds without it does not lose such a write or comparison.
It judges types rather than the path one value takes, so it can name a conversion whose copy the program never tells from the original.
A conversion of a new value, such as an object literal, a spread, or the result of a function that returns one, is not reported, because nothing else can reach that value.
The same holds for an array converted to an array of another element type and for a tuple converted to an array.
A record that something else can still reach is refused with `SC1090` where it is converted to a class instance and the program writes either type, because the conversion constructs a new instance where Node.js has the one plain object.
A union value that something else can still reach is refused with `SC2003` under the same condition where one of its members fits several members of the destination union and enters the one its own discriminant names, because that conversion builds a new object where Node.js has one.
Two conversions are not covered and copy without a report.
A promise used as a promise of another record type, such as a `Promise<Wide>` stored as a `Promise<Narrow>`, copies its payload when it settles.
A class stored in a record type, such as `const counter: { count: number } = Counter`, holds copies of the class's static fields.
An `as` assertion that only adds optional members to the properties a record type declares is not such a copy: it is erased, so the value keeps its identity and reading an added member answers what the object carries, as in Node.js.
`scriptc build` reports `SC1031` where such a member is assigned, because a record's layout has no storage for it.
Declaring a record value with a record type that names the same properties, where every declared field fits the value's storage, keeps the value's own layout instead: both names are the same object, as in Node.js.
When a declared field cannot store the values its type admits, such as a declared field that is a wider union type than the value's field, the declaration, a call that passes the value to a parameter the callee writes, or a literal that stores the value in that field is refused with `SC2021` if the program writes through the binding, because the copy would take the write instead of the original.
Assigning an existing value to a binding that keeps its initializer's layout, to one of its fields, or to an element of one of its arrays, is refused with `SC2021`, because the kept layout rebuilds the assigned value, so a later write through it would reach only the copy.
The refusal covers a short-circuit assignment that stores the value (`&&=`, or `??=` on a binding that can be nullish) like the plain assignment it stands for.
A member of another binding that kept its initializer's layout carries that layout into the store, so no refusal is reported when the two layouts already match.
Bracket writes such as `alias["k"] = v` write the field in the kept layout, like their dotted spelling.

Typed records with declared fields, indexed records, and arrays retain identity and mutations through native checked storage.
An empty array literal with no element type of its own, such as the `files` of `{ files: [] }` or `const NONE = [] as const`, takes its element type from the declared types its value reaches and is the same array under every name.
`scriptc build` reports `SC2015` where one such array is used at two element types or reaches a typed array through a computed key; writing the type at the literal (`[] as string[]`) compiles.
A record or class instance converted to a string-keyed dictionary type such as `Record<string, string>`, directly, through a `{}`, `object` or `unknown` binding or parameter, or as an element of a constant `unknown[]` array that holds such records, keeps its identity when the program writes through, compares or inspects values of that dictionary type.
Every dictionary type with the same value type then uses checked dynamic storage, which is slower than a native dictionary.
Class instances retain identity at their exact type or a base class, and at any interface or object type that a class instance is passed, stored, returned, or asserted as when the program could tell a copy from the instance: values of such a type are checked views of the original object, so writes, identity comparisons, Set and Map keys, `util.inspect`, and `JSON.stringify` through them reach the instance, as in Node.js.
These types use checked dynamic storage, which is slower than native record fields, for all of their values, including plain objects.
A type whose values are only read field by field, from fields the class does not write after construction and does not implement as accessors, stays a native record and holds a copy.
Object spread of a class instance is refused.
Dense array literals created directly in checked storage preserve identity, and supported native handles pass by reference.
A type derived from a built-in class whose values are native handles, such as an interface that extends `Response` or an `Omit` or `Pick` of it, an intersection with it, or a subset alias, names the built-in object itself and uses checked dynamic storage, also for an object the program makes for the type.
A member such a type adds or declares again is read from the object by name and checked against its declared type, so reading a required member that the built-in object does not have throws a `TypeError`, where Node.js answers `undefined`.
Assigning a member that the built-in object does not have throws, where Node.js stores it on the object.

`Object.keys`, `Object.values`, `Object.entries`, `for...in`, `JSON.stringify`, and `util.inspect` enumerate a record's keys in the order the object created them, as in Node.js.
scriptc chooses one order per record type from the order in which the program's object literals create the keys, follows object spread, rest destructuring, and record conversions back to the values they copy, and places an optional key that the program assigns after construction after the keys the object was built with.
When the object literals of one record type create its keys in different orders, including literals of two types with the same properties and property types, each value records the order its literal used: copies made by object spread, rest destructuring, and record conversions keep their source's order, and assigning an optional key the object lacks, including after `delete`, moves the key last.
A type keeps the declaration order of its first type, and `SC6002` reports object literals whose order differs where the program observes it, when one of its values gets its keys from a spread whose source order is not known, from a spread that also sets an optional key the source lacks, or from a copy of a value scriptc cannot trace, or when the type's values can take more than 16 orders.
`SC6014` reports such a type once, at a construction its order depends on, when the program observes the type's key order, although the objects the program builds may still list their keys as in Node.js.
`Object.assign` adds its source's properties to a record in the order the source object created them, and `SC6014` reports a call that adds two or more optional properties from a value converted at run time.

A record converted from a checked value, such as `const rows: { score: number }[] = JSON.parse(text)`, keeps the properties its type does not declare.
So does a record converted from an object with more properties, such as `const p: { a: number } = { a: 1, b: 2 }` or an argument passed to a parameter of a narrower type.
`JSON.stringify`, `Object.keys`, `Object.values`, `Object.entries`, `for...in`, `in`, `hasOwnProperty`, `propertyIsEnumerable`, `Object.hasOwn`, `util.inspect`, and untyped views of the record see them, together with the declared fields, in the source object's key order.
String conversion, `+`, and calls of `toString` and `toLocaleString` run the source object's own `toString` or `valueOf` when it has one.
Calling `valueOf` or `isPrototypeOf` by name on a record type that such an object converts into reports `SC1090` when the object has its own property of that name.
`Object.values` and `Object.entries` check those values against their static result type and throw a `TypeError` if one does not fit.
For a record converted from an object with more properties, these surfaces read the object's other properties when they run, so later writes to those properties are visible; the declared fields are a copy taken at the conversion.
Copies of such a record made by object spread, `Object.assign` or rest destructuring contain only the declared fields; a conversion to another record type keeps the properties too.
A record converted from an object keeps a property non-enumerable when the object held it as a non-enumerable data property that is writable and configurable, whether or not the record's type declares it.
`Object.keys`, `Object.values`, `Object.entries`, `for...in`, `JSON.stringify`, `util.inspect`, and untyped views of the record leave the property out, `in`, `Object.hasOwn`, and `Object.getOwnPropertyNames` include it, and its descriptor read through an untyped view reports `enumerable: false`.
A copy of such a record made by object spread, rest destructuring, or `structuredClone` holds a declared non-enumerable property as an ordinary enumerable one, where Node.js leaves it out of the copy, and `Object.assign` from the record throws a `TypeError` when the result's type requires the property.
`propertyIsEnumerable` called through the record's type answers `true` for a declared property that is not enumerable.
A conversion does not keep a declared property's read-only or non-configurable attribute or its accessor: it reads the property once, and typed writes to the field succeed.
A value that a compiled npm package returns into a type its declarations give it is refused with a `TypeError` when the object has an accessor, a read-only or non-configurable property, symbol-keyed properties, or a prototype, or when it is frozen, sealed, or not extensible, or when it has a non-enumerable property and the type keeps its keys without property attributes (an index-signature record).
Index-signature records list declared fields before the other keys.
A rest binding over an index-signature source copies its source's key order, so it lists the remaining declared fields before the other keys even when the source object created the other keys first.

An object literal asserted with `as` to a record type whose required properties it does not write, such as `const point = {} as Point`, keeps its identity, and the properties the program assigns afterwards complete it.
Reading a required property that nothing wrote throws a `TypeError`, where Node.js answers `undefined`.
The same holds for a read through a computed key, the read of a compound assignment such as `row[key] ??= value`, and an object spread of the value, which reads every property.
Freezing such an object freezes it for every reference, whether or not it is complete.
`scriptc build` reports `SC3001` at such an assertion when the object cannot be completed in place: when values of the asserted type may be part of a reference cycle, are one arm of a union of object types, are published or keep more than one property order, or when the literal holds a function.
Write every required property in the literal instead, or collect the properties in a `Record<string, T>` or `Partial<T>` value and assert that value once it is complete.

Optional fields and properties added through a record's `unknown` view can be deleted.
A required field has no absent state, so deleting it through the view throws `TypeError: Cannot delete property 'x' of #<Object>` and leaves the record unchanged, where Node.js removes the property.
A property that a record keeps but its type does not declare can be deleted through an `as` assertion that adds it as an optional member, such as `delete (entry as Entry & { legacy?: number }).legacy`.
`scriptc build` reports `SC1090` for such a delete when a record of a type that declares the property can be converted to the operand's type, because the property is then that record's field, which the delete cannot remove.

Records inherit `toString`, `toLocaleString`, `valueOf`, `hasOwnProperty`, `isPrototypeOf` and `propertyIsEnumerable` from `Object.prototype`.
A call of one of these methods by name runs the built-in with the record as its receiver, unless the record holds an own function under that name.
A keyed read such as `handlers[name]` of a record whose values are functions answers the built-in when the record lacks the key, so truthiness, `typeof` and calls match Node.js.
A keyed read of a record whose values are not functions, such as `counts["toString"]` on a `Record<string, number>`, answers `undefined` where Node.js answers the inherited function, so `counts[word] ?? 0` counts such a word as new.
Calling a built-in read from a record of functions throws a `TypeError` when its answer does not fit the record's function type, for example `valueOf` read from a `Record<string, (x: number) => number>`.
Other inherited names, such as `constructor`, read `undefined` from typed records.

### Buffer construction

`Buffer.from` accepts untyped strings, byte arrays, arrays, and plain array-like or Buffer JSON objects.
Encoding arguments must be supported literals.
`Buffer.from(arrayBuffer, byteOffset, length)` is a view of the ArrayBuffer or SharedArrayBuffer, with Node's offset and length checks.
Custom input `valueOf` hooks and opaque class or native-handle inputs throw an unsupported-operation error.
The deprecated `Buffer()` and `new Buffer()` take up to three arguments, without spreads, and follow Node: the first call made outside `node_modules` reports DEP0005 on the next tick, a number allocates zero-filled bytes, and other arguments convert as `Buffer.from` does.
In JavaScript, `Buffer` read as a value (the global or the buffer module's `Buffer`) is one function with Node's statics and `poolSize`.
Calling or constructing it through a stored value, reading its `prototype`, and its `copyBytesFrom` and `of` statics throw an unsupported-operation error.
`Buffer.prototype` members read as values are functions that run on Buffer receivers; inherited and symbol-keyed members throw an unsupported-operation error.

### readable-stream

`readable-stream` 2.x, 3.x and 4.x compile as Node.js's built-in `stream` module.
For 2.x and 4.x, that is the package's own behavior when the `READABLE_STREAM` environment variable is `disable`.
Without that variable, Node.js runs the package's JavaScript copy of the stream classes, so a native build can differ from Node.js wherever a program depends on that difference: `instanceof` checks against the built-in classes, class identity, error messages, and timing details.
The package's `lib/_stream_*.js` files also compile as the matching built-in classes, although Node.js loads the package's own implementations through them in every environment.
`readable-stream` 3.x is a copy of Node.js 10's streams.
Its own `disable` mode throws on Node.js 24, so no setting makes Node.js run it against the built-in classes; a native build maps the members its entry exports (the stream classes, `Stream`, `finished` and `pipeline`) to the built-in ones.

### HTTP headers

Without `@types/node`, the bundled declarations type every `req.headers` value as a string.
Reading `headers["set-cookie"]` is then refused, and a `{ ...req.headers }` copy holds `set-cookie` as one string joined with `", "`, where Node holds an array.
Read `headersDistinct["set-cookie"]`, or install `@types/node`, whose `headers["set-cookie"]` is `string[]`.

### Request URL credentials

`http.request()`, `http.get()`, `https.request()`, and `https.get()` accept a URL string or URL object in place of the options, with the options and callback after it.
A URL that carries a username or password throws a catchable error before anything is sent.
Node turns those credentials into a Basic `Authorization` header, and scriptc refuses the call rather than send the request unauthenticated.
Pass the header yourself instead, for example `{ headers: { Authorization: "Basic " + Buffer.from("user:pass").toString("base64") } }`.

### JSON callbacks

Function replacers and two-argument revivers support nested replacements, object-property deletion, and thrown exceptions.
Omitting the root through a replacer returns `undefined`.
Array replacers list the object keys to serialize, as in Node.js.
Stored `JSON.stringify` functions accept numeric and string indentation; direct calls require literal indentation.

Reviver source contexts are unsupported.
A reviver that takes a rest parameter or reads `arguments` is refused with `SC2020`, because Node.js passes it the source context as a third argument.
A reviver that deletes an array element throws, because checked arrays cannot represent holes.

Callback values use checked storage.
Typed records and arrays retain identity and mutations; record fields use declaration order; typed-array views retain backing storage and brands.
`Buffer.isBuffer` preserves the Buffer brand through `unknown`.
JavaScript callbacks receive the holder as `this`; TypeScript callbacks that access dynamic `this` are unsupported.

### Strings and sorting

Strings are stored as UTF-8, with lone surrogates kept as their own code units.
Length, string methods and relational comparisons such as `<` and `>` use UTF-16 semantics, including for lone surrogates.
`split` with a string separator and `replace` or `replaceAll` with a string pattern don't match a lone surrogate against half of a surrogate pair; regular-expression patterns do.
Output, file contents, and other encoded text write each lone surrogate as U+FFFD, as Node does.
File system paths and network host names that contain a lone surrogate reach the operating system unconverted.

One-argument `localeCompare` follows Node's default root-locale collation for Latin-script text (ASCII, Latin-1 Supplement, Latin Extended-A, combining diacritical marks, and U+2044 FRACTION SLASH): letters order before case and accents, so `"a".localeCompare("B")` is `-1`.
Strings containing other characters compare by code point, which can differ from Node's ICU collation; `SC6003` reports such string literals.
The locales and options arguments are unsupported.

`sort` and `toSorted` use stable natural merge sort.
Default ordering supports string, number, and boolean arrays, comparing their string forms in UTF-16 order; for example, `[2, 10, 1].sort()` produces `[1, 10, 2]`.
`sort` preserves holes at the end, while `toSorted` produces a fresh array with holes filled by `undefined`.
With an explicit comparator, consistent comparators produce the same sorted results as Node, but callback order and count can differ.

Typed-array `sort` and `toSorted` order numbers by default, with `-0` before `0` and `NaN` last.
With a comparator, they call it in the same order as Node and write the sorted values back after the last call.

### Published graphs

Graphs published with [`@scriptc/threads`](/docs/threads) are immortal: scriptc never frees them, even after every thread has stopped using them.
Publish long-lived data, not per-request messages.

A receiving thread reads the sender's objects, so they keep their classes, methods, and identity across threads and across messages.
Node.js delivers clones without prototypes, a new clone for every message.

Writes into published objects throw Node's frozen-object `TypeError`s.
In Node.js, `o.x = f()` evaluates `f()` before the write fails, and scriptc does the same, except for writes to union-typed class fields and for `Map`, `Set`, and array mutator methods, where the write check runs before the arguments are evaluated.
Private fields (`#x`) of published objects cannot be written either, although Node.js allows writing private fields of frozen objects.

### Dates and timezones

String constructors and `Date.parse` use V8's date-string grammar: ECMAScript date-time strings and the legacy forms Node.js accepts, such as RFC 1123 HTTP dates (`Mon, 01 Jan 2024 00:00:00 GMT`), `Date#toString` output, and `Jan 2, 2024`.
Strings without a timezone, and `new Date(year, monthIndex, ...)` fields, are local time.

Local calendar getters and local-time conversions use the operating system's timezone database.
Historical results can differ from Node.js when timezone data differs.
For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule.

`toString`, `toTimeString`, `String(date)` and template literals print the zone's long name after the GMT offset, as Node does (`GMT-0500 (Central Daylight Time)`). scriptc identifies the zone the way Node does, from `TZ` when it names a zone, else from `/etc/localtime`.
Outside Windows, a write to `process.env.TZ` changes the zone for later conversions and getters.
The names are the en-US names for the IANA zones; Node prints them in the language of the locale that `LC_ALL`, `LC_MESSAGES` or `LANG` selects, so with a non-English locale the name differs from Node's.
A `TZ` value that is a file path or a POSIX rule string (such as `EST5EDT,M3.2.0,M11.1.0`) gives offsets and names that differ from Node's, which ignores those forms.
On Windows, the name is the `GMT+hh:mm` form unless `TZ` names a zone.
On `wasm32-wasi`, local time is UTC and the name is `Coordinated Universal Time`.

### Weak collections and memory

`WeakMap` and `WeakSet` accept checked native objects and arrays, functions, numeric typed arrays, ArrayBuffers, native weak collections, and typed records, arrays, tuples, and classes that preserve identity through checked storage.
A promise is a key too: the same promise is one key whether it is held through its typed binding, a wider promise type, or an untyped value.
Entries keyed by a promise are released some time after the promise is gone, when later promise keys are added.
Copied values, other native handles, and symbol keys are unsupported.
Constructor seeds support arrays; custom iterators and detached methods are unsupported.
Weak collections do not retain their keys, but values that refer back to a key can keep it alive until deletion or collection release.

`FinalizationRegistry` keeps its registrations: `register` and `unregister` check their arguments and answer as in Node.js.
Its cleanup callbacks never run, which the ECMAScript specification allows.
Program output matches Node.js, which runs cleanup callbacks only when its garbage collector decides to.
Memory that a package frees only from a cleanup callback is not reclaimed until the program exits.
For example, a wasm-bindgen package frees the WebAssembly memory behind a wrapper object from its cleanup callback, so a long-running server that drops such wrappers without calling their `free()` method keeps that memory.
Call `free()` on wrappers the program no longer needs.
Symbol targets and unregister tokens are unsupported.
`WeakRef` keeps its target alive for as long as the `WeakRef` itself, so `deref()` always answers the target, which is Node.js's answer for a target the collector has not freed.
Constructing one checks its target as Node.js does: an object or a symbol outside the global registry, and a `TypeError` otherwise.

Memory uses reference counting.
Acyclic values are freed deterministically, and cycles are collected at defined collection points.
Native checked objects, arrays, Sets, and captured closures participate in cycle collection.
Cycles through opaque native capsules can remain allocated.

### Foreign-thread callbacks

FFI format 5 accepts retained, context-bearing `void` callbacks invoked by library-owned threads.
The native callback copies arguments and queues work; the script closure runs on the event loop at least one turn later.
These callbacks are not suitable for real-time execution.

Value-returning callbacks and direct script execution on foreign threads are unsupported.
Reference counting and exception state belong to the script thread, and waiting for the event loop can deadlock.

### TLS ciphers

Before the handshake Node's `tlsSocket.getCipher()` returns `undefined`, and after close `null`.
Its declared result is the `{ name, standardName, version }` record, so a typed read there throws a TypeError instead.
After the handshake the record matches Node, with OpenSSL's suite name in `name`.
The suite itself is the one the bundled mbedTLS negotiates, which can differ from OpenSSL's choice for the same peers.

### TLS certificates

`tlsSocket.getPeerCertificate()`, `getCertificate()`, `getPeerX509Certificate()`, and `getX509Certificate()` follow Node, including the `detailed` issuer chain, on sockets whose type is known.
On a TLS socket held in an `unknown` or `any` value, or in an untyped JavaScript parameter, they throw an error that names the call as not supported.
Client certificates follow Node: `tls.createServer`, `https.createServer`, and `new tls.TLSSocket` take `requestCert`, `rejectUnauthorized`, and `ca`, and `tls.connect` and `https.request` take `cert` and `key`.
A server's `authorized` and `authorizationError` report the client certificate's verification as Node does, and a server, client, or `tls.createSecureContext` given a `ca` also sends the chain it completes, as OpenSSL's automatic chain does.
A server verifies client certificates against the `ca` of the context it serves, including contexts from `addContext()` and `setSecureContext()`: an empty `ca` (an empty Buffer or array) trusts no certificate, and an omitted `ca` trusts the default CAs.
The same holds for a client's `ca` in `tls.connect`, `https.request`, and `http2.connect`, where an empty string is no `ca`, as in Node.
In `http2.connect`, a `ca` held in an `unknown` or `any` value that is an empty string is refused.
`requestCert` beside a TLSSocket's `secureContext` or an `SNICallback`, client certificates on http2 secure servers, and, on a tls server without an `SNICallback`, a `cert` without a `key` (or the reverse) are refused.
An `SNICallback` may be the only certificate source: the server options may leave out the default `cert`/`key` pair, and each handshake is then served from the context the callback answers.
`server.addContext()` and `server.setSecureContext()` follow Node on tls and https servers, including wildcard names, which match one label without regard to case.
A name with regular-expression groups or alternation, or one outside ASCII, is refused, as are both methods on a server that is not a tls or https server.
The `'tlsClientError'` event reports Node's error for a failed handshake: OpenSSL's code and reason, or `socket hang up` for a client that leaves.
OpenSSL's messages leave out the thread identifier Node prints before `error:` and the source location after the reason.
A failure whose OpenSSL reason the runtime does not know, such as a malformed client hello, is reported as an error with code `SC2020` that says so.
The X509Certificate those accessors return has the same refusals as any other certificate (see Crypto), so in JavaScript narrow it, for example with `if (cert)`, before reading its fields.

### TLS sessions

TLS servers send no session tickets, so a connection to them cannot be resumed.
A client's `'session'` event fires once per connection, when its handshake finishes and before `secureConnect`.
Node fires it for each ticket the server sends, after `secureConnect`, and OpenSSL servers send two.

### Console and process streams

`console.log`, `info`, and `debug` write to stdout; `error` and `warn` write to stderr.
They accept `unknown`, printing strings verbatim and other values through native `util.inspect`.
Console methods never color their output on their own: where Node colors numbers, booleans and the values inside objects and arrays, on a color terminal or with `FORCE_COLOR` set, the executable prints them plain.
Direct calls support global `console` and `node:console` imports.
Console output, `util.format`, and `util.formatWithOptions` with a format string known only at run time, spread arguments, or an options object format their arguments at run time.
There, `%s` of a class instance runs the class's `toString` or `[Symbol.toPrimitive]` conversion, declared or inherited, and uses its answer.
An answer that is an object or a symbol throws Node's `TypeError`.
A conversion the compiled program cannot call throws `SC2020`: a `toString` the program replaces at run time, or one that needs arguments Node does not pass.
`%s` of a function throws `SC2020`, because a compiled binary does not keep the source text Node prints.
`util.inspect` with `colors`, with `showHidden`, with option values known only at run time, or in the `inspect(value, showHidden, depth, colors)` form renders the value's untyped view in the runtime with Node's styles.
Colored output of errors, promises, regular expressions, `DataView`, `ArrayBuffer`, weak collections, iterators, and other native objects throws `SC2020`.
Inspect options other than `depth`, `colors`, `customInspect`, `showHidden`, `compact` (a number or `false`), `breakLength`, `maxArrayLength` and `maxStringLength` (a non-negative integer or `null`), `getters` (a boolean, `'get'` or `'set'`), `sorted` (a boolean), and `numericSeparator` (a boolean) throw `SC2020` when they are set to a value other than Node's default.
`util.inspect` calls `[util.inspect.custom]` methods of classes and objects with Node's depth, options, and `util.inspect` arguments, and `console.dir` and `customInspect: false` skip them.
A class instance with such a method that is held as an untyped value throws `SC2020` when it is inspected, and a class whose subclasses declare the method without declaring it itself is refused.
Under `showHidden` — the option, the boolean second argument, and `%o` — arrays print their hidden `[length]` entry, typed arrays their `[BYTES_PER_ELEMENT]`, `[length]`, `[byteLength]`, `[byteOffset]`, and `[buffer]` entries, `ArrayBuffer` and `DataView` their hidden byte fields, and objects their non-enumerable and symbol keys.
Objects also list the properties they inherit from up to three user-defined prototype layers, nearest layer first.
Values this runtime has no hidden view of — functions, errors, `RegExp`, weak collections, and `SharedArrayBuffer` (and views over one) — throw `SC2020` under `showHidden`, and `console.dir` renders the option through the value's checked-dynamic view, refusing the types that have no conversion to it.
`console` is one object and each of `console.log`, `info`, `debug`, `warn`, and `error` is one function, so both can be stored, passed, destructured, bound, compared, and called without a receiver.
A stored method formats the arguments it is called with at run time, as `util.format` does, including format specifiers.
A value the untyped inspector does not render, such as a `RegExp` or a promise, throws `SC2020` when a stored method prints it.
`console` flows into a record type whose members it has, such as `interface Logger { log(...args: unknown[]): void; error(...args: unknown[]): void }`, and each member of the record is console's own function.
The record is a separate value, so comparing it with `console` is refused, and a record type that declares `table`, `dir`, `time`, `trace`, or another member outside the five output methods is refused.
`{ ...console }` copies console's own keys in Node's order.
In TypeScript, keeping a console member other than the five output methods as a value, such as `const table = console.table`, is refused; `typeof console.table` and a truthiness test compile.
Reached through an untyped value, those members answer a function whose call throws `SC2020`.
`promise.catch(console.error)` is refused by the rule for `catch` handlers; `promise.then(undefined, console.error)` compiles.
Replacing a console member, such as `console.log = capture`, is refused in TypeScript and throws `SC2020` through an untyped value, because direct console calls never read the member.
`console` passed into a Node island is the island's own console; a console nested in an object that crosses into an island throws.
Members of custom `Console` instances as values are unsupported.

JavaScript and TypeScript can store `process.stdin`, `stdout`, and `stderr`, replace output `write` methods, and call saved methods with their stream receiver.
The replacement can be assigned through a stored stream as well, such as `out.write = wrapper` where `out` is a `NodeJS.WriteStream` parameter holding `process.stdout`.
`write` is the one member a process stream keeps: an assignment to another member, such as `process.stdout.isTTY = true`, compiles and throws an `SC2020` error when the statement runs, where Node stores the value.
A program that holds `process.stdout` or `process.stderr` as a value shares the stream's `write` method with its Node islands: a replacement installed by either side sees the output of both, and either side can call or restore the method the other saved.
In the islands of such a program `write` is an own accessor property of the two streams, so `Object.hasOwn(process.stdout, "write")` is `true` before any replacement.
There, `Object.defineProperty(process.stdout, "write", …)` throws a `TypeError`, and `delete process.stdout.write` throws one in strict code and answers `false` otherwise, where Node would replace or remove the method.
Writes that island code makes through the shared method do not pass through the island stream's own state, so its `writableLength`, `'drain'` event and `'error'` listeners do not reflect them.
In any other program with islands, a `write` replacement that island code installs sees island output only.
A worker thread's output does not pass through a `write` method replaced on the main thread.
Input supports data/end/error/readable listeners, pause/resume, buffered reads with `read(size)` and `readableLength`, and TTY raw mode.
Output writes are synchronous, with successful callbacks on the next-tick queue; queued backpressure is not modeled.
An executable built with `--stdout-buffering` instead keeps stdout bytes in a 64 KiB buffer whenever stdout is not a terminal, so a pipe or file can receive a line later than the call that wrote it; the runtime settles the buffer when it fills, before a stderr write, when the event loop sleeps, before a blocking stdin read, before `process.kill`, on `process.exit`, on an uncaught error, and at exit, and output that a kill skips those paths for (such as `SIGKILL`) can be lost up to the buffer size.
A terminal keeps one write per call.
Because no write is ever queued, `writableLength` is 0, `writableNeedDrain` is false, `writableHighWaterMark` is 65536, and `'drain'` never fires, so drain listeners register silently and `events.once(process.stdout, "drain")` stays pending, as it does in Node when no write returned false.
Stored streams do not support piping, encoding changes, arbitrary properties, or other events.
`getColorDepth` and `hasColors` exist on `process.stdout` and `process.stderr` only when the stream is a terminal, as in Node, so TypeScript refuses an assertion that the optional call answered, such as `process.stdout.getColorDepth?.() as number` or a trailing `!`, with `SC2020`; test the answer instead, as in `process.stdout.getColorDepth?.() ?? 1`.
A call of either method with an environment that is an array, a `Map`, a `Set`, or a function builds with warning `SC6015` and throws an error that names `SC2020` when it runs on a terminal, and so does a call whose environment is an object literal with the single variable `TERM` set to `constructor` or `__proto__`.
The build cannot see the same `TERM` value in the process's own environment or in an environment passed through a variable, so it prints no warning for it: the call throws the `SC2020` error on a terminal, where Node answers a member of `Object.prototype` instead of a color depth.
A TypeScript `NodeJS.WritableStream` parameter that only receives `process.stdout` or `process.stderr` supports `write(string)`.
When other streams flow into the same parameter, the process streams join them as `Writable` instances with the full writable surface.
`process.stdout` and `process.stderr` also flow into a record type that declares only methods, such as `interface Sink { write(text: string): void }`.
A call through the record runs the stream's current method with the stream as its receiver, so a program that replaces `process.stdout.write` reaches its replacement.
The record is a separate value: it is not identical to the stream, and it exposes only the methods it declares.
A record type that also declares a data member, such as `isTTY`, throws `SC2002`.
An instance of a stream class such as `Writable` throws `SC2002` in a record type of methods as well.
For those, pass an object literal that wraps the stream: `const sink: Sink = { write: (text: string) => { process.stdout.write(text); } }`.

### graceful-fs

The `graceful-fs` package compiles as `node:fs`.
Its imports and `require("graceful-fs")` calls resolve to the builtin module, so the package's JavaScript is not compiled, and `gracefulify(fs)` as a statement does nothing.
Programs behave as with the package except under file-descriptor exhaustion: graceful-fs queues operations that fail with `EMFILE` or `ENFILE` and retries them when a descriptor closes, while a compiled program reports the error to its caller, as `fs` does.
In a compiled program, `require("graceful-fs") === require("fs")` is `true`.

### DNS lookup errors

After a failed `dns.lookup` or `dns.lookupService`, Node calls the callback with the error alone, so the address and family parameters are `undefined`.
A callback whose address parameter is typed `string` (as Node's own type declarations type it) receives `""` and `NaN` there, or an empty array for `all: true`, because a typed string or number cannot hold `undefined`.
An untyped callback receives the error alone, as in Node.
The promise forms in `node:dns/promises` (also reached as `dns.promises`) reject with the error and are unaffected.

### Server and socket errors

`server.listen()` on a server that is already listening ends the process with exit status 1 and reports Node's `ERR_SERVER_ALREADY_LISTEN` as an uncaught error.
Node throws that error at the call, where a `try` statement can catch it.
A server that has closed does not listen again: a later `listen()` call is ignored and its callback never runs.
After a server has closed, another `close(callback)` call runs the callback with `ERR_SERVER_NOT_RUNNING`, as in Node, but the server's `'close'` listeners do not run a second time.
A write callback of a socket that is destroyed before it connects, or whose connection is refused, receives `ERR_SOCKET_CLOSED_BEFORE_CONNECTION`, as in Node.
A write callback of a connected socket that is destroyed while the write is pending runs without an error, and the callback of `socket.end()` does not run; Node passes both the error of the cancelled write.

### Socket data encoding

After `socket.setEncoding()`, an inline `'data'` listener receives strings, as in Node, whatever type its chunk parameter declares.
A listener passed by reference, such as a named function whose parameter is typed `Buffer`, keeps its `Buffer` parameter, so a string chunk throws `TypeError: expected Uint8Array at $[0], got string`.
Declare that parameter as `string` or `string | Buffer`, or pass an inline function.

### UDP socket options

`socket.addMembership()` and `socket.dropMembership()` on a `dgram` socket that is not bound yet throw `SC2020`; Node binds such a socket to an ephemeral port first, so bind the socket before joining a group.
The `SystemError` that the buffer size methods throw (`ERR_SOCKET_BUFFER_SIZE`, for example from `getRecvBufferSize()` before `bind()`) has Node's `message`, `code`, `errno` and `syscall`, but no `info` object.

In JavaScript, `socket.send()` also accepts lists of buffers, sends on a connected socket, and takes a completion callback, which receives `null` and the byte count, or the send's failure instead of an `'error'` event.
The callback runs after the socket's pending events, where Node runs it on the next tick.
In TypeScript, `send()` with a callback, a list, or on a connected socket fails to compile with `SC2020`.
JavaScript can read a socket's `on`, `once`, `addListener`, `removeListener`, `off`, `emit`, `send`, `close`, `bind`, `connect`, `disconnect`, `address`, `ref`, and `unref` as function values, for example `socket.emit.bind(socket)`; reading other members as values is refused with `SC1090`.
Listeners registered that way, or with untyped parameters, take only the `message`, `error`, `close`, `listening`, and `connect` events.
When a socket closes, listeners registered from TypeScript are released, so an `emit()` from JavaScript after `'close'` reaches only listeners registered from JavaScript.
A datagram socket's `'error'` event, for example for an address in use, has Node's `message`, `code`, `errno` and `syscall`, but not the `address` and `port` properties Node also sets; the error a `send()` callback receives has all of them.

### Unix domain sockets and request options

`server.listen(path)` and the `socketPath` option of `http.request()` and `https.request()` use Unix domain sockets on Linux and macOS.
On Windows, where Node maps these paths to named pipes, they fail with `ENOTSUP`.
`server.address()` on a server listening on a Unix socket returns the socket path, as in Node, when the result is typed `string | AddressInfo | null` (the @types/node declaration).
Where it is typed as the `{ address, family, port }` record alone, which cannot hold the path, the call throws an error for such a server.
A `backlog` argument to `server.listen()`, as in `listen(port, host, backlog, callback)` or `listen(path, backlog, callback)`, is refused at compile time; leave it out.
The listen queue length is Node's default, 511.
When the options of `http.request()` come from a variable, or from an object literal that spreads one, they are read at run time with Node's validation errors.
The `auth`, `createConnection`, `family`, `hints`, `insecureHTTPParser`, `joinDuplicateHeaders`, `localAddress`, `localPort`, `lookup` and `uniqueHeaders` options then throw a runtime error when they hold a value, and so do `setHost: false` and `setDefaultHeaders: false`.

### Process arguments and errors

`process.argv[0]` is `"scriptc"`, `process.argv[1]` is the binary's path, and program arguments begin at index 2.
In programs that use `node:test`, a leading `--test-update-snapshots` argument is Node's snapshot update flag, and leading `--test-reporter=spec` and `--test-reporter-destination=...` arguments are Node's reporter options: each is kept out of `process.argv` and appears in `process.execArgv`, in the order given, which is otherwise `[]`.
`process.version` and `process.versions.node` report the Node.js release the runtime is verified against.
The `process.features` object cannot be changed: under Node nine of its flags are writable and a program may add keys, but a compiled binary reads a flag as a constant, so a write to the object throws the `TypeError` of a frozen object and `Object.isFrozen(process.features)` is `true`.

Uncaught exceptions print `Uncaught <value>` to stderr instead of Node.js's source excerpt and stack-trace block; thrown strings and numbers print their value.
An uncaught Error and an unhandled rejection with an Error reason also print the stack frames the Error carries, one `at` line each under that first line.
A release build keeps stack frames only in programs that read an error's `stack`; in every other program its report is the first line alone.
A build with `--optimization dev` and without `--strip` keeps a frame in every function that can raise an error, and each frame names the file, line, and column of the statement that was running: `at decode (/app/src/user.ts:8:3)`.
A function that only computes, and so could fail in no other way than by exhausting the stack, keeps no frame in either build: a stack overflow in it is reported at the statement that called it.
A build with `--sanitize` keeps the frames of a release build.
A frame takes 16 bytes of stack per call, so the deepest recursion of the functions that keep one is slightly lower in a `dev` build.
The position is the start of the statement, where Node.js reports the call or `new` expression inside it.
Such a build also lists constructors, methods, and accessors (`at new Shape`, `at Shape.area`, `at Shape.get sides`) and the top-level code of a module (`at /app/src/main.ts:12:1`), which a release build leaves out.
The frames of an async function or a generator end at that function: the functions awaiting or iterating it are not listed.
On Linux and macOS, a 64-bit `dev` executable that aborts, for example on an array index that a non-null assertion wrongly promised was in range, prints the same frame lines under the runtime's message.
Exit codes and output written before the exception are preserved.
Runtime errors expose `message` and Node's `code` as typed members.
The other own properties Node gives them, such as a system error's `errno`, `syscall`, and `path`, a child process error's `status`, `stdout`, and `stderr`, or an assertion error's `actual` and `expected`, are readable through an untyped view of the error (for example a cast of a caught value) and appear in `util.inspect` output.

Errors carry stack frames that name the function and the source file, without Node.js's internal frames; `file:line:column` positions appear only in the `dev` builds described above.
An error's `stack` text therefore differs between a `dev` build and a release build of one program, and so does what `console.log` prints for an Error: a `dev` build shows its frames where a release build of a program that reads no `stack` prints `[Error: message]`.
A function passed to `Error.captureStackTrace` as the frame to stop at keeps a frame without a position.
A class constructor that passes `this.constructor`, `new.target`, or its own class to `Error.captureStackTrace` keeps no frame, and neither do the constructors of its subclasses, so the trace starts at the function that ran `new`.
`console.log`, `util.format`, and `util.inspect` print an Error from its stack the way Node.js does: the frames under the `name: message` heading, brackets when the stack has no frame line, and a subclass's header named after its class (for example `Bailable [Error]: message`).
Only the frames themselves diverge, so the output agrees with Node.js when `Error.stackTraceLimit` is `0`.

### Module path globals

`__dirname` and `__filename` are compile-time constants: the absolute path of the module's source directory and file at build time, the same strings as `import.meta.dirname` and `import.meta.filename`.
A `.ts` or `.tsx` file keeps them even when it uses `import` syntax, which Node.js would load as an ES module without these globals, because TypeScript projects are commonly shipped as CommonJS bundles where they exist.
In such a file `typeof __dirname` is `"string"`, where Node.js running the source directly reports `"undefined"`.
JavaScript ES modules (`.mjs`, or `.js` in a `"type": "module"` package) do not have them, as in Node.js.

### Defined environment variables

A `--define` value replaces what the executable's environment holds for the variable, so the program and its child processes see the defined value whatever the executable is started with.
The `if` statements of JavaScript files that the value decides are resolved at build time, so assigning or deleting a defined variable is a build error, and a change made another way, such as a write through a name computed at run time or `Object.assign(process.env, ...)`, doesn't change which branch runs.
The other conditions the value decides (the `if` statements of TypeScript files, conditional expressions, and the right operands of `&&` and `||`) are resolved at build time too, except in a program that writes or deletes an environment variable through a name computed at run time, and in a program with code that runs in a Node island, which shares the environment and can change the variable.
In those programs they are evaluated when the program runs.
Named imports of a CommonJS module that picks its exports this way see the names the selected module exports.
Node.js takes those names from the module that the last such `module.exports = require()` assigns, whatever the condition, so the two differ when the modules export different names.

<span id="embedded-javascript-engine" />

## Runtime code evaluation

A compiled program embeds no JavaScript engine, so it cannot run JavaScript source at runtime.
`eval`, indirect `eval`, `new Function`, and `Function` behave as they do under `node --disallow-code-generation-from-strings`, a deliberate difference from default Node.js: every call throws a catchable `EvalError` with the message `Code generation from strings disallowed for this context`.
The arguments are evaluated first, and the Function constructor converts them to strings before it throws, as Node.js does.
Packages that test for code generation before using it, such as zod 4, take their interpreted path.
Code that needs to evaluate source at runtime must be rewritten; to compute a value from code at build time, use `comptime`.
The `vm` module is refused for the same reason.
See [npm Dependencies](/docs/dependencies#which-packages-compile) for which packages compile.

## Build-time evaluation

`comptime(() => ...)` runs its callback while the program compiles, in an evaluator process based on QuickJS, and embeds the returned value as a constant.
The callback must be written inline in the call, must not be `async` or a generator, and must not refer to bindings outside itself, to `console`, or to another `comptime` call.
It sees the ECMAScript built-ins and nothing of the compiler or the machine: `process`, `require`, module loading, timers, file and network access, `Intl` and `WebAssembly` do not exist there, and using one is a build error.
The evaluator is a separate process that holds no object of the compiler, and only source text goes in and only the result's data comes back.
It is not a hardened sandbox: its protection rests on the JavaScript engine having no memory-safety bugs, so build only code you would run.
Each callback has 2 seconds and 256 MB, and exceeding either is a build error that names the limit.
Promise reactions never run, so a callback that leaves one pending, or leaves a rejected promise unhandled, is a build error.
The result must be a finite number, a string, a boolean, or arrays, records and unions with `null` or `undefined` of those, and its type must not contain itself.
Only a result's own enumerable properties are read, and an array hole reads as `undefined`.
A result that contains itself, is nested more than 512 levels deep, has an array of more than 1,000,000 elements, or is larger than 64 MB is a build error.
Two places in a result that hold the same object become two separate constants.
`Math` members give the values Node.js computes.
Members whose Node.js result depends on locale data throw at build time: `toLocaleString`, `localeCompare`, `toLocaleLowerCase`, `toLocaleUpperCase`, and `toString`, `toTimeString`, `toLocaleDateString` and `toLocaleTimeString` of a `Date`.
`Number.prototype.toString` with a radix other than 10 throws at build time unless the number is a safe integer.
`Date.now()`, `Math.random()` and the local time zone are the build machine's at build time.
The message of an error the engine itself throws, such as the `TypeError` for reading a property of `undefined`, is QuickJS's wording, not Node.js's.

## Process events

Static signal listeners resolve names on the target platform, including `SIGWINCH` on POSIX.
`on`, `once`, `off`, and `removeListener` accept computed signal names and deliver the signal name and number.
Computed names for other event families and chained registrations are unsupported.
Windows registration is limited to CRT signals; WASI has no OS signals.

Literal `uncaughtException` and `uncaughtExceptionMonitor` registrations compile.
Handled exceptions resume queued native work; monitors alone do not suppress failure.
Error objects and primitive exceptions can reach listeners, while arbitrary native objects remain subject to exception-value restrictions.
Exception capture callbacks are unsupported.

`NODE_NO_WARNINGS=1` leaves out the default report of a process warning on stderr once the program has called `process.emitWarning` or registered a `'warning'` listener; listeners still run.
Before that, and in a program that does neither, the runtime's own warnings (the listener-leak warning of `EventEmitter` and `EventTarget`, the timer duration warnings, the `dgram` deprecation) are still printed.
The variable is read when a warning is reported, so an assignment to `process.env.NODE_NO_WARNINGS` changes later reports, where Node reads it once at startup.

## Native addons

Node-API (N-API) and V8 `.node` addons depend on Node.js itself, so compiled programs can't load them.
Direct `createRequire` calls to local addons are rejected at compile time.
A package that requires an addon needs a JavaScript implementation that compiles, or a replacement through native FFI.

A package that looks for an optional addon at run time compiles with `--npm-static`.
In JavaScript dependencies, a `createRequire` require call with a computed specifier, and `require.resolve` called with a computed request or read as a value, resolve the specifier with the Node.js algorithm when the program runs.
A `createRequire` loader held as a value, stored in an object or passed to a function, in TypeScript or JavaScript, is a function that resolves its request the same way when it is called.
It has none of the properties that the Node.js `require` function carries: in TypeScript, a statement that reads `resolve`, `cache`, `main`, or `extensions` through a held loader builds with warning `SC6015` and throws an error that names SC2020 when it runs.
`require.resolve` returns the path Node.js returns.
When nothing resolves, both throw the `MODULE_NOT_FOUND` error that Node.js throws, so a package that probes for a missing addon takes the same branch as in Node.js.
When the specifier of a require call resolves to a native addon, a builtin, or a file that is not part of the compiled program, the program prints an SC2020 error that names it and exits with status 1, because Node.js would load that module.
A `catch` clause around the call does not run in that case.
Resolution steps that the runtime does not reproduce, such as package `imports` specifiers, `require.resolve` options, and subpaths that a package does not export, end the program the same way.
Coverage lists these calls as deferred to runtime.
Other computed uses compile when they are inside a `try` block: a require call with spread arguments, a `require.resolve` call with another argument count, and `require.resolve.paths` called with a computed request evaluate their arguments and then throw an error, and `require.resolve.paths` read as a value is a function that throws when called.
Coverage lists these as deferred to runtime as well, and the package's `catch` clause then runs as it does in Node.js when the addon is missing.
Outside a `try` block, these uses keep the package refused.
This applies until compiled programs can load addons through Node.js.

Use [Native FFI](/docs/ffi#replacing-a-node-api-addon) to call a plain C ABI function linked from an object or static archive.
The guide includes a replacement for a small Node-API helper.

## WebAssembly modules

scriptc translates WebAssembly modules to native code when it builds the program; there is no interpreter or JIT in the executable.
A build translates every module whose bytes it can see: files at paths the compiler resolves (`require.resolve`, `createRequire(...).resolve`, `import.meta.resolve`, `new URL("./x.wasm", import.meta.url)`, `path.join(__dirname, ...)`), including `.so` WebAssembly shared libraries such as quickjs-wasi's extensions, modules inlined as base64 or byte-array literals, `.wasm` files shipped by packages whose code uses WebAssembly, and files listed in a `scriptc.wasm` array in `package.json`.
At run time `WebAssembly.compile`, `instantiate`, `validate` and `new WebAssembly.Module` find the translated module by the SHA-256 of the bytes they are given.
Bytes the build did not see, including bytes produced at run time, reject with a `CompileError` that says the module was not translated, and `WebAssembly.validate` answers `false` for them.
`scriptc coverage` lists the modules a build finds, with their size, whether they validate, and the WebAssembly proposals they use.

Translated modules can use the WebAssembly 2.0 feature set: the MVP plus sign-extension operators, non-trapping float-to-int conversions, bulk memory, multi-value, mutable globals, reference types, and extended constant expressions.
Modules that use SIMD, threads, exception handling, tail calls, typed function references or GC, memory64, or multiple memories are not translated, so loading them rejects with a `CompileError` that names the proposal the module uses, and `WebAssembly.validate` answers `false` for them; packages that fall back to a scalar build when that happens, such as undici and es-module-lexer, take their fallback.
Invalid modules the build finds reject with a `CompileError` that includes the validation error.

Supported: `WebAssembly.compile`, `instantiate` (with bytes or a `Module`), `validate`, `new WebAssembly.Module`, `new WebAssembly.Instance`, `new WebAssembly.Memory`, `new WebAssembly.Table`, `new WebAssembly.Global`, `WebAssembly.Module.imports`, `exports` and `customSections`, and `instanceof` for every class; the error classes `CompileError`, `LinkError` and `RuntimeError`, constructed with or without `new`; `globalThis.WebAssembly` and its `compile`, `instantiate` and `validate` functions as values.
Instances expose exported functions, memories (`buffer`, `grow`), tables (`get`, `set`, `grow`, `length`) and globals (`value`, `valueOf`).
Imports can be functions, numbers or BigInts for immutable globals, and memories, tables and globals that the program constructs or other instances export; their limits, types and mutability are checked with Node.js's `LinkError` messages.
Function references from a table or global reach JavaScript as callable functions, and exported functions can be stored in tables and globals, which is what dynamic linkers such as quickjs-wasi's extension loader do.
Traps throw `WebAssembly.RuntimeError` with Node.js's messages, unbounded recursion throws `RangeError`, and an exception thrown by an imported function propagates through the WebAssembly frames unchanged.
A `WebAssembly.Module` can be sent to a worker through `workerData` or `postMessage`.

Not supported yet: the `Tag` and `Exception` constructors, shared memories (`new WebAssembly.Memory({ shared: true, ... })` throws a `TypeError`), `compileStreaming` and `instantiateStreaming`, and the classes used as values other than with `new`, `instanceof` and `WebAssembly.Module`'s static functions.
TypeScript sources fail to build with SC2020 at each such use, and JavaScript sources throw that diagnostic when the statement runs.
Programs built for `wasm32-wasi` can't use the WebAssembly API.

Differences from Node.js:

- Growing a memory, from JavaScript or inside the module, does not detach the previous `ArrayBuffer`.
  The memory never moves, so views created before the growth stay valid but keep their old length; code that reads `memory.buffer` again after growing, as WebAssembly glue code does, behaves as in Node.js.
- `valueOf()` answers a `WebAssembly.Global`'s value only when the receiver's static type is `WebAssembly.Global`; through an untyped value it answers the object, and converting a `Global` to a primitive uses the object rather than its value.
- Instances are never freed, together with their memories, imports and exports.
- A load whose result the module never uses may be removed instead of trapping when its address is out of bounds.
  Loads whose result is dropped explicitly still trap.
- NaN payloads may differ, for example when a signaling NaN passes through arithmetic or a JavaScript number.
- `WebAssembly.compile` and `instantiate` with bytes settle after the current microtasks, as in Node.js, but can settle after `setImmediate` callbacks that Node.js would run later.
- The executable reads `.wasm` files at run time from the paths they had when it was built.

## WASI target

The `wasm32-wasi` executable target supports async/await, promises, generators, timers, portable event-loop work, stdin/readline, filesystem callbacks and promises.

WASI Preview 1 does not provide sockets, process spawning, OS signals, network interfaces, or filesystem notifications.
Networking/fetch, child processes, signal APIs, `os.networkInterfaces()`, the `os` host facts without a WASI equivalent (`cpus()`, `hostname()`, `freemem()`, `loadavg()`, `uptime()`, `getPriority()`), and `fs.watch` are rejected before linking with `SC3002`.
`--sanitize` and native FFI are unavailable.

Library mode emits an async-free Wasm reactor with named host imports.
Native runtime localization and thread instances are unsupported.
Each Wasm instance owns independent state.
Filesystem access is limited to directories opened by the host; `scriptc run` exposes the current working directory and `/tmp`.
See [Platform Support](/docs/platforms) for build and run details.

## Tooling and native FFI

`scriptc run` does not forward extra CLI arguments to the program.
Build an executable and invoke it directly to pass arguments.

Native FFI links C functions declared in a manifest.
Formats 2–5 support call-scoped callbacks, copied string/byte callback parameters, explicitly released retained callbacks, and asynchronous foreign-thread delivery.
Format 6 adds single-precision floats, signed 8-bit and signed/unsigned 16-bit integers, and borrowed writable byte spans.
Variadic calls, structs by value, owned pointer/string/byte returns, runtime dynamic-library loading, and library-mode builds are unsupported.
See [Native FFI](/docs/ffi).

Numbers follow JavaScript's double-precision semantics.

## Browser embedding

Wasm reactors require a host-provided WASI Preview 1 adapter.
A reactor can run supported three.js CPU operations and send animated geometry, material state, and resource lifecycle notifications to an external web application through declared imports.
The application supplies DOM, graphics, media, audio, and decoder bindings, including the renderer.

Library mode excludes promises, async functions, timers.
See [WebAssembly Modules](/docs/wasm) for the embedding contract.

## Native debugging

Executable builds with `--optimization=dev` support source breakpoints and native stack frames for compiled TypeScript and JavaScript.
LLVM builds include source names and lexical scopes for locals, parameters, captured bindings, and module globals.
Release builds and builds with `--strip` omit these source mappings.
On macOS, executable builds produce an adjacent `.dSYM` bundle using the Command Line Tools' `dsymutil`.

Debugger values use native representations.
Numbers and booleans display directly.
Strings expose their `ScrStr` layout, including UTF-8 `data` and byte `len`; tagged unions expose their `tag` and `slot` fields.
Other heap values and scalar bindings captured before initialization can appear as opaque pointers.

Debuggers can't evaluate TypeScript expressions or format values as JavaScript objects.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)