first commit
This commit is contained in:
108
docs/security-stack/security/vault-sync/src/patch/patch.md
Normal file
108
docs/security-stack/security/vault-sync/src/patch/patch.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# patch
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## 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<typeof diffVaultItems>;
|
||||
}
|
||||
|
||||
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.
|
||||
Reference in New Issue
Block a user