Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.

Commit 12e716c

Browse files
committed
docs(packaging): clarify merge() array and record-delete behavior
Document two FileHelper.merge() semantics that are easy to get wrong: arrays (and primitives) are replaced wholesale, not merged element-wise; and passing `undefined` to delete one entry of a typed z.record(...).catch() fails validation and the .catch() wipes the whole record, not just that entry. Adds an "Arrays Are Replaced, Not Merged" section plus a warning in the File Models page.
1 parent 74933f6 commit 12e716c

1 file changed

Lines changed: 22 additions & 0 deletions

File tree

packaging/src/file-models.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,28 @@ await configToml.merge(effects, {
436436
} as any);
437437
```
438438

439+
> [!WARNING]
440+
> This removes a stale key your schema doesn't model. It cannot surgically delete one entry of a _typed_ collection that has a `.catch()` default. `merge({ users: { bob: undefined } })` against `users: z.record(...).catch({})` makes the whole `users` value fail validation, so the `.catch({})` replaces the **entire** record with `{}` — every entry is wiped, not just `bob`. To drop one entry while keeping the rest, rebuild the value in code and `write()` it.
441+
442+
### Arrays Are Replaced, Not Merged
443+
444+
`merge()` recurses into plain **objects** key by key, but it treats **arrays and primitives as atomic values** — whatever you pass replaces what was there. For an array this means no element-wise union, append, or de-duplication: the array in your patch becomes the new value in its entirety.
445+
446+
```typescript
447+
// stored: { friends: ["alice", "bob"] }
448+
await storeJson.merge(effects, { friends: ["alice"] });
449+
// result: { friends: ["alice"] } — "bob" is dropped, not preserved
450+
```
451+
452+
This produces an asymmetry that is easy to get wrong, because object keys and array elements behave oppositely under the same `merge()` call:
453+
454+
| What you merge | What happens to what you left out |
455+
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------- |
456+
| An **object** without a key | The key is **kept**`merge()` never deletes a key you don't mention (see [Unknown Key Preservation](#unknown-key-preservation)) |
457+
| An **array** without an entry | The entry is **gone** — the whole array is overwritten |
458+
459+
So to change one entry of an array, read the current array, edit it in code, and merge the **complete** new array — you cannot add or drop a single element by passing a one-element patch. When you instead need to remove an object key (or otherwise rebuild a structure wholesale), reach for `write()`, which replaces the entire file.
460+
439461
### Using SDK-Provided Schemas
440462

441463
For complex types like SMTP, use the SDK's built-in zod schemas. See [Actions](./actions.md) for the full SMTP configuration walkthrough.

0 commit comments

Comments
 (0)