How it works
The mental model: a domain, live reads, actions as the only writes, and what each piece guarantees.
Three things
Everything in DomainRuntime is one of three things:
- The domain — entities, links and actions, in one chain of TypeScript. Reading it tells you everything the product stores and everything it can do.
- Reads — queries from the browser or your backend that stay live, under the signed-in user's permissions.
- Actions — the only way data changes: validated input, your code, validated output, the caller recorded.
A query re-runs when the data it depends on changes, the way a React component re-renders when state changes. You never refetch: you call an action, and every affected query updates.
What happens on a request
Browser ──query / subscribe──▶ <env>.domainruntime.cloud ──▶ database
◀── live results ───── (permissions applied)
Browser ──db.actions.x(input)─▶ runtime ── checks input, records the execution
│
└─▶ your execute() ──▶ runtime.tx(...) ──▶ database
│
every affected live query refreshes ◀────┘db.actions and runtime.tx are the planned client and runtime APIs. Today actions are called over HTTP and write with runtime.db().
Why writes go through actions
Most realtime databases let the browser write and rely on rules to stop bad writes. That works until the rules must express business logic — "an order can be paid once, only by its customer, only if stock is reserved" — and become code nobody can test.
DomainRuntime splits the job:
- Rules answer which data can this user touch? They are small and apply to every read and write.
- Actions answer what does this operation do? They are ordinary TypeScript you can test, name and review.
The browser keeps live reads for a responsive UI. Every change is a named operation with an input contract, a caller and an execution record.
Where code runs, and what it guarantees
| A client query | An action's execute | One runtime.tx | A workflow action | A "use step" | |
|---|---|---|---|---|---|
| Reads | yes | runtime.query | — | through its steps | runtime.query |
| Writes | no | through runtime.tx | yes | through its steps | through runtime.tx |
| All or nothing | — | no, per tx | yes | no | per tx |
| Live for subscribers | yes | — | its commit refreshes them | — | — |
| Calls other services | no | yes | no | through steps | yes |
| After a failure | — | not re-run; one run per request id | — | resumes after the last finished step | runs again |
| Time | 30 s per query | a call waits up to 110 s | — | minutes to days | seconds |
Two rules follow:
- An action is not a transaction; a
txis. Put the writes that must happen together in oneruntime.tx. - Reading, then writing, is not a lock. Two calls can both read "no such customer" and both create one. Let the database decide with
.unique(). See Guarantees.
The pieces you own
Organization
└─ Project one product, one domain
├─ Folder (optional) groups environments, up to 8 levels
└─ Environment a running copy of the domain with its own data
https://<handle>.domainruntime.cloud- Organization — your company. Signing up creates a personal one; members and keys live here.
- Project — one product, with one domain.
- Environment — a running copy of the domain with its own data and URL.
| Kind | For | Lifetime |
|---|---|---|
| Development | You, while you build. One per developer; republished on every save. | Data is kept. The machine running your actions stops after 30 minutes idle or 7.9 hours and restarts on the next druntime. |
| Production | Your users. Runs an immutable release of your domain. | Permanent. |
Publish sends your domain to a development environment (druntime, druntime push). A release is an immutable build of your domain for production. Environments share code, never data. See Environments.
Where it runs
Everything runs in São Paulo (AWS sa-east-1): the runtime on a host there, data in PlanetScale Postgres with a primary and two replicas, files in Amazon S3. Development environments run your action code on an isolated container per environment. See Architecture.