React — quickstart
A minimal counter in React: one shared field, one worker method, one component. Steps 2–4 are the same for every framework — only the last step differs.
1 · Install
npm install @atolljs/core @atolljs/react2 · Declare shared memory — imported by both threads
import { defineSharedMemory, field } from '@atolljs/core';
export const counterMemory = defineSharedMemory({
count: field.number(),
});3 · Define the worker — methods live here
import { defineWorker } from '@atolljs/core';
import { counterMemory } from './counter.memory';
// defineWorker wires the message loop and registers every method.
// Plain functions type the client from their signature.
export const counterWorker = defineWorker({
sharedMemory: counterMemory,
methods: {
increment(delta: number) {
const next = counterMemory.count.read() + delta;
counterMemory.count.write(next); // write in place — no postMessage
return next;
},
},
});
export type CounterWorker = typeof counterWorker;4 · Connect from the main thread — type only
import { connectWorker } from '@atolljs/core';
import { counterMemory } from './counter.memory';
import type { CounterWorker } from './counter.worker'; // no worker code in this bundle
export const counter = connectWorker<CounterWorker>({
sharedMemory: counterMemory,
// Inline new Worker(new URL(..., import.meta.url)) — every bundler's
// worker transform can see the entry point this way.
worker: () => new Worker(new URL('./counter.worker.ts', import.meta.url), { type: 'module' }),
poolSize: 'auto', // navigator.hardwareConcurrency, or pass a number
});
// counter.increment(1) → Promise<number>. The pool spawns on first call
// (SSR-safe to import); counter.terminate() tears it down.5 · Bind it in React
Hooks subscribe via useSyncExternalStore — field reads return undefined until the contract binds on the client.
import { useSharedValue, useTask } from '@atolljs/react';
import { counterMemory } from './counter.memory';
import { counter } from './counter';
export function App() {
const count = useSharedValue(counterMemory, 'count');
const increment = useTask(counter.increment); // client method → latest-wins task
return (
<button onClick={() => increment.run(1)}>
count: {count ?? '…'}
</button>
);
}With shared memory: cross-origin isolation
SharedArrayBuffer only exists when the page is cross-origin isolated — see Hosting & headers for the COOP/COEP setup. Leave sharedMemory out of both defineWorker and connectWorker and you have a typed, pooled worker RPC that needs neither header.