The createMerger API
Relevant source files
The following files were used as context for generating this wiki page:
The createMerger API is the primary entry point for the keyfold library. It is designed to create a reusable, immutable merger function that folds partial updates (deltas) into a nested state tree. Unlike standard JSON Merge Patch, createMerger allows for the configuration of keyed lists, ensuring that arrays are reconciled by identity rather than simple index matching or wholesale replacement.
This API facilitates complex state transitions in environments like websocket reducers or optimistic UI updates by maintaining structural sharing. If a merge results in no changes, the original base reference is returned, making it highly efficient for change detection in state management systems.
Sources: README.md:1-25, src/merge.ts:13-18
Core Functionality and Architecture
The createMerger function operates by compiling a set of configuration options into a policy tree, which then guides a recursive folding process. The resulting merger function is stateless and guarantees that neither the base state nor the delta payload is mutated during the operation.
Data Flow and Recursion
The merging logic distinguishes between plain objects, keyed lists, and atomic values. Plain objects are deep-merged, while arrays are either replaced wholesale (default) or reconciled based on a configured identity field.
The diagram shows how the merger decides to recurse, reconcile, or replace based on the compiled policy and data types.
Sources: src/merge.ts:25-63, src/paths.ts:26-40
Configuration Options
The behavior of the merger is controlled by the MergeOptions object passed to createMerger. This configuration is validated at creation time to prevent unreachable policies or malformed paths.
| Option | Type | Description |
|---|---|---|
keyBy | Record<string, string> | Maps dot-separated list paths to their respective identity field names (e.g., { "items": "id" }). |
replace | string[] | Specifies paths that should be swapped wholesale rather than merged. Supports the [] item-swap idiom. |
wireDeletes | boolean | When true, enables interpretation of DELETE_TOKEN ("@@keyfold/delete") as a field deletion operator. |
Sources: src/types.ts:3-19, src/paths.ts:21-25
Keyed List Reconciliation
When a path is configured in keyBy, the merger treats the array at that path as a collection of entities.
- Updates: Items with matching IDs are recursively merged.
- Deletes: Items containing
$delete: trueare removed from the list. - Inserts: New IDs are appended to the end of the list.
- Order: Survivors maintain their original order relative to each other.
Sources: src/reconcile.ts:21-65, README.md:76-85
The Policy Tree and Path Grammar
Internally, createMerger converts the flat MergeOptions into a PolicyNode tree. Paths use dot-notation for properties and [] to traverse into keyed list items.
The relationship between compiled options and the recursive policy structure used during merging.
Sources: src/paths.ts:8-19, src/paths.ts:133-146
Validation Rules
The configuration process enforces several strict rules:
- Unreachable Paths: A
replacepolicy cannot exist beneath anotherreplacepath. - Missing Keys: Using the
[]suffix in a path requires a correspondingkeyByentry for the parent list. - Reserved Names: Paths cannot step through
__proto__,constructor, orprototype. - Identity Fields: Identity fields cannot be
$deleteor other reserved keys.
Sources: src/paths.ts:46-85, test/config.test.ts:7-35
Operational Sentinels
The API uses specific tokens to represent explicit deletion operations within the delta.
DELETE(Symbol): Used in-memory to remove optional object fields.DELETE_TOKEN(String): The wire-safe version ofDELETEfor JSON transports, enabled viawireDeletes.$delete(Boolean): A reserved field used within keyed list items to trigger item removal.
import { createMerger, DELETE, DELETE_TOKEN } from "@b2m9/keyfold";
const merge = createMerger<State>({ wireDeletes: true });
const delta = {
optionalField: DELETE, // Removes the field
wireField: DELETE_TOKEN, // Removes the field (if wireDeletes: true)
items: [{ id: "a", $delete: true }] // Removes item 'a' from keyed list
};
Sources: src/sentinels.ts:1-5, src/merge.ts:88-95, src/reconcile.ts:121-137
Type Safety and Atomic Values
The createMerger API is generic, accepting a state type T and an optional Atomic type parameter.
Delta Type Constraints
The Delta<T, Atomic> type ensures that:
- Required properties cannot be targeted with
DELETE. - Custom classes or opaque types (declared via
Atomic) must be provided in full if included in the delta, preventing the merger from attempting to recurse into private class members. - Built-in types like
Date,RegExp,Map, andSetare automatically treated as atomic.
Sources: src/types.ts:21-50, test/types.ts:31-70
Immutability and Structural Sharing
The merger implementation uses a lazy-copy strategy for objects. It only creates a new object container when a property actually changes value or is deleted.
The sequence of property evaluation ensuring structural sharing.
Sources: src/merge.ts:74-110, src/objects.ts:31-45
Error Handling
The API utilizes two specific error classes:
KeyfoldConfigError: Thrown duringcreateMergerif the providedMergeOptionsare invalid.KeyfoldMergeError: Thrown during the execution of the merger function if data invariants are violated (e.g., duplicate IDs in a keyed list, or a tombstone containing extra data fields).
Sources: src/errors.ts:1-10, src/reconcile.ts:31-40
The createMerger API provides a robust mechanism for synchronizing complex nested states by combining the simplicity of JSON Merge Patch with the necessity of identity-based list reconciliation, all while maintaining strict immutability and performance through structural sharing.