Remote / Declared writes
Failure and recovery
| Outcome | Meaning | What the client does |
|---|---|---|
| Written | the row as it now is, under the caller's rules; a row the write hid from them as deleted | nothing; the answer replaces what showed |
Forbidden | a hidden row, or a rule that does not hold | show it; asking again is refused again |
Conflict | the input's revision is not the row's | re-read, then let the author decide |
Unmet | the row does not hold the write's when | show why; the state changed |
Invalid | the row the write would leave breaks an invariant, over a row or over the change | show its message beside its path; change the input |
Taken | an insert's id is already a row's, and the ledger holds no record of this operation making it (a 409 over HTTP) | choose another id; the refusal names the Entity and the id, nothing of the row |
| "changed while it was written" | the row kept moving through three decisions | retry later |
Unreachable | no answer came back; the write may or may not have landed | Data.retry (a durable write is sent again on its own); safe as far as the retry table says |
| "needs a database with transactions" | a ledger over a database with neither a transaction nor D1's batch | give the writer a Drizzle database |
What each database guarantees
| SQLite | Postgres | D1 | |
|---|---|---|---|
Revision, when, increment, invariant | one statement | one statement | one statement |
checkConstraints, entityChecks | in create table | alter table … add | in create table |
| Ledger | a synchronous transaction | an awaited transaction; the primary key settles a race | a read, then one batch; the primary key settles a race |
revisionTrigger | yes | yes | yes (SQLite) |
One statement is atomic on every database, so a precondition and an increment
need no transaction. D1 runs no BEGIN, so its ledger writes the row and the
record in one batch, the record decided in SQL (changes() > 0) since a batch
reads nothing between its statements. Opening a transaction is not by itself a
guarantee: the ledger relies on its primary key, not on isolation, to refuse a
second copy.
Limits
- One row by id. Bulk predicates, nested writes and multi-row programs are handlers.
- An invariant reads one row. Uniqueness, foreign keys and limits across rows need the database to coordinate them; a predicate cannot say them.
- A computed assignment reads its own row before the write and the input, never another row or another assignment's result.
- A rule that reads facts outside the database (a remote permission service) cannot be made atomic with the write; only what the statement can read is.
- Without the trigger, the pin trusts every writer to move the revision.
- A ledger's record lasts its retention; an increment retried later is applied again.