@atolljs/core ships a SharedWorker-based runtime alongside WorkerPool. Its purpose is shared state, not messaging: one worker owns one SharedArrayBuffer, and every page, tab, and iframe that connects binds its contract to that same buffer — writes in one are readable and observable in all.
import { connectSharedWorker } from'@atolljs/core';
import { incidentsMemory, InitIncidents, QueryIncidents } from'./contracts';
const worker = awaitconnectSharedWorker({
// Inline new SharedWorker(new URL(...)) so your bundler emits the chunk.createWorker: () => newSharedWorker(
newURL('./incidents.sharedWorker.ts', import.meta.url),
{ type: 'module' }
),
sharedMemory: incidentsMemory,
tasks: { initIncidents: InitIncidents, queryIncidents: QueryIncidents },
});
await worker.initIncidents(); // first-class task methodconst page = await worker.queryIncidents({ offset: 0, limit: 50 });
worker.sharedMemory.metrics.read(); // shared across ALL clients
worker.disconnect(); // close this port only
Config
Option
Type
Notes
workerUrl
URL
Worker entry URL — one of the three connect options is required
createWorker
() => SharedWorker
Preferred — bundlers only emit the chunk when new SharedWorker(new URL(...)) appears inline
port
MessagePort
Pre-opened port — tests or custom plumbing
sharedMemory
SharedMemory
Contract; bound to the worker's buffer on connect
memory
MemoryConfig
Buffer sizing hint — first connected client wins; later clients share that buffer
tasks
TaskMap
Named contracts → first-class methods
connectTimeoutMs
number
Handshake timeout, default 10s
Client surface
Member
Behavior
runTask(contract, ...args)
Compute dispatch; resolves with the validated result for this client
clientIndex
Connection order reported by the host
sharedMemory
The bound contract — observe() and framework bindings work unchanged
disconnect()
Closes this port; the worker and buffer live on for other clients
State propagates through memory, not messages
Cross-context sync needs no messaging at all: every write bumps a shared version counter (Atomics.add + notify), and each client's observe()/watch() subscribers wait on that counter (Atomics.waitAsync). A write in one tab resolves every other tab's observer directly — the port only carries the connect handshake and task dispatch.
// tab A writes
memory.metrics.write((m) => ({ ...m, critical: m.critical + 1 }));
// tab B observes — fires on tab A's write, no postMessage involvedobserve(memory, 'metrics').subscribe(render);
Pool or shared worker?
Use…
When
WorkerPool
CPU parallelism in one page; per-page private memory
connectSharedWorker
One shared dataset across tabs/iframes — state syncs through memory
Caveats
Safari dropped SharedWorker. Feature-detect and fall back to a per-page WorkerPool:typeof SharedWorker === 'undefined'.
Same cross-origin isolation rules — it's still SharedArrayBuffer; see Hosting & headers. Iframed clients need the full embedding chain there.
One worker, serialized work — a SharedWorker is a single thread. State sync costs nothing (it's just shared memory), but task execution is one-at-a-time; use WorkerPool for CPU parallelism.
No terminate() — the browser owns the worker lifecycle; clients only disconnect.