Skip to content

Use it from a Worker

Your own Worker gets a typed client bound to the Ramose server — no URL, no token to manage. This page is for anyone who has deployed once and wants server-side reads and writes.

You declare what the Worker may do — a capability (glossary) — and, separately, how the call travels. The Ramose server is the one Cloudflare Worker that serves all your databases; Ramose’s code calls it the peer. The examples/kv-style Worker binds it once at start-up:

examples/kv-style/app.ts:28-33 · app.ts:146
export const App = Cloudflare.Worker(
"App",
{ main: import.meta.url },
Effect.gen(function* () {
// The binding *is* the client. One `Databases`, bound once at init.
const ramose = yield* Ramose.ReadWriteDatabases(Server);
// …
}).pipe(Effect.provide(Ramose.ServerBinding)),

Inside, a request handler names a database, writes, and reads its own write back — no fetch, no connection string:

examples/kv-style/app.ts:59-68
const tenant = ramose.db(tenantId, Movies);
const { t, dbAfter } = yield* tenant.transact(function* (tx) {
const ada = yield* tx.entity();
yield* ada.add(User.name, "Ada");
});
// `dbAfter` carries the min-`t` floor, so this reads its own write
const names = yield* dbAfter.q(
Ramose.query(User).select({ name: User.name }),
);

ramose.db(name, catalog) costs nothing per request; creating a customer’s database at sign-up is on One database per customer. The file’s effect/unstable/http imports are Effect 4’s HTTP module — the name is Effect’s, not a warning about Ramose.

capabilityyour Worker gets
Ramose.ReadWriteDatabases(Server)q, pull, basis, asOf, history, principal, transact, install
Ramose.ReadDatabases(Server)q, pull, basis, asOf, history — no transact, install, or principal

live and livePull are in the type of both but do not work here (see below). There is no write-only capability: a writer that cannot read cannot look anything up.

layerhow the call travels
Ramose.ServerBindinga service binding — Cloudflare’s Worker-to-Worker call (glossary): no public hop, no URL. The server must be declared as a Cloudflare.Worker.
Ramose.ServerHttpthe server’s public URL over ordinary fetch — also what alchemy dev and deploy-time actions use

Provide one with Effect.provide around the whole Worker; nothing inside changes when you swap.

Errors are tagged, so mapping them to HTTP is one Effect.catchTags:

examples/kv-style/app.ts:128-143
Effect.catchTags({
TxRejected: (e) =>
HttpServerResponse.json({ error: e.message, code: e.code }, { status: 409 }),
Unavailable: (e) =>
HttpServerResponse.json(
{ error: e.message },
{ status: 503, headers: { "retry-after": String(Math.ceil(e.retryAfterMs / 1000)) } },
),
QueryBudgetExceeded: (e) =>
HttpServerResponse.json({ error: e.message, clause: e.clause }, { status: 413 }),
InvalidRequest: (e) => HttpServerResponse.json({ error: e.message }, { status: 400 }),
Unauthorized: (e) => HttpServerResponse.json({ error: e.message }, { status: 401 }),
DatabaseNotFound: (e) => HttpServerResponse.json({ error: e.message }, { status: 404 }),
InternalError: (e) => HttpServerResponse.json({ error: e.message }, { status: 500 }),
NetworkError: (e) => HttpServerResponse.json({ error: e.message }, { status: 502 }),
}),

Configuration mistakes are not in that list on purpose: a missing binding or a malformed URL fails at start-up, not on some unlucky request. Unavailable and NetworkError are tried 6 times before you see them.

The complete Worker is examples/kv-style: four files, type-checked with the repo.