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, andSyntaxErroraccept{ cause }options, including through inherited constructors. Reading, assigning, deleting, and testing"cause" in errorpreserve 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 ofcauseremain unsupported.- Native class fields, inherited accessors, and added properties retain their identity across untyped values. Reflection such as
JSON.stringifyandObject.keysstill 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, andbindwork 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 usethis, 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 thex == null/x != nullnullish idiom. Object-to-primitive comparisons remain fenced because customvalueOf/toStringmethods can execute arbitrary code; convert the object explicitly first. - Block- and function-scoped
usingandawait usingcompile for statically represented classes with zero-parameter disposal methods and for the supported FileHandle, timer, immediate, child-process, and readline handles. Arrayfor (using ... of ...)loops compile. Top-level and switch-clause declarations, disposal through an engine-held package value, and theDisposableStackconstructors remain fenced; a caughtSuppressedErrorexposes its Node-compatiblenameandmessage, while itserrorandsuppressedpayload properties remain outside the static catch surface. - Typed async generator functions and methods compile with lazy execution,
await, directyield, queued.next()/.return()/.throw()requests, andfor await.for awaitalso consumes NodeReadablestreams, WebReadableStreamvalues, and statically represented class iterators whose zero-parameternext()returns a promise of a{ value, done? }record; abrupt completion runs the iterator's cleanup, including literaldestroyOnReturnandpreventCanceloptions. Asyncyield*delegation, stored Web/Node iterator handles, and other structural async-iterator objects remain fenced. Thenode:timers/promisessetInterval(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:pathfunctions, including optionalbasenameand variadicjoin/resolve, are first-class static values across the bare, POSIX, and win32 modules; the exact zero-argumentnode:osfunctions andquerystring.escape/unescapeare first-class too. The fixed-signature filesystem functionsexistsSync,unlinkSync,chmodSync,chownSync,renameSync,closeSync, and thenode:fs/promisesunlink,chmod, andrenamefunctions 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.promisifyhas compile-time projections forchild_process.execFileand utf8fs.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-levelawaitis honored, repeated imports share one namespace identity, exported mutable bindings stay live, andObject.keyson 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
bigintvalues 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. Nativeunknownand JavaScriptanystorage preserve BigInts exactly, including checked Map keys and Set elements. JSON serialization throws Node’s BigIntTypeErrorunless 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/forEachon 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 readingu.lengthonstring | 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.joinon 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[] — compilesundefinedandnullcompile 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
unknownwhile preserving identity,typeof,name, andlength, and an exact typed cast restores construction; construction or static-member access directly throughunknownremains unsupported. - Runtime-length argument spreads compile for fixed parameters and rest parameters whose types support checked
unknownconversion. 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
supercalls preserve receiver and field initialization order and capture the base prototype at class evaluation. Constructors that return replacement objects, spreadsuperarguments, and reflective static inheritance remain unsupported. - Symbol-keyed instance fields compile when the key is a stable module-level
Symbol()orSymbol.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 asundefined; 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 orObject.assignremain unsupported. Represented symbol fields and methods can be accessed through checked values, including inherited members.
Standard library
-
TextEncoderandTextDecoderinstances compile as native values in fields, arguments, return values, arrays, and closures.encode(string)andencode()produce UTF-8 bytes;decode(Uint8Array)anddecode()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 usingvaror lacking an initializer remain fenced. Useletorconstwith an initializer for stored codecs. -
EventEmitteruses a fixed argument tuple for literal event names. Computed string names compile when a constant prefix or suffix proves they cannot nameerror, 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 typedlisteners()/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-onlyrequire.cachelookup/enumeration) compiles natively. A compiled binary has a fixed module graph, so cache deletion/reloading, metadata writes,module.pathsmutation, andrequire.extensionsremain explicit refusals. -
child_process.fork(modulePath, args?, options?)compiles whenmodulePathresolves at build time fromnew URL("./worker.ts", import.meta.url)orfileURLToPath(new URL("./worker.ts", import.meta.url)), including a never-reassignedconstalias 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 atprocess.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 withcwd,env,silent,windowsHide, or[stdin, stdout, stderr, "ipc"]compile.execArgvis accepted but has no native effect because no Node executable or source loader starts at runtime. ParentChildProcessand childprocesschannels supportsend(message, callback?),connected,disconnect(), andon/oncefor"message"and"disconnect"; message listeners may returnvoidorPromise<void>. Messages use Node's default newline-delimited JSON serialization, so their static types must be JSON-stringifiable orunknownbacked by the checked JSON tree.send()returns the native channel's backpressure state, its callback runs later withnullor anError, anddisconnect()changesconnectedsynchronously before either side's later"disconnect"event. Runtime-valued worker paths, customexecPath, 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,globalThisas a value in static builds, and array/Map/Set methods beyond the lowered sets. -
Datevalues support zero-argument construction, one number/string argument, storage and passing,getTime/valueOf,toISOString, the local and UTC calendar getters, andgetTimezoneOffset.Date.parse(dateString)accepts one string using the same bounded parser asnew 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, throwingDatevalues, and locale/string formatters remain fenced; the supported string grammar is described below. -
URLsupports absolute inputs and relative inputs with runtime string orURLbases, including optional bases, and read-onlyprotocol,origin,username,password,pathname,href,host,hostname,port,search, andhashgetters.searchParamsremains 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.openSyncaccepts inline bitwise OR expressions offs.constants.O_RDONLY,O_WRONLY,O_RDWR,O_CREAT,O_EXCL,O_NOFOLLOW,O_NONBLOCK,O_TRUNC, andO_APPEND, with an optional creation mode. Computed numeric flags remain fenced because their bit values differ by target.fstatSync,fchmodSync,fsyncSync, andlinkSynccompile at direct call sites. On Windows, a numeric open withO_NOFOLLOWandfchmodSyncthrowENOSYSbecause the CRT cannot enforce those contracts; programs requiring them should use a POSIX target. -
Numeric
process.exitCode = valuewrites in statement position set the implicit exit status;process.exit()with no argument uses that status. Reading or resettingprocess.exitCode, and assignments used as values, remain fenced. -
process.getBuiltinModule()exposes native subsets ofpath,path/posix, andpath/win32, nativeosfunctions, and main-threadworker_threadsmetadata. Module references and stored functions retain identity. Other modules and exports, includingWorker, throwSC2020.process.versionsis a shared dictionary containingnodeandopenssl; other component versions are absent unless defined by the program. -
Fixed-length
ArrayBuffervalues 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 throughunknown, including typed arrays nested in records and arrays. Numeric typed-array constructors can be stored and used withnew, including constructors read from an array'sconstructorproperty. 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.Segmentersupports default Unicode grapheme segmentation, segment iteration, andcontaining(). Locale negotiation, word and sentence segmentation,resolvedOptions(), and detached method values remain unsupported. -
Stored
globalThisreferences support identity and probes for absent host capabilities. Most builtin members still require direct global access; reading them through a stored global object can raiseSC2020. -
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
unknownslots, including JavaScript's unannotatednew Map()andnew Set(), use native checked values: supported primitives compare by value and supported object references retain their identity. Typed nested collections other thanMap<unknown, unknown>andSet<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, andgzipSyncaccept default options or an inline{ level: n }with an integer literal from-1through9. Other compression options and runtime-valued levels remain fenced. -
Synchronous
for...ofover checked iterables follows their live iterator protocol, including compiled class iterator methods and cleanup on abrupt completion. Checked, mapper-lessArray.fromalso 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 iteratorreturnandthrowmethods. -
Set<unknown>preserves its backing collection through native checked storage. Checked calls supportadd,has,delete,clear,size, andforEach, 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, andtest. Runtime constructors accept checked strings and native regex copies; string replacement accepts checked regex search values with string templates. Statefultestwithgory,lastIndex, detached methods, andexecthrough an untyped receiver remain unsupported. -
Object.freezeandObject.isFrozensupport 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
anywithout--dynamicis generally a compile error (SC2011) — useunknownand a checked cast, or opt into the engine. Native Map and Set type arguments may useany; their slots use the same checked values asunknownwithout embedding an engine.anyandunknownride locals, parameters, and returns; record and tuple fields holdunknown. JavaScript instance fields can also hold native checked values without a JavaScript engine: bare fields start asundefined, and redeclaring a field with the same checked-value type resets it in initialization order. Explicit TypeScriptany/unknownclass fields, static checked-value fields, statically typed array elements, and union arms remain unsupported in static builds. Program class instances can pass throughunknownand 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 catchableTypeError. 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
unknownbeyond the supported surface (truthiness,typeofnarrowing, 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
--dynamicbuild,const host: any = globalThisor(globalThis as any).TextDecoderaccesses 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,TextDecodersupports UTF-8 labels only), and optional capabilities such asfetchare 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
awaitracing a package promise resolves in a documented, deterministic order that can differ from Node's interleaving. - Top-level
awaitin 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--dynamicisland. --npm-staticand--provenance-sourcesare 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 rundoes not forward extra CLI arguments to the program —buildand 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.