JavaScript has had workers for fifteen years, and the way most apps use them hasn't changed: pick the expensive function, post it some data, await the result. That works, until the work isn't a function call. It's a million-row scan. It's a UI tree re-rendering every frame. It's state that both threads need live, not cloned-and-stale.
AtollJS is built around four goals, each one a layer: Share state, don't serialize it
postMessage clones everything, fine for results, wrong for state a worker updates continuously. The raw alternative, SharedArrayBuffer, is fast and gives you nothing to keep both sides honest.
So the layout is a type. defineSharedMemory takes a spec of fixed-width fields and compiles it to a deterministic byte layout, one declaration, imported by main and worker alike:
The schema is the layout: reef mints validators the compiler reads a byte width from, so the two sides can't disagree - there's only one source of offsets. And it costs nothing you didn't ask for: the schema engine is vendored into reef, so no zod dependency ships with the SDK.
Deep dive: Shared Memory Is a Contract, Not a Buffer. Make worker calls feel like method calls
A worker is an RPC surface - it should look like one. defineWorker declares the method list inside the worker; connectWorker gives the main thread a typed proxy over a lazily-spawned pool:
import type matters: worker code never enters the app bundle. Under the hood the pool handles what bare workers don't - queueing, taskTimeout, cancellation, crash respawn. A pool call always settles.
Deep dive: Worker Pools That Fail Gracefully. Let results flow back as reactivity
The payoff of shared memory is live reads - main doesn't await a return value, it watches the field the worker is writing:
observe and defineTask are the primitives; framework bindings adapt them - useSharedValue/useTask in React, equivalents in Vue, Solid, Svelte, Angular, Next.js. Your component subscribes; the worker writes; the diff flows through the version counter, not a message.
Push the idea to its limit and the framework tree can live in the worker too. @atolljs/islands mounts a registered app inside a worker; it renders against a proxy document and streams serialized DOM ops back to a driver that replays them. A React island, end to end:
Render, diff, and state all run off the main thread - what's left behind is a thin shell replaying ops. The facade wraps the same mountIsland driver call - the contract's dynamic import is the split point, so a heavy island's chunk only loads when it mounts - and the driver takes a bare element on framework-free shells. Per-framework packages (react-island, vue-island, …) keep each worker's renderer to the framework it actually uses.
Everything above is the browser story; the pool doesn't change when the runtime does. On node:worker_threads, @atolljs/nestjs puts the pool behind dependency injection: AtollModule.registerPool makes it an injectable provider, and @AtollService makes a service class the interop surface - inject it anywhere and every method dispatches:
Each worker boots its own Nest application context, so the method body runs with real DI on both sides - the class file is the contract, the same discipline as every browser layer. Node needs no COOP/COEP for SharedArrayBuffer, so shared memory comes along free; and a route subtree can even be housed inside workers outright, proxied at the framework boundary.
Every layer leans on the same discipline: declare once, in types both threads share. The memory contract is a schema; the worker surface is typeof your worker module; islands scope every mount to an app@N instance. Nothing is duplicated across the boundary - so nothing drifts.
And the footprint follows the same rule. Dependencies stay external and opt-in - no shared-memory fields means no schema code in your bundle at all; islands need no SharedArrayBuffer and no special headers, so the graceful-degradation story is real. The whole stack is what you use of it, byte for byte.
