You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(api): describe the stopped-batch NOT_ATTEMPTED tail on the non-atomic arm (#7539)
`data-api.mdx` described `atomic: false` as "sequential best-effort, stopping
at the first failure" without saying what the response contains for the
records it never reached — the shape the fix makes explicit. `batch.mdx` is
regenerated from the two `.describe()` strings this change touches.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016gd2bypaK4KYs78q8RP38G
Copy file name to clipboardExpand all lines: content/docs/api/data-api.mdx
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -241,7 +241,7 @@ Execute a batch operation (create / update / upsert / delete) on multiple record
241
241
242
242
**Response**: `BatchUpdateResponse` with `succeeded`, `failed`, `total`, and a per-record `results` array. Each entry in `results` has `id`, `success`, `index` (the row's position in the request array), an optional `errors` array (`ApiError[]` — read `errors[0].message`, branch on `errors[0].code`), and optional `data` (the full record, present when `returnRecords` is `true`).
243
243
244
-
`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` — each row's `errors[0].code` says what happened: `ROLLED_BACK` (written, then undone), the causal row's own error code, or `NOT_ATTEMPTED` (never reached). 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`.
244
+
`options.atomic` defaults to `false`: sequential best-effort that stops at the first failure. Records written before the failure stay written — nothing is rolled back on this arm — and every record after it is reported with `errors[0].code``NOT_ATTEMPTED` rather than omitted, so `results` always covers all `total` records and `succeeded + failed === total` (#7539). Send `continueOnError: true` to process the remaining records instead of stopping. Set `atomic` to `true` and the whole batch runs inside one transaction: the first failure rolls back every prior write, and the response reports `succeeded: 0` — each row's `errors[0].code` says what happened: `ROLLED_BACK` (written, then undone), the causal row's own error code, or `NOT_ATTEMPTED` (never reached). 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`.
245
245
246
246
### `POST /data/:object/createMany`
247
247
@@ -284,7 +284,9 @@ selects rows, so no body key can widen the delete into a filter.
284
284
deleted one at a time by primary key, so each honours `deleteBehavior`
285
285
(`cascade` / `set_null` / `restrict`) on relations pointing at it. The run stops
286
286
at the first failure; `continueOnError: true` processes the remaining ids and
287
-
reports the failures instead.
287
+
reports the failures instead. Either way every id gets a `results` entry — the
288
+
ids a stopped run never reached carry `errors[0].code``NOT_ATTEMPTED`, so the
289
+
counters reconcile against `total` (#7539).
288
290
289
291
`options.atomic: true` is honoured here the same way as on `/batch` (#4620): the
290
292
whole id list runs inside one transaction, the first failure rolls back every
Copy file name to clipboardExpand all lines: content/docs/references/api/batch.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -55,7 +55,7 @@ const result = BatchConfigSchema.parse(data);
55
55
| :--- | :--- | :--- | :--- |
56
56
|**id**|`string`| optional | Record ID if operation succeeded |
57
57
|**success**|`boolean`| ✅ | Whether this record was processed successfully |
58
-
|**errors**|`{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +259 more>; message: string; category?: string; httpStatus?: integer; … }[]`| optional | Array of errors if operation failed. Branch on `errors[0].code` — an atomic batch that rolled back marks rows that were written then undone with code ROLLED_BACK and rows never reached with NOT_ATTEMPTED, while the causal row keeps its own error (#4793). |
58
+
|**errors**|`{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +259 more>; message: string; category?: string; httpStatus?: integer; … }[]`| optional | Array of errors if operation failed. Branch on `errors[0].code` — an atomic batch that rolled back marks rows that were written then undone with code ROLLED_BACK and rows never reached with NOT_ATTEMPTED, while the causal row keeps its own error (#4793). A NON-atomic batch that stopped (the `continueOnError: false` default) marks its un-attempted tail with the same NOT_ATTEMPTED code — rows before the failure stay written and keep reporting success, since nothing was rolled back (#7539). |
59
59
|**data**|`Record<string, any>`| optional | Full record data (if returnRecords=true) |
60
60
|**index**|`number`| optional | Index of the record in the request array |
61
61
|**droppedFields**|`{ object: string; fields: string[]; reason: Enum<'readonly' \| 'readonly_when' \| 'primary_key'> }[]`| optional | Write-observability (#3407/#3431/#3455): caller-supplied fields LEGALLY stripped from THIS row before it was written — static `readonly` (#2948) / TRUE `readonlyWhen` (#3042) on update, or the #3043 create-ingress strip. Per-row because a batch can drop different fields on different rows (`readonlyWhen` is record-state-dependent). Present ONLY when ≥1 field was dropped for this row; the row still succeeded (success unchanged). A single response header cannot express per-row drops, so this body field is the canonical bulk channel — REST does not emit `X-ObjectStack-Dropped-Fields` for batches. Optional — omit-when-empty keeps the shape backward-compatible. |
@@ -83,7 +83,7 @@ const result = BatchConfigSchema.parse(data);
83
83
| :--- | :--- | :--- | :--- |
84
84
|**atomic**|`boolean`| ✅ | Opt-in all-or-nothing. When explicitly true the whole batch runs inside ONE engine transaction: the first failure rolls back every prior write, and the response reports zero successes — each row carries `errors[0].code` ROLLED_BACK (written, then undone), the causal row its own error, and rows never reached NOT_ATTEMPTED. A runtime that cannot roll back REFUSES the request (501 NOT_IMPLEMENTED) rather than silently degrading to best-effort — probe `capabilities.transactionalBatch` on /discovery first. Takes precedence over continueOnError. Default false: sequential best-effort. |
85
85
|**returnRecords**|`boolean`| ✅ | If true, return full record data in response |
86
-
|**continueOnError**|`boolean`| ✅ | If true (and atomic=false), continue processing remaining records after errors |
86
+
|**continueOnError**|`boolean`| ✅ | If true (and atomic=false), continue processing remaining records after errors. Default false: the first failure ENDS the run — records before it stay written (nothing is rolled back on this arm), and every record after it is reported `errors[0].code` NOT_ATTEMPTED rather than omitted, so `results` always covers all `total` records and `succeeded + failed === total` (#7539).|
87
87
|**validateOnly**|`never`| optional |[REMOVED]`options.validateOnly` was removed from BatchOptions in @objectstack/spec (#4052). It was never implemented: the batch surfaces persisted regardless, so a "dry-run" would have silently executed. There is no dry-run today — drop the key. If you need to preview a batch without writing, open an issue so it can be designed (no-commit cascade / constraint semantics) and reintroduced as a flag that actually holds. |
0 commit comments