Skip to content
AI Primer
release

scriptc v0.2.8 adds zero-copy object sharing between TypeScript workers

scriptc v0.2.8 adds @scriptc/threads with Worker-compatible APIs that share immutable object graphs by reference. The same programs use cloning when run in Node.js.

3 min read
scriptc v0.2.8 adds zero-copy object sharing between TypeScript workers
scriptc v0.2.8 adds zero-copy object sharing between TypeScript workers

TL;DR

  • @scriptc/threads shares immutable object graphs by reference, as ctatedev announced; it ships in v0.2.8, according to the release post.
  • The same programs run in Node.js with cloning, per the announcement; cramforce says the API preserves ordinary JavaScript semantics.
  • Agent code-mode workloads expose the cost of moving large payloads between threads: thdxr points to database-query results.

Published native graphs are never freed, the sharpest catch in the API. The v0.2.8 release notes also add sharing for deeply frozen plain data sent to workers, without an explicit publish() call.

Worker API

scriptc compiles TypeScript and JavaScript to native executables, using TypeScript's compiler for parsing and type checking. The project's goal is compiled TypeScript competitive with Go and Rust, according to cramforce, who described robust multithreading as a requirement.

The package example keeps the worker plumbing familiar:

  • Worker, isMainThread, and workerData come from node:worker_threads.
  • publish(value) comes from @scriptc/threads, makes the reachable graph immutable, and returns the original value.
  • workerData and postMessage deliver published graphs by reference in native builds; the sender retains access.
  • sharesPublishedGraphs reports true in native builds and false under Node.js.

Publishable values

Publication validates the entire reachable graph before changing anything, according to the v0.2.8 package contract. An unsupported value causes a TypeError and leaves the whole graph unchanged.

  • Accepted: non-symbol primitives, plain objects, arrays, class instances, Map, and Set.
  • Rejected when reached: functions, symbols, accessor properties, Date, RegExp, Error, Promise, typed arrays, and other built-in objects.
  • Collection writes: published Maps and Sets reject set, add, delete, and clear, as applicable.
  • Repeated publication: publish() is idempotent, and publishing an already-published graph is cheap.

Typed arrays sit outside the accepted set, despite being a common vehicle for worker payloads.

Immortal graph lifetime

Native publication walks the graph once using compiler-generated walkers for each static type. Every object it visits becomes immortal: the runtime stops reference counting it and never collects or frees it, according to the native-runtime contract.

That lifetime applies to the whole reachable graph, including objects nested beneath the root. Publishing fresh graphs repeatedly therefore permanently retains each one.

Node cloning

Compatibility has three boundaries in the package README:

  1. Receiver-side freezing. The receiver calls publish() on the received graph. Native delivery makes this a no-op; in Node.js, it freezes the clone so subsequent writes fail.
  2. Class prototypes. Native delivery preserves class instances, methods, and instanceof relationships. Node's structured clone delivers class instances as plain objects.
  3. Object identity. Two messages containing one published graph deliver the same object in scriptc. Node.js creates separate clones.

Tool-result serialization

Large database results moving between agent tool calls can make thread serialization a problem, thdxr argued in a separate code-mode discussion. His team built an interpreter after avoiding a QuickJS implementation for three reasons:

  • WebAssembly complexity.
  • Required worker threads.
  • Serialization of values back and forth.

Compiler write guards

scriptc enforces published immutability with compiler-emitted store guards consisting of one load and branch, according to the implementation notes. The contract records two additional behavioral differences from Node.js:

  • Private fields: published objects' #fields cannot be written in scriptc. Node.js permits private-field writes on frozen objects.
  • Evaluation order: writes to union-typed class fields and Map, Set, or array mutator calls check the guard before evaluating arguments. A rejected mutation can therefore skip side effects that Node.js would execute before throwing.

Further reading

Discussion across the web

Where this story is being discussed, in original context.

On X· 2 threads
TL;DR1 post
Publishable values1 post
Share on X