← Back to archive

Repo of the Day

coactionjs/coaction: Effortless multithreading for high-performance JavaScript web apps.

Published: Aug 16, 2026

Open repository ↗

Effortless multithreading for high-performance JavaScript web apps. - coactionjs/coaction

Summary

Coaction is a TypeScript state runtime that combines immutable snapshots with fine-grained path reactivity and cached derived values. It treats writes as explicit transactions, so the same set() boundary can feed reactivity, history, Worker authority, or local-first synchronization without rewriting write semantics.

What it is useful for

The README is candid that most apps do not need it: for a small single-tab store, Zustand or Jotai ships less code with a larger ecosystem. Coaction earns its dependency line when several of these come together — immutable application snapshots with automatic deep render tracking, derived state without manual memoization, mutable-looking writes on an immutable result, undo/redo or persistence from the same transitions, Worker or SharedWorker authority, or local-first remote sync. Documented trade-offs include a roughly 10.7 KiB gzip footprint for the core consumer and about half the throughput of Zustand in a maintained 1,000-item update-then-read benchmark.

How engineers can use it

Install with npm install coaction for the framework-agnostic runtime, or npm install coaction @coaction/react for React. A counter looks like this:

import { create, observer } from '@coaction/react';

const useCounter = create((set) => ({
  count: 0,
  step: 1,
  get doubled() { return this.count * 2; },
  increment() {
    set(() => { this.count += this.step; });
  }
}));

const Counter = observer(() => {
  const store = useCounter();
  return <button onClick={store.increment}>{store.count} × 2 = {store.doubled}</button>;
});

Inside observer(), only the paths a render reads become dependencies, so a component reading user.profile.name is not invalidated by a change to profile.age. Plain getters are cached derived values; subtotal recomputes only when items change, and total recomputes only when subtotal or taxRate change. For off-main-thread authority, the same store source is created with @coaction/react/shared and a new Worker(...) option, making the worker the mutation authority while the UI holds a local mirror. For local-first sync, @coaction/sync adds a pull/push middleware with separate backends for Supabase, Firestore, IndexedDB, and TanStack Query.

Coaction ships adapters for Vue, Angular, Svelte, Solid, MobX, Redux Toolkit, Pinia, Jotai, Valtio, and XState. Imports must come from the same entry used to create the store; switch to the /shared entries only when state crosses a Worker boundary. The 4.0 migration guide and threading contract details live in the project documentation.