TypeScript Delta Contract
Relevant source files
The following files were used as context for generating this wiki page:
The TypeScript Delta Contract in keyfold defines the static type system used to represent partial updates to a state tree. It ensures that deltas are structurally compatible with the base state while providing mechanisms for field deletion and keyed-item reconciliation. The contract distinguishes between recursive mergeable objects, atomic values that must be replaced wholesale, and special operators like DELETE.
This contract serves as the compile-time guardrail for the createMerger API, preventing operations that would violate the integrity of the state tree, such as deleting required properties. It also establishes the boundaries between structural data and library-interpreted operators.
Sources: src/types.ts:24-34, README.md:129-138
The Delta Type
The Delta<T, Atomic> type is a recursive conditional type that transforms a base state type T into its corresponding partial representation. It navigates through objects, arrays, and primitive types, applying specific rules for each.
The diagram above illustrates how the Delta type recursively evaluates properties to determine if they can be partially updated or if they require the special DELETE operator.
Sources: src/types.ts:35-47, test/laws.test.ts:74-80
Structural Rules
| Type Category | Delta Behavior | Source Citation |
|---|---|---|
| Plain Objects | Recursive partial update; fields can be undefined (ignored) or DELETE. | src/types.ts:54-59 |
| Keyed Lists | Array items admit a { $delete: true } property for reconciliation. | src/types.ts:61-68 |
| Atomic Types | User-defined types (e.g., Classes) that must be supplied in full. | src/types.ts:35-37 |
| Opaque Built-ins | Types like Date, RegExp, and Promise that are never recursed. | src/types.ts:71-83 |
| Scalars | Primitives (strings, numbers, booleans) replace wholesale. | src/types.ts:47 |
Atomic and Opaque Values
The system identifies certain types as "opaque," meaning the merger will not attempt to recurse into them. Instead, they are treated as leaf nodes in the state tree and are replaced wholesale.
Built-in Opaque Types
The following types are automatically treated as opaque:
Date,RegExp,PromiseMap,Set,WeakMap,WeakSetArrayBuffer,ArrayBufferView- Functions
Sources: src/types.ts:71-83
The Atomic Parameter
Applications can extend this behavior using the Atomic type parameter in createMerger. This is necessary because TypeScript cannot always distinguish between a class instance (intended to be atomic) and a plain object (intended to be merged).
Sources: README.md:104-127, test/types.ts:54-62
// src/types.ts:35-37
export type Delta<T, Atomic = never> = T extends Atomic
? T
: // ... recursive logic
Deletion Operators
The contract defines specific mechanisms for removing data from the state tree. These are interpreted at specific positions during the merge process.
Field Deletion (DELETE)
The DELETE symbol is used to remove optional fields from objects. The type system prevents the use of DELETE on required fields to maintain the integrity of the return type T.
Sources: src/types.ts:57-58, test/types.ts:34-40
// test/types.ts:34-40
const requiredDelete: Delta<Shape> = {
// @ts-expect-error required properties cannot be deleted
required: DELETE,
};
Item Tombstones ($delete)
Inside keyed lists, an item is removed if it contains the $delete: true property. The type system allows this on any object that is an element of an array within the delta.
Sources: src/types.ts:61-68, README.md:144-146
Wire Deletes (DELETE_TOKEN)
For JSON-serialized deltas where symbols cannot be used, the wireDeletes option enables the interpretation of the string @@keyfold/delete as a field delete.
Sources: src/sentinels.ts:4-7, README.md:148-156
Contract Constraints
The contract imposes several constraints that ensure safety and predictability:
- Required Property Protection: A required property in
Tremains required in the output; therefore, theDeltatype does not permitDELETEfor these keys.
Sources: src/types.ts:58, test/types.ts:42-46 - Immutability and Structural Sharing: If a delta results in no changes, the contract (at runtime) ensures the
basereference is returned. Sibling branches that are untouched retain their original references.
Sources: AGENTS.md:21-22, test/laws.test.ts:133-144 - Idempotency: Re-applying a valid delta to its result must return the same reference.
Sources: test/laws.test.ts:133-144, README.md:204-206
Summary of Type Mapping
Base Type T | Delta<T> Representation |
|---|---|
string | number | boolean | T (Self) |
T[] | DeltaItem<T>[] (Items allow $delete) |
Optional?: string | string | undefined | DELETE |
Required: string | string | undefined (DELETE is prohibited) |
Atomic Class | T (Must be full instance) |
| Sources: src/types.ts:35-68, test/types.ts:18-62 |