/** * RFC 6902 JSON Patch — compute + apply. * * This module is the "delta history" primitive: instead of * snapshotting the full record on every put (the behavior), * `Collection.put` computes a JSON Patch from the previous version to * the new version and stores only the patch in the ledger. To * reconstruct version N, we walk from the genesis snapshot forward * applying patches. Storage scales with **edit size**, not record * size — a 10 KB record edited 1000 times costs ~10 KB of deltas * instead of ~10 MB of snapshots. * * ## Why hand-roll instead of using a library? * * RFC 6902 has good libraries (`fast-json-patch`, `rfc6902`) but every * single one of them adds a runtime dependency to `@noy-db/core`. The * "zero runtime dependencies" promise is one of the core's load-bearing * features, and the patch surface we actually need is small enough * (~150 LoC) that vendoring is the right call. * * What we implement: * - `add` — insert a value at a path * - `remove` — delete the value at a path * - `replace` — overwrite the value at a path * * What we deliberately skip (out of scope for the ledger use): * - `move` and `copy` — optimizations; the diff algorithm doesn't * emit them, so the apply path doesn't need them * - `test` — used for transactional patches; we already have * optimistic concurrency via `_v` at the envelope layer * - Sophisticated array diffing (LCS, edit distance) — we treat * arrays as atomic values and emit a single `replace` op when * they differ. The accounting domain has small arrays where this * is fine; if we ever need patch-level array diffing we can add * it without changing the storage format. * * ## Path encoding (RFC 6902 §3) * * Paths look like `/foo/bar/0`. Each path segment is either an object * key or a numeric array index. Two characters need escaping inside * keys: `~` becomes `~0` and `/` becomes `~1`. We implement both. * * Empty path (`""`) refers to the root document. Only `replace` makes * sense at the root, and our diff function emits it as a top-level * `replace` when `prev` and `next` differ in shape (object vs array, * primitive vs object, etc.). */ /** A single JSON Patch operation. Subset of RFC 6902 — see file docstring. */ export type JsonPatchOp = { readonly op: 'add'; readonly path: string; readonly value: unknown; } | { readonly op: 'remove'; readonly path: string; } | { readonly op: 'replace'; readonly path: string; readonly value: unknown; }; /** A complete JSON Patch document — an array of operations. */ export type JsonPatch = readonly JsonPatchOp[]; /** * Compute a JSON Patch that, when applied to `prev`, produces `next`. * * The algorithm is a straightforward recursive object walk: * * 1. If both inputs are plain objects (and not arrays/null): * - For each key in `prev`, recurse if `next` has it, else emit `remove` * - For each key in `next` not in `prev`, emit `add` * 2. If both inputs are arrays AND structurally equal, no-op. * Otherwise emit a single `replace` for the whole array. * 3. If both inputs are deeply equal primitives, no-op. * 4. Otherwise emit a `replace` at the current path. * * We do not minimize patches across move-like rearrangements — every * generated patch is straightforward enough to apply by hand if you * had to debug it. */ export declare function computePatch(prev: unknown, next: unknown): JsonPatch; /** * Apply a JSON Patch to a base document and return the result. * * The base document is **not mutated** — every op clones the parent * container before writing to it, so the caller's reference to `base` * stays untouched. This costs an extra allocation per op but makes * the apply pipeline reorderable and safe to interrupt. * * Throws on: * - Removing a path that doesn't exist * - Adding to a path whose parent doesn't exist * - A path component that doesn't match the document shape (e.g., * trying to step into a primitive) * * Throwing is the right behavior for the ledger use case: a failed * apply means the chain is corrupted, which should be loud rather * than silently producing a wrong reconstruction. */ export declare function applyPatch(base: T, patch: JsonPatch): T;