# patch _(no submodules)_ ## Purpose `patch.ts` is the RFC 6902 patch application engine for `@suaveplan/diff`. It applies a sequence of operations to a document, producing a `PatchResult` with the new document state, a count of applied operations, and any error messages encountered. The engine supports both fail-fast (default) and lenient (collect-errors) modes, atomic rollback, and emits OpenTelemetry spans for observability. The `revertPatch` helper provides a convenient undo primitive: given the post-patch document, the original patch, and the pre-patch snapshot, it computes the inverse patch and applies it. ## Features - **`applyPatch`** — applies all six RFC 6902 operation types to a document copy - **Atomic mode** (default) — rolls back to the original on any failure - **Fail-fast mode** (default) — throws `DiffError` at the first error - **Lenient mode** — `failFast: false` collects errors without throwing - **OTel integration** — emits `diff.apply` span with `outcome`, `duration_ms`, `applied_count` attributes - **`revertPatch`** — undo a patch using the pre-patch document snapshot - All mutations operate on a deep clone; the original document is never modified ## Usage: Basic Example ```ts import { applyPatch } from "@suaveplan/diff/patch"; import type { JsonValue } from "@suaveplan/diff/utils"; const doc: JsonValue = { name: "Alice", age: 30 }; const { doc: updated, applied, errors } = applyPatch(doc, [ { op: "replace", path: ["age"], value: 31 }, { op: "add", path: ["city"], value: "NYC" }, ]); // updated: { name: "Alice", age: 31, city: "NYC" } // applied: 2, errors: [] ``` ## Usage: Advanced Example ```ts import { applyPatch, revertPatch } from "@suaveplan/diff/patch"; import { diff } from "@suaveplan/diff/diff"; import type { JsonValue } from "@suaveplan/diff/utils"; const before: JsonValue = { items: [1, 2, 3], meta: { v: 1 } }; const after: JsonValue = { items: [1, 3], meta: { v: 2 } }; // Compute and apply patch const patch = diff(before, after); const { doc: patched } = applyPatch(before, patch); // Undo — requires the original doc const reverted = revertPatch(patched, patch, before); // reverted deepEquals before ✓ // Test operation — throws DiffError on mismatch (atomic rollback) try { applyPatch(before, [ { op: "test", path: ["meta", "v"], value: 999 }, // wrong value { op: "replace", path: ["meta", "v"], value: 2 }, ]); } catch (e) { // e.code === "DIFF.TEST_FAILED" // before is unchanged (applyPatch never mutates its input) } // Lenient mode — collect all errors, apply partial patch const result = applyPatch(before, [ { op: "remove", path: ["missing"] }, { op: "replace", path: ["meta", "v"], value: 5 }, ], { failFast: false, atomic: false }); // result.errors contains the remove error; replace still applied ``` ## API Reference ### `PatchResult` ```ts interface PatchResult { doc: JsonValue; // Document state after applying the patch applied: number; // Count of successfully applied operations errors: string[]; // Error messages (empty on full success) } ``` ### `PatchOptions` ```ts interface PatchOptions { failFast?: boolean; // default true — throw on first error atomic?: boolean; // default true — rollback on failure } ``` ### `applyPatch(doc, patch, options?): PatchResult` Applies `patch` to `doc`. Never mutates `doc`. With `atomic: true` (default), any failure rolls back to the original clone. With `failFast: true` (default), throws a `DiffError` on the first failure. Returns `PatchResult`. **Throws** `DiffError` with codes: - `DIFF.TEST_FAILED` — a `test` operation value mismatch - `DIFF.APPLY_FAILED` — `move`/`copy` source path not found, or generic apply failure ### `revertPatch(doc, patch, originalDoc?): JsonValue` Inverts `patch` (using `originalDoc` as the pre-patch snapshot) and applies the inverse to `doc`. Returns the reverted document. If `originalDoc` is omitted, falls back to `doc` (only correct for add-only patches). ## Implementation Notes - `applyOperation` mutates its `doc` argument in-place for performance, but `applyPatch` always passes a deep clone so the caller's original is never affected. - OTel integration uses a lazy require so packages that do not configure an OTel provider pay zero cost. The tracer is memoised in `_tracer` after first resolution. - The `_tracer` module-level variable is reset to `undefined` on module load, which is correct for test isolation — each test process gets a fresh module. - The `revertPatch` function delegates to `invertPatch` from `operations.ts`, which steps through intermediate document states to produce accurate inverses for `remove` and `replace` operations.