4.7 KiB
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
DiffErrorat the first error - Lenient mode —
failFast: falsecollects errors without throwing - OTel integration — emits
diff.applyspan withoutcome,duration_ms,applied_countattributes 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
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
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
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
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— atestoperation value mismatchDIFF.APPLY_FAILED—move/copysource 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
applyOperationmutates itsdocargument in-place for performance, butapplyPatchalways 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
_tracerafter first resolution. - The
_tracermodule-level variable is reset toundefinedon module load, which is correct for test isolation — each test process gets a fresh module. - The
revertPatchfunction delegates toinvertPatchfromoperations.ts, which steps through intermediate document states to produce accurate inverses forremoveandreplaceoperations.