Immutability & Structural Sharing
Relevant source files
The following files were used as context for generating this wiki page:
Immutability and structural sharing are core architectural constraints of the keyfold project. The library is designed to fold partial deltas into state trees without ever mutating the base state or the incoming delta. When a merge operation results in no semantic changes, the library returns the original base reference. If only a specific branch of the state tree is updated, all untouched sibling branches retain their original references.
This approach ensures that state updates are deterministic, idempotent, and failure-safe. If an error occurs during the merge (e.g., due to duplicate identities), the process is aborted without leaving behind a partially modified tree.
Sources: AGENTS.md:15-20, README.md:139-147
Core Mechanics
The system achieves immutability by lazily copying objects and arrays only when a change is detected. If the delta restates current values or provides no updates for a specific subtree, the existing reference is preserved.
Lazy Copying & Reference Equality
The foldObject function iterates through delta keys. It only creates a shallow copy of the base object (using copyPlainObject) when it encounters the first field that actually differs from the base value. Reference equality is checked using Object.is.
The diagram above illustrates the lazy copying logic where the original base is only duplicated if a modification is confirmed.
Sources: src/merge.ts:74-114, src/objects.ts:25-39
Structural Sharing in Keyed Lists
During keyed-list reconciliation, the library maintains the order of surviving items from the base list. If a specific item in the list is not mentioned in the delta, the original reference is pushed into the new array. If the entire list reconciliation results in no changes (e.g., all patches restate existing values and no items are added/deleted), the original list reference is returned.
| Scenario | Resulting Reference |
|---|---|
| Delta mentions no items in the list | Returns base list reference |
| Delta patches an item with identical values | Returns base list reference |
| Delta deletes an item | Returns new list reference, sharing surviving item references |
| Delta appends a new item | Returns new list reference, sharing surviving item references |
Sources: src/reconcile.ts:26-64, test/laws.test.ts:104-113
Failure Safety and Determinism
The library guarantees that a throw will not leave a partial write behind. This is achieved by performing validation and indexing before constructing the output tree. For example, in reconcile, the delta is indexed and validated (checking for duplicate IDs) before any result array is populated.
Algebraic Laws
The implementation follows specific algebraic laws verified through property-based testing:
- Value-idempotent:
merge(merge(base, delta), delta)is equal tomerge(base, delta). - Reference-idempotent: Re-applying the same delta returns the exact same reference as the first application.
- Deterministic: Given the same
baseanddelta, the output is always identical.
Sources: test/laws.test.ts:104-135, AGENTS.md:16-18
Implementation Details
copyPlainObject
This utility creates a shallow copy of a plain record while preserving its prototype (e.g., null-prototype objects). It uses Object.defineProperty to ensure property descriptors (like enumerability) are maintained during the copy.
Sources: src/objects.ts:25-39
finishContainerFold
This internal helper determines whether to return the newly constructed "folded" container or fall back to the base. If the mode is "merge" and the folded result is identical to the workingBase (a temporary scaffolding), it returns the original base reference to maximize structural sharing.
Sources: src/merge.ts:121-127
Conclusion
By prioritizing structural sharing, keyfold allows consumers to use simple reference equality checks (===) to detect state changes. This is particularly efficient for UI frameworks or stores that can skip rendering or processing if a subtree's reference has not changed, even after a complex merge operation.
Sources: README.md:143-147, AGENTS.md:19-20