Limitations

Honesty is the product. The static surface is large but not total, and a few behaviors diverge from Node by design. This page is the plain-language version; scriptc coverage on your program is the real answer for your code, and every blocker it reports is specific and coded. Nothing on this page is silent: everything here is either a compile error or a documented, numbered divergence.

Native debugging

Executable builds with --optimization=dev support source breakpoints and native stack frames for statically compiled TypeScript and JavaScript. LLVM builds also describe source locals, parameters, captured bindings, and module globals using their source names and lexical scopes. Numbers and booleans display directly; strings expose their native ScrStr layout, including UTF-8 data and byte len, and tagged unions expose their tag and slot arms. Other heap values and forward-captured scalar bindings can appear as opaque native pointers. TypeScript expression evaluation, JavaScript object formatting, and stepping through code executed by the dynamic engine are not implemented. Release builds and builds with --strip omit these source mappings. macOS uses an adjacent .dSYM bundle generated with the Command Line Tools' dsymutil.

What doesn't compile (yet)

These are rejected at compile time with an SC code, a code frame, and usually a rewrite hint. A non-exhaustive tour of the ones you're most likely to meet:

Language edges

  • Error, TypeError, RangeError, and SyntaxError accept { cause } options, including through inherited constructors. Reading, assigning, deleting, and testing "cause" in error preserve the distinction between an absent cause and an explicitly undefined cause. Constructor causes are non-enumerable; assignment creates an enumerable property when the cause is absent. Property descriptors and checked-value aliases share the same cause, including getters and setters installed after construction. Accessor constructor options and explicit subclass redeclarations of cause remain unsupported.
  • Native class fields, inherited accessors, and added properties retain their identity across untyped values. Reflection such as JSON.stringify and Object.keys still requires every declared field to have a supported checked-value representation; classes containing opaque fields such as native Maps retain a checked error for those operations.
  • Native class methods can be stored and passed as unbound function values. A field call supplies the containing instance as this; call, apply, and bind work when the function signature can cross the checked-value boundary. Extracted overrides keep the selected method even when called with another receiver. Receiver validation accepts the declaring class and its subclasses; borrowing a method onto an unrelated object remains unsupported. Detached methods that use this, builtin method values, abstract method declarations, unspecialized generic methods, and incompatible override signatures retain checked errors or compile-time refusals.
  • Loose ==/!= comparisons compile across statically represented primitive and primitive-union operands, including number/string/boolean/BigInt coercions and the x == null/x != null nullish idiom. Object-to-primitive comparisons remain fenced because custom valueOf/toString methods can execute arbitrary code; convert the object explicitly first.
  • Block- and function-scoped using and await using compile for statically represented classes with zero-parameter disposal methods and for the supported FileHandle, timer, immediate, child-process, and readline handles. Array for (using ... of ...) loops compile. Top-level and switch-clause declarations, disposal through an engine-held package value, and the DisposableStack constructors remain fenced; a caught SuppressedError exposes its Node-compatible name and message, while its error and suppressed payload properties remain outside the static catch surface.
  • Typed async generator functions and methods compile with lazy execution, await, direct yield, queued .next()/.return()/.throw() requests, and for await. for await also consumes Node Readable streams, Web ReadableStream values, and statically represented class iterators whose zero-parameter next() returns a promise of a { value, done? } record; abrupt completion runs the iterator's cleanup, including literal destroyOnReturn and preventCancel options. Async yield* delegation, stored Web/Node iterator handles, and other structural async-iterator objects remain fenced. The node:timers/promises setInterval(delay, value) async iterator compiles, while its AbortSignal options remain fenced.
  • Generics monomorphize when the target resolves statically. The remaining edges include generic functions declared inside another function, generic class expressions, generic classes whose base depends on their own type parameters, and generic methods that would require dynamic dispatch.
  • Generic function values must be pinned at use to a concrete signature and come from a never-reassigned binding; unpinned or rebound values remain fenced. Immutable aliases of overloaded program functions retain per-call overload resolution and function identity. Typed rest-parameter functions are first-class values: indirect calls pack their surplus arguments into the function's typed array slot. Lowered node:path functions, including optional basename and variadic join/resolve, are first-class static values across the bare, POSIX, and win32 modules; the exact zero-argument node:os functions and querystring.escape/unescape are first-class too. The fixed-signature filesystem functions existsSync, unlinkSync, chmodSync, chownSync, renameSync, closeSync, and the node:fs/promises unlink, chmod, and rename functions are first-class as well. Filesystem APIs with behavior-bearing trailing arguments—including writers, open, directory creation/removal, metadata readers, and directory readers—remain call-only so function-width adaptation cannot discard modes, flags, encodings, or options. Other immutable aliases of table-backed Node builtins can be called directly, while escaping them remains fenced; util.promisify has compile-time projections for child_process.execFile and utf8 fs.readFile.
  • Spread arguments compile when their arity is static: non-empty fixed tuples flatten into fixed signatures with left-to-right, evaluate-once semantics, while arrays, Sets, and statically represented class iterables spread into typed rest parameters. Runtime-length spreads into fixed signatures remain on the checked-dynamic or island paths in JavaScript and otherwise receive a compile-time diagnostic.
  • Literal import() of compiled ESM/TypeScript modules and supported Node builtins compiles without the dynamic engine. Evaluation remains lazy and microtask-delayed, top-level await is honored, repeated imports share one namespace identity, exported mutable bindings stay live, and Object.keys on compiled-module namespaces uses Node's sorted key order. Computed specifiers, import attributes, CommonJS namespace synthesis, and shipped-JavaScript package imports remain explicitly fenced or require --dynamic.

Types and shapes

  • Arbitrary-precision bigint values compile statically across literals, arithmetic and bitwise operators, comparisons, conversions, arrays, records, classes, closures, unions, promises, Buffer 64-bit reads/writes, and DataView 64-bit access. Native unknown and JavaScript any storage preserve BigInts exactly, including checked Map keys and Set elements. JSON serialization throws Node’s BigInt TypeError unless a replacer converts the value. BigInt64Array/BigUint64Array, BigInt filesystem stats, other statically typed BigInt collection keys, and crossing a native BigInt into the dynamic island remain unsupported.

  • Record shapes are exact structs. Passing {a, b} where {a} is expected is SC2002. Where the compiler does accept a strict field-subset flow, it copies the record — see divergences below.

  • Union edges: same-ABI methods on class unions and map/forEach on array-arm unions dispatch by runtime arm, while shared or joinable field reads compile too. Remaining edges include operations with no common per-arm ABI (such as reading u.length on string | string[] — narrow first), union-into-union widening outside the re-tag and width rules, and function arms beside data arms. (Printing a whole union is fine: console.log(u) dispatches per arm.)

  • Watch for tuple inference: Promise.all([work(1), work(2)]) infers a tuple type, and tuple edges (like .join on a tuple) are fenced. Type the array first:

const jobs: Promise<number>[] = [work(1), work(2), work(3)];
const results = await Promise.all(jobs); // number[] — compiles
  • undefined and null compile both as standalone values and as union arms. Optional record and class fields compile, as do optional/default/rest parameters.
  • Numeric static fields support prefix/postfix ++ and -- through the declaring class or an immutable alias. Updates through a subclass name remain unsupported because JavaScript creates a separate property on that subclass.
  • Class expressions inside functions preserve fresh constructor identity and shared captured variables across constructors, methods, accessors, and arrow fields, including local inheritance when the base resolves to a compiled class. Static members, decorators, private brands, generic members, explicitly generic factories returning local constructors, and references through the class's internal name remain unsupported. Constructors can pass through unknown while preserving identity, typeof, name, and length, and an exact typed cast restores construction; construction or static-member access directly through unknown remains unsupported.
  • Runtime-length argument spreads compile for fixed parameters and rest parameters whose types support checked unknown conversion. Arguments evaluate in source order before defaults run, and surplus arguments still evaluate. Calls without any native parameter slot and parameter types outside that conversion domain remain unsupported.
  • Module-level JavaScript classes can extend a compiled ordinary constructor function. Native super calls preserve receiver and field initialization order and capture the base prototype at class evaluation. Constructors that return replacement objects, spread super arguments, and reflective static inheritance remain unsupported.
  • Symbol-keyed instance fields compile when the key is a stable module-level Symbol() or Symbol.for() binding with a literal description or a constant string binding. Equal registry keys share a field across modules. JavaScript fields without an initializer start as undefined; TypeScript fields require an initializer or an unconditional constructor assignment. Reassigned keys, runtime-computed descriptions, distinct symbols with the same description in one class, and copying symbol fields with rest/spread or Object.assign remain unsupported. Represented symbol fields and methods can be accessed through checked values, including inherited members.

Standard library

  • TextEncoder and TextDecoder instances compile as native values in fields, arguments, return values, arrays, and closures. encode(string) and encode() produce UTF-8 bytes; decode(Uint8Array) and decode() support recognized literal WHATWG labels with default options. Runtime labels, constructor options, streaming, encodeInto, codec property getters, inspection, JSON/dynamic conversion, and module-scope codec declarations using var or lacking an initializer remain fenced. Use let or const with an initializer for stored codecs.

  • EventEmitter uses a fixed argument tuple for literal event names. Computed string names compile when a constant prefix or suffix proves they cannot name error, the meta events, or stream-internal events; those calls pass exact-arity checked-dynamic arguments. Unrestricted names, symbols, computed names that may reach an internal event, and typed listeners()/rawListeners() results for names that may have computed registrations remain fenced.

  • CommonJS module metadata (module.id, filename, path, paths, loaded, isPreloading, parent, children, require.main, and read-only require.cache lookup/enumeration) compiles natively. A compiled binary has a fixed module graph, so cache deletion/reloading, metadata writes, module.paths mutation, and require.extensions remain explicit refusals.

  • child_process.fork(modulePath, args?, options?) compiles when modulePath resolves at build time from new URL("./worker.ts", import.meta.url) or fileURLToPath(new URL("./worker.ts", import.meta.url)), including a never-reassigned const alias and statically resolvable template parts. The worker and its imports are embedded in the executable; the native binary re-executes itself and starts only that worker module. Arguments begin at process.argv[2] in the worker, matching Node's positions under the documented process-shape divergence below. The default inherited stdio and an inline options object with cwd, env, silent, windowsHide, or [stdin, stdout, stderr, "ipc"] compile. execArgv is accepted but has no native effect because no Node executable or source loader starts at runtime. Parent ChildProcess and child process channels support send(message, callback?), connected, disconnect(), and on/once for "message" and "disconnect"; message listeners may return void or Promise<void>. Messages use Node's default newline-delimited JSON serialization, so their static types must be JSON-stringifiable or unknown backed by the checked JSON tree. send() returns the native channel's backpressure state, its callback runs later with null or an Error, and disconnect() changes connected synchronously before either side's later "disconnect" event. Runtime-valued worker paths, custom execPath, advanced serialization, transferred sockets/servers, uid/gid, timeout/signal options, and other stdio arrays remain explicit refusals.

  • The type checker sees the full standard library; only the supported surface compiles. Reaching declared-but-unlowered surface is SC2020 with the supported alternatives in the hint — e.g. parts of the regex API (re.exec), Symbol, globalThis as a value in static builds, and array/Map/Set methods beyond the lowered sets.

  • Date values support zero-argument construction, one number/string argument, storage and passing, getTime/valueOf, toISOString, the local and UTC calendar getters, and getTimezoneOffset. Date.parse(dateString) accepts one string using the same bounded parser as new Date(dateString).getTime(). JavaScript Date values also preserve identity through checked storage, accept copying from another Date, and support JSON serialization. Date-armed unions, the year/month field constructor, setters, identity comparisons on the statically typed TypeScript Date representation, throwing Date values, and locale/string formatters remain fenced; the supported string grammar is described below.

  • URL supports absolute inputs and relative inputs with runtime string or URL bases, including optional bases, and read-only protocol, origin, username, password, pathname, href, host, hostname, port, search, and hash getters. searchParams remains a live view. Setters remain fenced. Non-ASCII and percent-escaped hosts are rejected; opaque paths retain their input bytes without the WHATWG C0 encoding pass.

  • fs.openSync accepts inline bitwise OR expressions of fs.constants.O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_NOFOLLOW, O_NONBLOCK, O_TRUNC, and O_APPEND, with an optional creation mode. Computed numeric flags remain fenced because their bit values differ by target. fstatSync, fchmodSync, fsyncSync, and linkSync compile at direct call sites. On Windows, a numeric open with O_NOFOLLOW and fchmodSync throw ENOSYS because the CRT cannot enforce those contracts; programs requiring them should use a POSIX target.

  • Numeric process.exitCode = value writes in statement position set the implicit exit status; process.exit() with no argument uses that status. Reading or resetting process.exitCode, and assignments used as values, remain fenced.

  • process.getBuiltinModule() exposes native subsets of path, path/posix, and path/win32, native os functions, and main-thread worker_threads metadata. Module references and stored functions retain identity. Other modules and exports, including Worker, throw SC2020. process.versions is a shared dictionary containing node and openssl; other component versions are absent unless defined by the program.

  • Fixed-length ArrayBuffer values support shared typed-array and DataView storage. Resizing and transferring buffers remain unsupported. Numeric typed arrays and Buffer values preserve their element kind and shared storage when passed through unknown, including typed arrays 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. Float16 and BigInt typed arrays remain unsupported.

  • Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection and replacing compiled accessors remain unsupported. Prototype storage on local, generic, mixin, and runtime-provided classes remains unsupported.

  • Intl.Segmenter supports default Unicode grapheme segmentation, segment iteration, and containing(). Locale negotiation, word and sentence segmentation, resolvedOptions(), and detached method values remain unsupported.

  • Stored globalThis references support identity and probes for absent host capabilities. Most builtin members still require direct global access; reading them through a stored global object can raise SC2020.

  • Map keys and Set elements support numbers and strings by value, and records, class instances, arrays, symbols, and server handles by identity. Unions consisting only of these identity types also compile. Collections with unknown slots, including JavaScript's unannotated new Map() and new Set(), use native checked values: supported primitives compare by value and supported object references retain their identity. Typed nested collections other than Map<unknown, unknown> and Set<unknown>, and promises whose payload requires an adapter cannot cross into those checked slots; Promise<unknown> retains its original identity. Other statically typed key forms, mixed scalar/reference unions, nullable key unions, and key conversions that copy an existing reference remain fenced.

  • Static zlib.deflateSync, deflateRawSync, and gzipSync accept default options or an inline { level: n } with an integer literal from -1 through 9. Other compression options and runtime-valued levels remain fenced.

  • Synchronous for...of over checked iterables follows their live iterator protocol, including compiled class iterator methods and cleanup on abrupt completion. Checked, mapper-less Array.from also consumes those iterator methods and supports plain array-like objects. Sparse checked arrays, general borrowed iterator factories, and native Map/Set iterator objects remain unsupported; direct statically typed class loops retain their existing restriction on iterator return and throw methods.

  • Set<unknown> preserves its backing collection through native checked storage. Checked calls support add, has, delete, clear, size, and forEach, including mutations during callbacks. Detached method values and iterator objects remain unsupported.

  • Regular expressions can pass through native checked storage, preserving identity and exposing source, flags, flag properties, and test. Runtime constructors accept checked strings and native regex copies; string replacement accepts checked regex search values with string templates. Stateful test with g or y, lastIndex, detached methods, and exec through an untyped receiver remain unsupported.

  • Object.freeze and Object.isFrozen support plain checked dictionaries and primitives. Freezing is shallow and preserves accessors. Freezing aliased typed records, arrays, proxies, and other native objects remains unsupported.

The any/unknown boundary

  • any without --dynamic is generally a compile error (SC2011) — use unknown and a checked cast, or opt into the engine. Native Map and Set type arguments may use any; their slots use the same checked values as unknown without embedding an engine.
  • any and unknown ride locals, parameters, and returns; record and tuple fields hold unknown. JavaScript instance fields can also hold native checked values without a JavaScript engine: bare fields start as undefined, and redeclaring a field with the same checked-value type resets it in initialization order. Explicit TypeScript any/unknown class fields, static checked-value fields, statically typed array elements, and union arms remain unsupported in static builds. Program class instances can pass through unknown and recover at their exact static class type or a base class without losing identity, including classes with internal collections. Dynamic property inspection of a class whose own fields cannot cross the checked-value boundary throws a catchable TypeError. Supported native handles also retain identity. JavaScript argument and return conversions preserve native array references; other JSON-shaped record and array conversions retain the copy boundary described below.
  • Operations on unknown beyond the supported surface (truthiness, typeof narrowing, property access, +, switch, throw) need a checked cast first.

Three.js

With --npm-static=three, published three.js modules support CPU math, geometry, scene graphs, materials, and a subset of raycasting in native and WASI builds without a JavaScript engine. Raycasting covers mesh intersections, sorted results, recursive traversal, layers, distance limits, perspective and orthographic camera rays, lines, and points. Mesh attribute interpolation can still fail when three.js reads components beyond the end of a vertex attribute; numeric fields cannot retain the resulting undefined. WebGL rendering, browser DOM integration, and native graphics windows remain unsupported.

Type surface

scriptc typechecks your program in its own type world: the standard es2025 lib plus its own ambient declarations. In enumerated places those declarations are deliberately different from stock lib or @types/node, typed as what actually compiles — JSON.parse returns unknown (not any), and the Promise executor's reject reason is pinned to Error. So "programs that typecheck" means scriptc's type world, and a program clean under its own tsc can still be redirected here — but never with a bare type error it can't reproduce: the tightened declarations get a second-chance preflight (a program clean in the project's own type world passes, and the affected sites meet per-site diagnostics with rewrite hints instead), and where the shipped fallback declarations lack a lowering for a stock member (console.table, console.time, ...), the member is declared anyway so the call lands on SC2020 naming the supported alternative. console.log/info/debug/error/warn themselves take unknown arguments and render with Node's console semantics — strings verbatim, everything else through the static util.inspect.

Run scriptc build on a file to get the full, current list for that program — the compiler is always more up to date than this page.

What diverges by design

A static-tier program otherwise produces byte-identical stdout and the same exit code as Node. Every known divergence is deliberate and pinned by the differential test suite; these are the ones with real consequences:

Typed-array reads retain bounds checks. Ordinary arrays track holes separately from present undefined values: missing indexed reads and empty pop()/shift() return undefined, indexed writes can grow the array, and length growth creates holes. Statically typed numeric reads from typed arrays abort for invalid indexes rather than returning undefined. Invalid numeric indexed writes are ignored, preserving the assignment expression's original value.

Runtime traps are not catchable. User throw is fully catchable, and runtime failures Node models as exceptions (JSON parse errors, checked-cast failures, fs errors, regex errors) throw real error objects. Remaining hard traps, including invalid statically typed numeric reads from typed arrays, abort the process.

Untyped Buffer construction accepts native data. Buffer.from accepts untyped strings, byte arrays, arrays, and plain array-like or Buffer JSON objects. Encoding arguments must be supported literals. Custom valueOf hooks on the input object and opaque class or native-handle inputs throw an explicit unsupported-operation error.

JSON callbacks have a native subset. Function replacers and two-argument revivers run statically, including nested replacements, object-property deletion, and thrown exceptions. A replacer that omits the root returns undefined. Stored JSON.stringify functions support numeric and string indentation; the direct-call form retains its literal-indentation restriction. Replacer property lists and reviver source contexts remain unsupported; a reviver that deletes an array element throws because checked-dynamic arrays cannot represent holes. Callback values use the checked-dynamic boundary: typed records and arrays become snapshots, so callback mutations do not update the original typed containers, record fields retain declaration order, and typed-array views retain their backing storage and brands. Buffer.isBuffer preserves the Buffer brand across unknown storage. JavaScript callbacks receive the holder as this; TypeScript callbacks that access a dynamic this remain unsupported.

A lying cast on dynamic data throws instead of corrupting memory — the headline divergence, and the point. JSON.parse(s) as Config with mismatched data throws a catchable error naming the offending path (expected number at $.port, got string) where JS would silently hand you garbage.

Structural width subtyping copies. A record flowing into a strict field-subset shape is copied, not aliased: mutations through the narrower reference are invisible to the original. Typed records and arrays generally cross the checked-value boundary as copies. JavaScript argument and return conversions preserve native array references, and class instances retain identity at their exact class type or a base class. Fresh dense array literals created directly in checked storage preserve their own identity, and supported native handles cross by reference.

Object.keys/values/entries and JSON.stringify report a record's declaration order, not per-object insertion order. Identical to Node whenever objects are built in declaration order (the overwhelmingly common case).

Strings are stored as UTF-8. Invisible through .length and the string methods (which compute UTF-16 semantics) — except relational comparison (<, >), which uses code-point order, and surrogate-splitting operations, which produce U+FFFD.

Date parsing and timezone data are bounded. The one-string constructor and Date.parse(dateString) accept ECMAScript date-only forms, date-times with an explicit Z or numeric offset, and the GMT certificate-validity strings returned by the supported X509 surface; other V8-specific forms and offset-less local date-times return an Invalid Date or NaN, respectively. Local calendar getters use the operating system's timezone database, so historical results can differ when it and Node's timezone data disagree. When a platform's calendar API cannot represent an otherwise-valid extreme year, scriptc queries its zone rule at a calendar-equivalent year in the 400-year Gregorian cycle.

Weak collections have a bounded native key surface. WeakMap and WeakSet support checked native objects and arrays, functions, numeric typed arrays, ArrayBuffers, and other native weak collections. They do not retain their keys. Typed records and arrays that cross into untyped code as copies, class instances, promises, other native handles, and symbol keys remain unsupported. Constructor seeds support arrays; custom iterator protocols and detached method values remain unsupported. Values that refer back to a weak key can keep that key alive until the entry is deleted or the collection is released.

Memory is reference-counted. Acyclic values free deterministically; reference cycles are collected at deterministic collection points, not by a concurrent GC. Native checked objects, arrays, Sets, and captured closures participate in collection. Cycles through opaque native capsules or across the static/island boundary can remain alive.

Foreign native callbacks are asynchronous and not real-time capable. FFI format 5 accepts retained, context-bearing, void callbacks invoked by library-owned threads, but the native trampoline only copies arguments and enqueues work. The closure runs on the script event loop at least one turn later. Value-returning foreign callbacks and direct execution on the library thread are refused: waiting for the loop is deadlock-prone, while scriptc's reference counting and exception state are thread-confined.

Console output uses the process streams. log, info, and debug write to stdout; error and warn write to stderr. Direct calls work through the global console and node:console imports. Stored global console values and detached output methods retain identity and support ordinary arguments; format specifiers with substitution arguments through stored methods, other stored console APIs, custom Console instances, and console mutation remain unsupported.

Stored process streams have a limited native surface. JavaScript can store process.stdin, process.stdout, and process.stderr, replace output write methods, and call saved methods with their stream receiver. Input supports data/end/error listeners, pause/resume, buffered reads, and raw mode on a TTY. Output writes are synchronous and do not model queued backpressure; successful callbacks run on the next-tick queue. Stream piping, encoding changes, arbitrary properties, and other stream events are not supported through these stored values yet. TypeScript output parameters retain the existing write(string) surface.

Process shape — process.argv[0] is "scriptc" and argv[1] is the binary's path (positions line up with Node; argv[2] onward are your args). The uncaught-exception stderr line reads Uncaught <value> instead of Node's stack-trace block (exit code and pre-throw stdout are identical). Runtime errors carry message and Node's code, but not errno/syscall/path.

Comparator call sequences differ in sort and toSorted (scriptc uses a stable bottom-up merge sort, while V8 uses TimSort). Sorted results are byte-identical for consistent comparators. Ordered inputs use a linear number of comparator calls, but merge-buffer movement remains O(n log n) even when every boundary is already ordered. localeCompare compares code units, not ICU collation.

Dynamic-tier limits

  • Host globals: In a TypeScript --dynamic build, const host: any = globalThis or (globalThis as any).TextDecoder accesses the island's global object. Its Node globals use the island shims, even without an embedded npm package; those shims still have their own limits (for example, TextDecoder supports UTF-8 labels only), and optional capabilities such as fetch are installed only when the build links their bridge.
  • The island is quickjs-ng, not V8 — correct, but slower for CPU-bound dependency code. The win is startup, size, memory, deployment shape.
  • The island's Node builtins are shims — reimplementations, reported per-builtin in the coverage report, not the real modules.
  • Island microtask interleaving: static fibers drain first, then the engine's jobs at loop quiescence — a static await racing a package promise resolves in a documented, deterministic order that can differ from Node's interleaving.
  • Top-level await in embedded ESM packages is not supported yet. It does compile in your program's own ESM graph and in npm packages compiled through --npm-static; the remaining limit is package code running inside the --dynamic island.
  • --npm-static and --provenance-sources are experimental — see npm Dependencies for the maturity notes.

Process events

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

Literal uncaughtException and uncaughtExceptionMonitor registrations compile without a JavaScript engine. Handled exceptions resume queued native work; monitors alone do not suppress failure. Error objects and primitive exception values cross the listener boundary, while arbitrary native objects retain the existing exception-value restrictions. Exception capture callbacks and dynamic-island exception handling remain unsupported.

Native addons

Node-API (N-API) and V8 .node addons require Node's addon runtime, which scriptc does not embed in either static or --dynamic builds. Direct createRequire calls to local addons are rejected at compile time. Embedded npm packages receive a catchable ERR_DLOPEN_FAILED when they attempt to load an addon, allowing packages with JavaScript fallbacks to select them.

Use Native FFI to call the underlying native operation through a plain C ABI function linked from an object or static archive. The guide includes a complete replacement for a small Node-API helper.

WASI target limits

The production wasm32-wasi target supports the complete executable language tier through LLVM: async/await, promises, generators, timers and other portable event-loop work, stdin/readline, filesystem callbacks and promises, and the --dynamic island. Portable WASI Preview 1 has no socket, process-spawn, OS-signal, network-interface, or filesystem-notification capabilities, so networking/fetch, child processes, signal APIs, os.networkInterfaces(), and fs.watch are rejected before linking with SC3002. --sanitize and native FFI are also unavailable. Library mode emits an async-free Wasm reactor with named host imports; native runtime localization and thread instances are refused. Each Wasm instance owns independent state. Filesystem access is bounded by the host's preopens; scriptc run exposes the current working directory and /tmp. See Platform Support for build and run details.

Tooling gaps

  • scriptc run does not forward extra CLI arguments to the program — build and invoke the binary directly.
  • Native FFI is a direct, manifest-declared C ABI link surface. Formats 2–5 cover 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 remain unsupported. See Native FFI.
  • Numbers are JS-exact f64 everywhere. Integer inference and ownership analysis — the systems-language performance ceiling — are roadmap, not shipped.

Browser embedding

Wasm reactors need a host-provided WASI Preview 1 adapter. DOM APIs, browser graphics bindings, and three.js WebGLRenderer are not compiled today. Supported three.js CPU operations can execute inside a reactor while an external web application renders their results. Library mode excludes promises, async functions, timers, and the JavaScript island. See WebAssembly Modules for the embedding contract.