The typed, realtime database for apps on Cloudflare — describe your data in TypeScript, write it through a typed API, and read it with queries that update themselves in every open tab.
A schema, a live query, and a typed write — that is the whole app:
import * as Ramose from "ramose/db";
import { useLive, useTransact } from "ramose/react";
import * as Schema from "effect/Schema";
const Todo = Ramose.Namespace("todo", {
title: Ramose.Attr(Schema.String),
done: Ramose.Attr(Schema.Boolean),
});
const ramose = Ramose.connect({ url: import.meta.env.VITE_RAMOSE_URL });
const db = ramose.db("todos", Ramose.Catalog({ todo: Todo }));
// A query is a value: declare it once, then run it live.
const todos = Ramose.query(Todo).select({ id: Todo.id, title: Todo.title, done: Todo.done });
const Todos = () => {
const { rows } = useLive(db, todos); // re-runs itself whenever the data changes
const { run } = useTransact();
const toggle = (todo: Ramose.Row<typeof todos>) =>
run(
db.transact(function* (tx) {
yield* tx.add(todo.id, Todo.done, !todo.done);
}),
);
return (
<ul>
{rows?.map((todo) => (
<li key={todo.id} onClick={() => void toggle(todo)}>
{todo.title}
</li>
))}
</ul>
);
};Nothing refetches after that write, and nothing invalidates a cache: useLive
is a query the server keeps up to date, in every tab that is watching it.
- A typed schema. One TypeScript file your app, your rules, and your deploy all import. A wrong write is a red squiggle, not a bad row.
- Live queries.
useLive(db, query)re-runs itself when the data changes. No refetch code, no invalidation, no WebSocket server to write. - Permissions in the database. Who may read or write each field is checked on the server, deny by default — not middleware you remember to add.
- A database per customer.
ramose.db("acme", schema)is a function call, not a provisioning step. - Nothing overwritten. Every version is kept; read the database as it was at any earlier point in time.
- Your Cloudflare account. One Worker, Durable Objects, and R2, deployed with Alchemy. No Ramose bill, no dashboard, no managed service.
bun add ramose react react-domOne package. effect, alchemy and the two @effect/platform-* packages come
with it at versions that resolve, so there is nothing else to pin. React is an
optional peer — a server-only app installs ramose alone.
- What is Ramose? — the one-minute version
- Getting started — a realtime todo app from an empty folder, in 15 minutes
- Tour of Reef — a multi-tenant issue tracker whose whole backend is 680 lines
- How it compares — against Convex, Supabase, Instant, Firebase, and D1
- Examples — runnable apps in this repository
Ramose is pre-release. The API moves between releases, so pin exact versions. Issues and pull requests are welcome.
Ramose itself is a Bun monorepo — bun install && bun test. See
CONTRIBUTING.md.