Blog

Long-form writing on what AtollJS is for and how its pieces fit together. Posts live in docs/blog/ in the repo and are compiled into this site.

Introducing AtollJS

A worker atoll: pools, shared memory, and worker-rendered UI — under one contract

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.

Facades Over Threads

How AtollJS makes real JavaScript multithreading feel like ordinary code

JavaScript has had real threads for fifteen years, and the honest answer to "why isn't everything multithreaded" is that the API makes you earn it. postMessage gives you a pipe and a serialization boundary; the protocol on top — request ids, response matching, error plumbing, progress events — is work nobody assigned you but everybody pays for.

Dependency Injection Across the Boundary

`@AtollService`: a NestJS provider whose methods run in a pool

NestJS is where this question matters most, because Nest's value proposition is that the provider graph is the architecture. If worker code lives outside that graph — reached through a bespoke client with its own error conventions — you've given up the reason you chose the framework.

Shipping SharedArrayBuffer

COOP/COEP, cross-origin isolation, and what works without it

Here's the bug report every shared-memory project eventually gets: the demo works locally, deploys fine, and in production SharedArrayBuffer is undefined and nothing renders. That's crossOriginIsolated — browsers gate SAB behind cross-origin isolation because shared memory is a Spectre-class timing primitive.

Clustering and Persistence

Socket-transfer, sticky sessions, and Redis-backed shared memory

Gateway routing moves compute off the main thread — but every connection still lands there first. For most apps that's fine. For workloads where the per-request cost is the product — hashing, compression, aggregation over shared state — Node 26 added the missing primitive: transferring the accepted socket itself to a worker.

Atoll on the Server

`node:worker_threads`, HTTP offload, and gateway routing

Everything so far assumed a browser. The backend has the same story with higher stakes: an endpoint that aggregates a million records parks the event loop for tens of milliseconds per request — and while it runs, every other request on that process waits. One slow route is a denial of service on your own server.

The Proxy DOM

How worker-rendered UI stays fast: an op stream, not a VDOM diff

The objection writes itself: a worker can't touch the DOM. True — and it's exactly why most off-main-thread rendering experiments end at a <canvas> or a proof-of-concept nobody ships.

Islands That Render in Workers

Real framework components, off the main thread

"Islands" in this industry usually means small interactive widgets in a mostly-static page — a like button here, a date picker there. A fine pattern that's answering the wrong question for CPU-bound UI.

One Pool Per Browser

`SharedWorker` makes shared memory actually shared

Open your app in three tabs and count what you just paid for: three pools, three copies of the same dataset, three workers running the same queries. Every tab pays full price because Worker is per-page — that's the API, not your design.

Reactivity Without Messages

`Atomics` beats `postMessage` fan-out

Getting worker state into a UI conventionally means a subscription fan-out: the worker posts updates, the main thread forwards them to whoever subscribed — a message for every field change, on every client, even when nothing is looking at that field. It's the part of every worker integration everyone builds and nobody enjoys.

Worker Pools That Fail Gracefully

Dispatch, cancellation, timeouts, backpressure, and crash respawn

Picture the failure mode: your worker throws during module init — or OOMs mid-task — and the postMessage you sent it simply never gets an answer. The await hangs forever. Your loading spinner is now a permanent fixture.

Shared Memory Is a Contract, Not a Buffer

Why AtollJS's `SharedArrayBuffer` layer starts with a schema

If you've ever allocated a SharedArrayBuffer and then written Float64Array offsets into a comment so both files agree — you already know the failure mode this post is about.