DomainRuntimedocs
Working with data
VerifiedWhat works todayReviewed 2026-09-23

Guarantees

What is atomic, what runs once, what is live, and what you design for yourself.

Every guarantee on this page holds in production today. Examples use the planned runtime.tx and runtime.query; today the same guarantees apply to await runtime.db() with db.transact and db.query.

This page is the contract. If your code relies on something not listed here, it relies on luck.

At a glance

GuaranteedNot guaranteed
One runtime.txall its operations apply, or none doisolation from concurrent reads
One actioninput and output validated; runs once per request id; caller recordedthat its separate tx calls are atomic together
.unique()enforced by the database on every write—
Permissionsevery read and write of a production action runs under the caller's rules—
Live queriesevery commit, by any writer, refreshes affected subscribersordering between two different queries
Workflow stepsa finished step never runs againthat a running step runs only once

A tx is the transaction

await runtime.tx((tx) => [
  tx.accounts_account[from].update({ balance: fromBalance - amount }),
  tx.accounts_account[to].update({ balance: toBalance + amount }),
]);

Both updates commit together or not at all. Two runtime.tx calls are two transactions: if the second fails, the first stays. Inside one tx, if two operations set the same attribute of the same object, the last one wins.

Reading does not lock

runtime.query reads committed data; it does not hold it. Between your read and your tx, another action can change it. A check-then-write is only correct if the database can enforce the condition itself:

// ❌ two concurrent calls can both see "none" and both create
const { orders_customer } = await runtime.query({
  orders_customer: { $: { where: { email: input.email } } },
});
if (orders_customer.length === 0) {
  await runtime.tx((tx) => tx.orders_customer[id()].update({ email: input.email, name: input.name }));
}

// ✅ `email` is .unique(); this creates or updates the one customer
await runtime.tx((tx) =>
  tx.orders_customer[lookup("email", input.email)].update({ name: input.name }),
);

For counters and balances, prefer appending over overwriting: record each movement as its own object and derive the total with a query.

Planned. Conditional writes — a tx that commits only if what you read has not changed — are designed, not built.

An action runs once per request

Same request id, same inputthe first execution is returned; your code does not run again
Same request id, different inputREQUEST_CONFLICT
Your code throwsthe execution fails with action_failed; committed tx calls stay
Your code returns an invalid outputthe execution fails with execution_output_invalid; committed tx calls stay
The process running it diesthe execution is marked failed, not re-run

A failed execution may have done part of its work. Keep each action's writes in one tx, and make a retry with a new request id harmless with caller-chosen ids or unique attributes.

Permissions apply inside actions

Reads and writes inside an action run with the caller's identity, in development and in production: a user's action cannot read or write what that user could not. A write the rules forbid fails the execution with action_failed; writes committed before it stay. Only a service key bypasses rules. See Permissions.

Live queries follow commits

Subscriptions refresh from the database's change log. A commit by an action, a workflow step or any other writer refreshes every affected query, in every tab, for every user who may see the result.

Workflows

A finished step is saved and never runs again. A step that was running when the process stopped runs again, so steps run at least once. A run keeps the identity it was admitted with and the release it started on. See Workflows.

Stale runners cannot write

Each execution attempt carries a fence. If an attempt is superseded — its 30-second lease expired and another took over — writes from the old one are rejected.

On this page