Building Websocket Reducers
Relevant source files
The following files were used as context for generating this wiki page:
Building websocket reducers with keyfold involves using immutable delta folding to synchronize state trees between a server and a client. This system allows for partial updates of large, nested state objects, specifically addressing the common pitfalls of merging arrays by index. By using identity-keyed list reconciliation, keyfold ensures that partial payloads correctly update, append, or delete items within lists based on unique identifiers rather than position.
Sources: README.md:1-25, AGENTS.md:5-10
The library serves as a specialized reducer that processes authoritative deltas. It is designed to be deterministic and idempotent, ensuring that re-applying the same delta returns the previous state by reference, which is critical for efficient UI updates and preventing unnecessary re-renders in websocket-driven applications.
Sources: README.md:135-145, AGENTS.md:20-22
Architecture and Data Flow
The core architecture centers on a merger function created via createMerger. This function takes a base state and a delta (partial update) to produce a new next state tree. The flow differentiates between plain objects (which deep-merge), keyed lists (which reconcile), and unkeyed arrays/scalars (which replace wholesale).
State Reconciliation Flow
The following diagram illustrates how a delta is processed through the merger to update a websocket-connected client's state.
Sources: src/merge.ts:40-66, README.md:40-55
Key Components
| Component | Description |
|---|---|
createMerger | Factory function that compiles path policies and returns a reusable merge function. |
Delta<T> | A TypeScript type that represents a recursive partial update of state T. |
keyBy | Configuration mapping list paths to their stable identity fields (e.g., id or sku). |
wireDeletes | Option to enable interpretation of DELETE_TOKEN for JSON-compatible field deletions. |
reconcile | Internal logic for matching keyed items, preserving order, and handling tombstones. |
Sources: src/merge.ts:16-25, src/types.ts:4-15, README.md:65-85
Implementing Wire-Safe Deletions
In a websocket environment, deletions must be transmitted via JSON. keyfold provides two mechanisms for this: $delete tombstones for list items and DELETE_TOKEN for object fields.
JSON Field Deletion Sequence
When wireDeletes is enabled, the string token "@@keyfold/delete" is interpreted as an instruction to remove a field, allowing JSON payloads to perform deletions that undefined cannot represent in standard JSON.
Item Tombstones
For keyed lists, an item is removed by sending an object containing the identity field and $delete: true. This mechanism is JSON-native and does not require the wireDeletes flag.
// Example Delta for removing an item from a list keyed by 'id'
const delta = {
items: [
{ id: "item-123", $delete: true }
]
};
Sources: src/reconcile.ts:114-128, README.md:129-132
Configuration and Path Policies
Path policies are compiled once during the creation of the merger. This ensures that malformed paths or contradictory policies (such as a replace path shadowing a keyBy path) are caught at startup rather than during message processing.
Path Syntax
- Dot Notation:
order.customertargets a nested object field. - Item Marker:
items[]targets the individual items within a keyed list for specific policies (like replacement).
Sources: src/paths.ts:40-60, README.md:148-155
Validation Constraints
The compileOptions function enforces several invariants to protect the integrity of the state tree:
- Unsafe Keys: Paths containing
__proto__,constructor, orprototypeare rejected. - Reachability: A
replacepolicy at a high level makes anykeyBypolicy deeper in that subtree unreachable and thus invalid. - Identity Stability: Identity fields must be strings or numbers;
NaNand reserved tokens are prohibited.
Sources: src/paths.ts:64-90, src/objects.ts:4-5, AGENTS.md:28-30
Performance and Determinism
keyfold is optimized for websocket updates where the delta is typically small compared to the total state.
- Delta-Proportional Work: The merger only visits branches mentioned in the delta. Keyed lists are the exception, requiring an O(list) scan to index identities when the list is touched.
- Structural Sharing: Branches of the state tree that are not modified by the delta retain their original memory references.
- Idempotency: Re-applying a delta to the state it just produced returns the base reference (
===), allowing stores to skip redundant work.
Sources: AGENTS.md:32-46, README.md:182-185, src/merge.ts:98-102
Summary
Building websocket reducers with keyfold provides a robust, typed way to handle partial state updates. By configuring identity-based reconciliation and enabling wire-compatible deletions, developers can ensure that client-side state stays synchronized with the server's authoritative data without the risks of index-based array corruption or inefficient full-state replacements.
Sources: README.md:1-15, AGENTS.md:50-55