Skip to content

Commit 83b61c9

Browse files
committed
docs(api): true up the batch atomic docs that D4 made wrong (#4612)
The docs-drift check flagged the hand-written API docs, and three of them were genuinely stale — they documented the bug ADR-0118 D4 fixes: - `wire-format.mdx` stated it outright: "By default (`options.atomic: true`) processing stops at the first failing record — records already written earlier in the same batch are **not** rolled back, since there is no wrapping database transaction." Accurate before, wrong on both halves now. - `client-sdk.mdx`'s options table listed `atomic` default `true` with "Rollback entire batch on any failure" — the default moved, and the rollback claim only became true with this change. - `data-api.mdx` described `atomic` as the default on `/batch` and `deleteMany`. Each now describes what the route actually does, including the 501 refusal and the rolled-back response shape (`succeeded: 0`, ROLLED_BACK / NOT_ATTEMPTED). Kept honest about scope: `deleteMany` and `updateMany` did NOT get D4's treatment, so `deleteMany`'s note now says plainly that its `atomic` only stops the run and rolls nothing back (#4620 tracks the fix). Documenting the fixed behaviour on routes that still carry the bug would have replaced one inaccuracy with another. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NKcGqCYCCpMkB5UW8jNPXx
1 parent 7236bb0 commit 83b61c9

3 files changed

Lines changed: 23 additions & 9 deletions

File tree

content/docs/api/client-sdk.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -504,9 +504,13 @@ The `find` method accepts an options object with **canonical** (recommended) fie
504504

505505
| Property | Type | Default | Description |
506506
|:---------|:-----|:--------|:------------|
507-
| `atomic` | `boolean` | `true` | Rollback entire batch on any failure |
507+
| `atomic` | `boolean` | `false` | Run the batch in one transaction and roll every write back on the first failure. Refused with `501 NOT_IMPLEMENTED` where the driver cannot roll back, rather than degrading to best-effort. Takes precedence over `continueOnError` |
508508
| `returnRecords` | `boolean` | `false` | Include full records in response |
509-
| `continueOnError` | `boolean` | `false` | Continue after errors (when atomic=false) |
509+
| `continueOnError` | `boolean` | `false` | Continue after errors (when atomic is false) |
510+
511+
A rolled-back atomic batch reports `succeeded: 0`, with each row carrying
512+
`ROLLED_BACK:`, the causal error, or `NOT_ATTEMPTED:` — no row is reported as a
513+
success, because none of them survived.
510514

511515
---
512516

content/docs/api/data-api.mdx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ Execute a batch operation (create / update / upsert / delete) on multiple record
228228

229229
**Response**: `BatchUpdateResponse` with `succeeded`, `failed`, `total`, and a per-record `results` array. Each entry in `results` has `id`, `success`, an optional `errors` array, and optional `data` (the full record, present when `returnRecords` is `true`).
230230

231+
`options.atomic` defaults to `false` (sequential best-effort, stopping at the first failure). Set it to `true` and the whole batch runs inside one transaction: the first failure rolls back every prior write, and the response reports `succeeded: 0` with each row marked `ROLLED_BACK:`, the causal error, or `NOT_ATTEMPTED:`. A deployment whose driver cannot roll back rejects an atomic request with `501 NOT_IMPLEMENTED` instead of running it best-effort — probe `capabilities.transactionalBatch` on `/discovery` first. `atomic` takes precedence over `continueOnError`.
232+
231233
### `POST /data/:object/createMany`
232234

233235
Batch create multiple records.
@@ -260,16 +262,20 @@ ignored, on this route and on `deleteMany`.
260262

261263
Batch delete records by ID list.
262264

263-
**Body**: `{ "ids": ["1", "2", "3"], "options": { "atomic": true } }``options` is
265+
**Body**: `{ "ids": ["1", "2", "3"], "options": { "continueOnError": true } }``options` is
264266
the same `BatchOptions` bag `/batch` takes. The body is validated against the
265267
contract and unknown keys are dropped: the id list is the *only* thing that
266268
selects rows, so no body key can widen the delete into a filter.
267269

268270
**Response**: `BatchUpdateResponse` — one `results` entry per id. Records are
269271
deleted one at a time by primary key, so each honours `deleteBehavior`
270-
(`cascade` / `set_null` / `restrict`) on relations pointing at it. `atomic`
271-
(default) stops the run at the first failure; `atomic: false` with
272-
`continueOnError: true` processes the remaining ids and reports the failures.
272+
(`cascade` / `set_null` / `restrict`) on relations pointing at it. The run stops
273+
at the first failure; `continueOnError: true` processes the remaining ids and
274+
reports the failures instead.
275+
276+
Note that on this route `atomic` only stops the run — deletes already performed
277+
are **not** rolled back. Unlike `/batch`, `deleteMany` has no wrapping
278+
transaction yet.
273279

274280
### Batch size
275281

content/docs/api/wire-format.mdx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,11 @@ release group); any code reading them directly should move to `error.code`.
471471

472472
**`POST /api/v1/data/task/batch`**
473473

474-
Process many records of a **single** operation type in one request. The body carries one `operation` (`create`, `update`, `upsert`, or `delete`) plus a `records` array. By default (`options.atomic: true`) processing stops at the first failing record — records already written earlier in the same batch are **not** rolled back, since there is no wrapping database transaction. Set `options.atomic: false` (with `options.continueOnError: true`) to keep processing every record and collect a full partial-success report.
474+
Process many records of a **single** operation type in one request. The body carries one `operation` (`create`, `update`, `upsert`, or `delete`) plus a `records` array.
475+
476+
By default (`options.atomic` omitted or `false`) processing stops at the first failing record, and records written earlier in the same batch are **not** rolled back — there is no wrapping transaction. Add `options.continueOnError: true` to keep going instead and collect a full partial-success report.
477+
478+
Send `options.atomic: true` to run the whole batch inside one database transaction: the first failure rolls back every prior write, and the response reports zero successes with each row marked `ROLLED_BACK:`, the causal error, or `NOT_ATTEMPTED:`. A deployment whose driver cannot roll back **rejects** an atomic request with `501 NOT_IMPLEMENTED` rather than quietly running it best-effort — probe `capabilities.transactionalBatch` on `/discovery` to know in advance. `atomic` takes precedence over `continueOnError`.
475479

476480
### Request
477481

@@ -483,7 +487,7 @@ Process many records of a **single** operation type in one request. The body car
483487
{ "id": "tsk_01HQ4B8C0E4G6H9K3L5M", "data": { "status": "done" } }
484488
],
485489
"options": {
486-
"atomic": true,
490+
"atomic": false,
487491
"continueOnError": false
488492
}
489493
}
@@ -509,7 +513,7 @@ The response is the `BatchUpdateResponse` envelope: a top-level `success` flag p
509513

510514
### Partial Failure Response
511515

512-
When `options.atomic: false` and some records fail, the failing entries carry a single `error` message string (not an array):
516+
When the batch is not atomic and some records fail, the failing entries carry a single `error` message string (not an array). An atomic batch never returns this shape — it either commits everything or reports every row as failed:
513517

514518
```json
515519
{

0 commit comments

Comments
 (0)