Transactions, batches and field transforms
Change many documents at once (all or nothing), read-then-write safely under concurrency, and update fields atomically on the server.
Every document has a version (1 when created, +1 on each write) and an update_time. Writes go through one atomic commit: up to your plan's writes per commit, applied together or not at all, each checked by your collection rules and row policies.
Field transforms
Transforms run on the server inside the commit, on the document as it is then — two clients incrementing a counter never lose an update.
| Transform | Effect |
|---|---|
increment(n) | Adds n (sets n when the field isn't a number) |
maximum(n) / minimum(n) | Keeps the larger / smaller value |
arrayUnion(...) | Adds elements that aren't already in the array |
arrayRemove(...) | Removes every matching element |
serverTimestamp() | The commit time (ISO 8601, UTC) |
delete() | Removes the field (with update or a merging set) |
import { FieldValue } from '@sovaria/sdk'
await sov.doc('posts/p1').update({
likes: FieldValue.increment(1),
tags: FieldValue.arrayUnion('featured'),
'stats.lastLikedAt': FieldValue.serverTimestamp(),
})
await sov.doc('users/alice').set({ prefs: { theme: 'dark' } }, { merge: true }) // deep merge
await sov.doc('users/alice').update({ draft: FieldValue.delete() }, { version: 7 }) // only if still v7Batched writes
const batch = sov.batch()
batch.set(sov.doc('orders/o1'), { status: 'paid' })
batch.update(sov.doc('stock/sku1'), { qty: FieldValue.increment(-1) })
batch.delete(sov.doc('carts/c1'))
await batch.commit() // all three or noneTransactions
A transaction reads documents, then writes based on what it read. The commit sends the versions it read: if any of those documents changed in the meantime, nothing is written and the SDK runs your function again (up to 5 times). Read everything before the first write.
await sov.runTransaction(async (tx) => {
const from = await tx.get(sov.doc('accounts/a'))
const balance = from.data!.balance as number
if (balance < 10) throw new Error('Insufficient funds')
tx.update(sov.doc('accounts/a'), { balance: balance - 10 })
tx.update(sov.doc('accounts/b'), { balance: FieldValue.increment(10) })
})Errors
| Status · code | Meaning |
|---|---|
409 ABORTED | A document the transaction read changed meanwhile — retry (the SDKs do). |
409 ALREADY_EXISTS | create on a document that exists. |
404 NOT_FOUND | update (or exists: true) on a document that doesn't exist. |
412 FAILED_PRECONDITION | The document isn't at the version / update_time you gave. |
403 PERMISSION_DENIED | A collection rule or row policy refused one of the writes; nothing was written. |
403 PLAN_LIMIT_REACHED | More writes than docs.max_transaction_writes (500 on every plan). |