Skip to content

How Ramose works

This page shows how a write becomes a read, with one picture. It is for anyone who has read a guide and wants to know what happens after they call db.transact.

  • Worker — Cloudflare’s serverless function. Your app’s backend and the Ramose server are Workers.
  • Durable Object — a Worker with exactly one running instance and its own storage (glossary). The writer and the read copies are Durable Objects.
  • R2 — Cloudflare’s object storage, like S3 (glossary). Every version of every database is kept there.
  • D1 — Cloudflare’s SQL database. Ramose never uses it; Reef’s sign-in Worker keeps Better Auth’s accounts in one.
  • Service binding — a Worker calling another Worker directly, no URL. How your Worker reaches the Ramose server (glossary).
How a write becomes a read in Ramose Your app sends a write to the Ramose server. The server checks the token and the rules and hands the write to the writer, one per database. The writer commits it in order and stores every version in object storage. Read copies learn the new version and every open tab is told within about a second, so live queries re-run. your app browser or Worker the Ramose server serves every database the writer one per database object storage (R2) every version kept, nothing overwritten read copies where queries run every open tab live queries re-run 1 2 3 4 5 1 write (HTTPS) · 2 token and rules checked, committed in order · 3 stored; every version kept 4 read copies learn the new version · 5 every open tab told in about a second; live queries re-run
Green is the write path; grey is the fan-out that follows. On a narrow screen, scroll the picture sideways.

The five numbered arrows are the five steps below, in the same order.

Reef’s “drag an issue to Done” is one write — one all-or-nothing group of changes; Ramose calls it a transaction (glossary). Here is its journey:

  1. Your app sends the write over HTTPS to the Ramose server — the one Cloudflare Worker that serves all your databases; Ramose’s code calls it the peer (glossary).
  2. The server verifies the sign-in token, then checks the rules in the policy (the writer checks them again before committing). A viewer moving a card is refused here; nothing else happens.
  3. The server hands the write to the writer — the one thing per database that commits writes, in order (glossary). The writer stores it before answering, the database’s version number t goes up by one (glossary), and your app gets report.t back.
  4. The read copies learn the new version.
  5. Every open tab is told within about a second, and every live query on those pages re-runs and hands the screen new rows (glossary). Nobody subscribed to anything.
  1. A query runs on a read copy — a copy of the data near your users, kept current by the writer (glossary). It never waits on the writer.
  2. The rules filter what comes back: a field you may not read is missing, not an error.
  3. The read happens at a version. Live reads follow the newest one; db.asOf(t) pins an earlier one.
  • One writer per database. All writes to a database go through one place, in order. There are no write conflicts to resolve and no “eventually”.
  • Version numbers are dense and in order. Every write gets the next t; nothing mints, skips or supplies one.
  • A write is stored before you get an answer. If db.transact returns, the write is durable.
  • Reads never block the writer. Queries run on read copies; the writer’s queue does not compete with them.
PieceRuns asYou write it?
your appa browser page, or your own Workeryes
the Ramose serverone Cloudflare Worker, all databasesno — you declare it
the writera Durable Object, one per databaseno — you name it
read copiesDurable Objectsno — you name them
object storagea Cloudflare R2 bucketno — you name it

That table is why the deploy file names three things: a storage bucket where every version is kept, and two Durable Objects — one for the writer, one for the read copy.

examples/reef/src/infra/resources.ts:37-39
const Store = Cloudflare.R2.Bucket("Store");
const Transactor = Cloudflare.DurableObject("TransactorDO", { className: "TransactorDO" });
const Replica = Cloudflare.DurableObject("QueryReplicaDO", { className: "QueryReplicaDO" });

You name them; you never write them. Every version of every database ends up in object storage, and nothing there is rewritten, which is why you can still read yesterday.

  • The writer’s storage fails mid-write. Nothing from that write is kept; the client sees a 503 and retries; version numbers continue with no gap.
  • A read copy falls behind or disconnects. It reconnects on the next request and catches up from the log.
  • The WebSocket connection drops. The client reconnects with backoff; live queries pick up where they left off.

Details, settings and what to look at are in The server.