Atomic updates
Update counters and flags atomically in the database.
Atomic update envelopes read and write a column in one database statement. They are useful for balances, counters, retry counts, and flags that may be changed concurrently.
const account = await client.accounts.update({
where: { id },
data: {
balance: { increment: 100 },
loginCount: { increment: 1 },
retries: { decrement: 1 },
locked: { toggle: true },
},
});Operators
Numeric columns accept increment, decrement, multiply, divide, set, max, and min. Boolean columns accept toggle: true. Direct values remain valid.
Several numeric operators may be combined, except max and min, which stand alone. Better Drizzle always applies them in this order: set, increment, decrement, multiply, then divide.
await client.accounts.update({
where: { id },
data: { balance: { set: 10, increment: 4, multiply: 3 } }, // 42
});divide: 0, non-finite operands, empty envelopes, and operators used on incompatible columns fail before SQL is sent. NULL and integer-division behavior remain those of the selected database.
max and min
max keeps the larger of the column and the operand, and min keeps the smaller. Use them for values that only move one way, such as a high score or a stock cap.
await client.players.update({
where: { id },
data: { highScore: { max: score } },
});
await client.products.updateMany({
where: { warehouseId },
data: { stock: { min: maxStock } },
});| Dialect | { max: v } | { min: v } |
|---|---|---|
| PostgreSQL | GREATEST(col, v) | LEAST(col, v) |
| MySQL | GREATEST(COALESCE(col, v), v) | LEAST(COALESCE(col, v), v) |
| SQLite | MAX(COALESCE(col, v), v) | MIN(COALESCE(col, v), v) |
A NULL column becomes v on every dialect. max and min cannot be combined with each other or with other operators on the same column.
Supported writes
Use atomic envelopes in update, updateMany, updateEach, upsert, and upsertMany. In updateEach, return the envelope from the column callback:
await client.accounts.updateEach({
by: accounts.id,
data: [{ id: 1, delta: 5 }],
update: { balance: (row) => ({ increment: row.delta }) },
});PostgreSQL, SQLite, and MySQL support atomic updates in every write that accepts them, including upsertMany.
Hooks
After an atomic write, post-write hooks receive a readonly compiled object containing the final SET expressions. Plugins still receive the declarative envelope before execution.
const client = better(db, {
hooks: {
afterUpdate({ compiled }) {
console.log(compiled);
},
},
});