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(state): document nested append paths and Case-2 behavior change
Updates `docs/workers/iii-state.mdx` to surface the nested-path
support that lands with this PR and the FR-9 Case-2 transition.
- Append's example row in the operations table swapped to the
nested-path form to make the new shape obvious at a glance.
- The "first-level field names" sentence narrowed to set / increment
/ decrement / remove (the four ops still gated to single-string
paths). Merge + append now share a paragraph + side-by-side code
examples covering root / first-level / nested paths.
- New explicit note on the FR-11 nested-vs-single-path divergence
for missing leaves: nested missing leaves are ALWAYS arrays even
for string values, while single-string paths preserve the legacy
string-concat tier. This is the surface-level user-facing rule.
- New **Behavior change worth noting** callout on the Case-2
transition (object/scalar leaf -> `append.type_mismatch` rather
than silent no-op). Marks the boundary between back-compat
payloads (unchanged) and the documented break.
- Two new error code rows: `append.path.too_deep` and
`append.path.empty_segment` mirror the merge-side validation
surface.
Refs #1552.
Ridden with [Loa](https://github.com/0xHoneyJar/loa)
Copy file name to clipboardExpand all lines: docs/workers/iii-state.mdx
+16-7Lines changed: 16 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -182,27 +182,34 @@ name: bridge
182
182
| `merge` | `{ "type": "merge", "path": ["sessions", "abc"], "value": { "ts": "chunk" } }` | Shallow-merge an object at the root or at any nested path. |
183
183
| `increment` | `{ "type": "increment", "path": "count", "by": 1 }` | Add `by` to a numeric field. |
184
184
| `decrement` | `{ "type": "decrement", "path": "count", "by": 1 }` | Subtract `by` from a numeric field. |
185
-
| `append` | `{ "type": "append", "path": "events", "value": { "kind": "chunk" } }` | Push one element to an array or concatenate a string value. |
185
+
| `append` | `{ "type": "append", "path": ["sessions", "abc", "events"], "value": { "kind": "chunk" } }` | Push one element to an array (or concatenate a string) at the root, a first-level field, or any nested path. |
186
186
| `remove` | `{ "type": "remove", "path": "status" }` | Remove a field from the current object. |
187
187
188
-
For `set`, `increment`, `decrement`, `append`, and `remove`, paths are first-level field names. For example, `user.name` updates the field named `user.name`; it does not traverse into `{ "user": { "name": ... } }`.
188
+
For `set`, `increment`, `decrement`, and `remove`, paths are first-level field names. For example, `user.name` updates the field named `user.name`; it does not traverse into `{ "user": { "name": ... } }`.
189
189
190
-
For `merge`, `path` accepts either a single string (legacy / first-level field) or an array of literal segments for nested merge:
190
+
For `merge` and `append`, `path` accepts either a single string (legacy / first-level field) or an array of literal segments for nested traversal:
Each array element is a *literal* key. `["a.b"]` writes a single key named `"a.b"`, not `a → b`.
205
208
209
+
**Append at a nested missing leaf is always an array.** When `append` walks to a missing leaf at the end of an array-form path, it creates `[value]` regardless of the value's type — including string values, which would be kept as a string under the legacy single-string path's string-concat tier. This is the core fix for [issue #1552](https://github.com/iii-hq/iii/issues/1552).
210
+
211
+
**Behavior change worth noting (issue #1552 case 2):** `append` at a path whose existing leaf is a JSON object or scalar (number/boolean) — i.e. a value that can't be appended to — now returns a structured `append.type_mismatch` error in the response `errors` array. Previously this would silently no-op when reached via the single-string path. Existing callers using `path: ""` or `path: "field"` against array, string, null, or missing-field leaves are unaffected.
212
+
206
213
Validation: invalid update inputs are rejected with a structured error in the response's `errors` array. Reasons include path depth > 32 segments, segment > 256 bytes, value depth > 16, > 1024 top-level keys, type mismatches, non-object targets, or any segment / top-level key matching `__proto__` / `constructor` / `prototype`. Successfully applied ops still reflect in `new_value`.
207
214
</ResponseField>
208
215
</Accordion>
@@ -238,6 +245,8 @@ Each `state::update` op may add an entry to the response `errors` array. Operati
238
245
| `<op>.path.segment_too_long` | A path segment is longer than 256 bytes | Shorten the field name or merge path segment. |
239
246
| `merge.path.too_deep` | A nested merge path has more than 32 segments | Reduce the nested path depth. |
240
247
| `merge.path.empty_segment` | A nested merge path array contains an empty segment | Remove the empty segment. |
248
+
| `append.path.too_deep` | A nested append path has more than 32 segments | Reduce the nested path depth. |
249
+
| `append.path.empty_segment` | A nested append path array contains an empty segment | Remove the empty segment. |
241
250
| `merge.value.not_an_object` | `merge` value is not a JSON object | Pass an object as the merge value. |
242
251
| `merge.value.too_deep` | `merge` value has JSON nesting deeper than 16 levels | Flatten the value. |
243
252
| `merge.value.too_many_keys` | `merge` value has more than 1024 top-level keys | Split the write into smaller updates. |
0 commit comments