34 lines
3.1 KiB
Markdown
34 lines
3.1 KiB
Markdown
# Capability Spec: Diff
|
|
|
|
**Package**: `@suaveplan/diff`
|
|
**Version**: 0.0.0 (genesis)
|
|
**Status**: ADDED
|
|
|
|
---
|
|
|
|
## ADDED Requirements
|
|
|
|
### DIFF-01 — RFC 6902 JSON Patch operations
|
|
The package MUST implement all six RFC 6902 operation types (`add`, `remove`, `replace`, `move`, `copy`, `test`) with typed factories `createAdd`/`createRemove`/`createReplace`/`createMove`/`createCopy`/`createTest`. Operations MUST be validated via `validateOperation`; invalid shapes MUST throw `DiffError` with code `DIFF.INVALID_PATCH`.
|
|
|
|
### DIFF-02 — Structural diff round-trip
|
|
`diff(a, b, options?)` MUST produce a `Patch` such that `applyPatch(a, diff(a, b)).doc` is structurally equal (`deepEqual`) to `b` for any JSON-serializable pair. `DiffOptions.arrayStrategy` MUST accept `'index'` (positional replacement) or `'lcs'` (longest-common-subsequence minimal edits).
|
|
|
|
### DIFF-03 — Apply / revert contract
|
|
`applyPatch(doc, patch, options?)` MUST return `PatchResult { doc, applied, errors }` where `applied` is the count of successfully applied operations. When a `test` operation fails, the patch MUST abort and throw `DiffError` with code `DIFF.TEST_FAILED`, leaving `doc` unchanged. `revertPatch(doc, patch)` MUST be implemented such that `revertPatch(applyPatch(doc, p).doc, p)` equals `doc` for any patch that does not contain `copy` or `move` operations affecting removed data.
|
|
|
|
### DIFF-04 — Patch inversion
|
|
`invertPatch(patch)` MUST produce a patch that, when applied to the result of the original, yields the original document. `invertOperation(op)` MUST satisfy `invertOperation(invertOperation(op))` deep-equals `op` for all operation types. Operations that cannot be losslessly inverted (`remove` without prior value capture) MUST throw `DiffError` with code `DIFF.INVERT_FAILED`.
|
|
|
|
### DIFF-05 — Path utilities
|
|
`parsePath` and `formatPath` MUST implement RFC 6901 JSON Pointer grammar. `parsePath(formatPath(p))` MUST deep-equal `p` for every valid `Path`. Escape/unescape MUST handle `~0` (`~`) and `~1` (`/`). `isPrefix`, `pathEquals`, `parentPath`, `lastSegment` MUST be pure functions with zero allocation on the hot path where possible.
|
|
|
|
### DIFF-06 — History manager
|
|
`History` MUST implement bounded linear undo/redo with branch truncation: pushing a new entry after an undo MUST discard all redo entries. Size limits MUST be configurable via `HistoryOptions.maxEntries`. `HistoryState` MUST be serializable via `JSON.stringify`. Listeners MUST be notified on every state transition.
|
|
|
|
### DIFF-07 — Sync manager
|
|
`SyncManager` MUST accept local deltas via `push` and remote deltas via `pull`, emitting `SyncDelta` objects to registered listeners. Conflicts MUST be detected via `operationsConflict` and resolved per the configured `SyncOptions.conflictResolution` strategy (`'local-wins' | 'remote-wins' | 'reject'`).
|
|
|
|
### DIFF-08 — Observability
|
|
Every `applyPatch` call MUST emit OTel counters `diff.apply.total` (labels: `outcome: 'success' | 'failure'`) and histogram `diff.apply.duration_ms`. Every `diff` call MUST emit `diff.diff.duration_ms`. Meters MUST use `@opentelemetry/api` and be no-ops when no exporter is registered.
|