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.
The smallest Worker
Section titled “The smallest Worker”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:
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:
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.
What the Worker can do
Section titled “What the Worker can do”| capability | your 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.
How calls travel
Section titled “How calls travel”| layer | how the call travels |
|---|---|
Ramose.ServerBinding | a service binding — Cloudflare’s Worker-to-Worker call (glossary): no public hop, no URL. The server must be declared as a Cloudflare.Worker. |
Ramose.ServerHttp | the 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.
Handling failures
Section titled “Handling failures”Errors are tagged, so mapping them to HTTP is one Effect.catchTags:
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.
Live queries need a browser
Section titled “Live queries need a browser”The complete Worker is examples/kv-style: four files, type-checked with the repo.