Skip to content

plasma

One schema, one set of mutators, running identically on the browser and on Cloudflare Workers. Sync, live queries, files, and CRDT convergence handled by the engine.

plasma is a TypeScript sync engine you drop into a Cloudflare Workers + D1 stack. You define your schema and mutation functions once; they run:

  • on the browser, optimistically, against IndexedDB — for instant reactive UI
  • on the Worker, canonically, against D1 (or Postgres via Hyperdrive) — for the source of truth

plasma owns the /sync/* endpoints, the change log, the pull cookie, the outbox, the rebase, the WebSocket poke, the R2 blob uploads for file() columns, and everything else in between.

Isomorphic DSL

Types flow end-to-end. db.select().from(todos).where(eq(todos.done, 0)) means the same thing on the browser and on the Worker, and returns the same row shape.

Optimistic + convergent

Mutations apply locally the moment you call them. The sync loop pushes them to the server, rebases on top of concurrent changes, and — with CRDT columns — converges without conflict resolution boilerplate.

Files, encryption, CRDT

Declare a file() column and R2 handles the bytes. Declare .encrypted() and the client wraps at-rest. Declare crdtOrSet<string>() and concurrent tabs merge additions and removals losslessly.

Cloudflare-native

Ships with SyncCoordinator (Durable Object with hibernation-aware WebSocket fan-out), SequencerDO (per-region monotonic id source), Miniflare + workerd test harness, and R2 blob storage adapter.

plasma is designed for the shape of app where each user has a manageable set of rows they own or share with a small group: todos, notes, chats, kanbans, drafts, contacts, small collaborative documents.

It is not designed for public feeds (Twitter timeline shape) or analytics-heavy workloads. The change log grows with writes, not with reads, so a heavy-read / light-write app is fine; a shape where a single row is fanned out to thousands of subscribers is not.