guide

Contention

A permission model can't stop two people booking the same 3pm slot. Preconditions can — and each is just an argument on a call you already make.

A 409 conflict is the mechanism working, not an error to retry away. Catch it and tell the user the slot, stock, or edit was taken.

Uniqueness — keyed creates

Derive the key from the thing that must be unique. The second writer gets a 409; your own retry returns your record back (existing: true); deleting the record frees the key.

await bookings.create({ who: email }, { key: "slot:2026-07-15T15:00" })

Limited stock — N units anyone can buy

Claim units with keyed creates — on conflict try the next unit; all taken = sold out. Race-proof under every safety rule, because writes on other users' records are never allowed.

await orders.create({ item: 42 }, { key: "unit:item42:1" })  // then :2 … :N

Counters — the server does the math

Atomic ops run against current state on the server. Breaching the floor or ceiling → 409, record untouched.

await products.update(id, { stock: { decrement: 1, floor: 0 } })

An object in a patch is an atomic op ONLY when its keys are exactly increment|decrement (+ optional floor|ceiling), all numbers — anything else is stored as plain data.

Shared editing — versions

Pass back the version you read. A stale save gets a 409 instead of silently clobbering someone's edit — re-read, reapply, retry.

await pages.update(id, { body }, { ifVersion: page.version })

Compound: one-per-user AND limited

A booking that must be unique and capped per user wants two keyed creates — claim the resource first, then the per-user cap; if the second 409s, delete the first (deleting frees its key):

const slot = await bookings.create({ when }, { key: "slot:" + when })
try {
  await quotas.create({ ref: slot.id }, { key: "user-day:" + user.userId + ":" + day })
} catch (e) {
  if (e.code === "conflict") { await bookings.delete(slot.id) }  // give the slot back
  throw e
}

Never find-then-create, never compute counters client-side. Both race — and both fail only when real users collide, which is exactly when it matters. Keys are 1–120 chars of letters, numbers, and : _ . @ / -.