Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
# patch
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.