A reactive store, quietly backed by a CRDT.
Write it like in-memory state. Get real-time collaboration, offline persistence, and undo/redo — opt-in, behind the same API.
StoreValue<T> is a typed handle that behaves like a normal in-memory store — until you give it a
document. Then the same handle is a Yjs CRDT: reads stay synchronous, writes stay a
method call, and concurrent edits merge. Yjs stays hidden, even across the network.
import { StoreValue } from "@super-store/store"
// Fully local — identical to an in-memory store:
const counter = new StoreValue(0)
counter.subscribe(() => console.log(counter.getSnapshot()))
counter.set(1) // logs 1
counter.set(1) // no-op
// Make it collaborative — same API, opt-in. Relay bytes over any transport, no yjs import:
counter.onUpdate((update, { local }) => { if (local) bus.send(update) })
bus.on("message", (update) => counter.applyUpdate(update))| Package | What it is |
|---|---|
@super-store/store |
The StoreValue primitive. One runtime dep (yjs). No React. |
@super-store/react |
useStore / useStoreSelector — tear-free hooks over useSyncExternalStore. |
pnpm add @super-store/store # yjs comes with it
pnpm add @super-store/react react # for the hooks- One API, two modes. A
StoreValueis a plain value until you bind a doc; then it's a CRDT. Binding is lazy and cascades from the root. - Real merge. Backed by Yjs — concurrent edits converge per field, per array slot, per set member.
- Collaboration without importing Yjs.
encodeState/applyUpdate/onUpdatemove CRDT bytes over any transport you own; a{ local }flag breaks echoes. The wire is a CRDT; your code never seesY.*. - Undo that respects peers. Opt-in per root; only your own edits revert; a remote merge is never undone.
- Tear-free React. A cached, reference-stable snapshot; a remote merge re-renders like a local
set().
See the docs for guides and the full API reference.
examples/synced-canvas — a collaborative canvas: shapes synced live across
tabs, a server that persists and co-writes, undo/redo, and an origin-tagged debug panel. State is a
StoreValue; the wire is super-line. No yjs import in the
app.
pnpm --filter @super-store/example-synced-canvas dev # open http://localhost:5173 in two windowssuper-store ships an agent skill so your coding agent writes correct code:
npx degit mertdogar/super-store/skills/super-store .claude/skills/super-storeOther agents: see skills/super-store/AGENTS.md and the
guide.
pnpm install
pnpm build # tsup -> dist (esm + cjs + d.ts) for each package
pnpm test # vitest across packages
pnpm typecheck # single root tsc --noEmit
pnpm lint # oxlint
pnpm docs:dev # local docs site (typedoc + vitepress)Monorepo layout: packages/* (the libraries), examples/* (runnable demos), docs (the VitePress site).
MIT © Mert