# patch _(no submodules)_ ## Purpose `patch.ts` re-exports the RFC 6902 JSON Patch primitives provided by `@suaveplan/diff`, narrowed to the `VaultItem` domain. It exists so the sync engine and the offline queue can compute and apply small, structural deltas instead of shipping whole items across the wire — saving bandwidth on the most common case (a single field change) and making conflict detection precise per-path. Every applied patch is re-validated against `VaultItemSchema` so a malformed delta cannot poison local state. ## Features - `diffVaultItems(before, after)` — minimal JSON Patch via `@suaveplan/diff` - `applyPatch(item, patch)` — applies and re-validates against `VaultItemSchema` - Empty diff for deeply-equal inputs (no spurious operations) - Structured `VaultSyncError(PATCH.INVALID)` for bad ops and shape failures - OTel histogram `vault_sync.patch.apply.duration_ms` per apply call ## Usage: Basic Example ```ts import { applyPatch, diffVaultItems } from "@suaveplan/vault-sync"; const before = { id: "i1", title: "Old", password: "p", folderId: null, history: [], nodeId: "a", updatedAt: 1, clock: { a: 1 }, }; const after = { ...before, title: "New", updatedAt: 2 }; const patch = diffVaultItems(before, after); console.log(patch); // [{ op: "replace", path: ["title"], value: "New" }, // { op: "replace", path: ["updatedAt"], value: 2 }] const applied = applyPatch(before, patch); console.log(applied.title); // "New" ``` ## Usage: Advanced Example ```ts import { applyPatch, diffVaultItems } from "@suaveplan/vault-sync"; import { VaultSyncError } from "@suaveplan/vault-sync"; import { VAULT_SYNC_CODES } from "@suaveplan/vault-sync/errors"; import type { VaultItem } from "@suaveplan/vault-sync/types"; interface Mutation { itemId: string; patch: ReturnType; } function recordMutation(before: VaultItem, after: VaultItem): Mutation { return { itemId: before.id, patch: diffVaultItems(before, after) }; } function safeApply(item: VaultItem, mutation: Mutation): VaultItem { try { return applyPatch(item, mutation.patch); } catch (err) { if ( err instanceof VaultSyncError && err.code === VAULT_SYNC_CODES["PATCH.INVALID"] ) { // Skip this mutation; the queue will retry on the next pull. return item; } throw err; } } ``` ## API Reference - `diffVaultItems(before: VaultItem, after: VaultItem): JsonPatch` — returns an RFC 6902 patch (`Operation[]`). Empty array when `before` deeply equals `after`. Internally serialises both via `JSON.parse(JSON.stringify(...))` to coerce undefined-valued fields to canonical absent form. - `applyPatch(item: VaultItem, patch: JsonPatch): VaultItem` — applies the patch and parses the result through `VaultItemSchema`. Throws `VaultSyncError(PATCH.INVALID)` for: structurally-invalid patches (`reason: "structural"`), failures during apply (`reason: "apply"`, e.g. missing path, out-of-bounds array index), and post-apply shape failures (`reason: "shape"`). - `JsonPatch` — re-exported from `@suaveplan/diff` for downstream consumers that want one import path for the whole patch surface. ## Implementation Notes The package leans on `@suaveplan/diff` rather than re-implementing patch math. The thin wrapper here is responsible for: (a) erasing `@suaveplan/diff`'s richer error types into `VaultSyncError`, (b) re-running `VaultItemSchema` after each apply, and (c) recording the single OTel duration histogram. Re-validating after apply is paranoid but cheap — the schema run is microseconds — and it is the only line of defence between a malformed remote patch and corrupt local state. The `asJson` helper round-trips through JSON to drop `undefined` values, which is consistent with what the sync wire format does anyway.