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

frontend
npm install @atolljs/core @atolljs/react

2 · Declare shared memory — imported by both threads

counter.memory.tsfrontend
import { defineSharedMemory, field } from '@atolljs/core';

export const counterMemory = defineSharedMemory({
  count: field.number(),
});

3 · Define the worker — methods live here

counter.worker.tsfrontend
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

counter.tsfrontend
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.

App.tsxfrontend
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.