first commit
This commit is contained in:
138
docs/foundation/data/diff/src/utils/utils.md
Normal file
138
docs/foundation/data/diff/src/utils/utils.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# utils
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`utils.ts` provides the low-level primitives that the entire `@suaveplan/diff` pipeline is built upon: deep clone, deep equality, FNV-1a hashing, and a family of path-navigation helpers for immutable and mutable document manipulation. Every operation is JSON-safe — it operates exclusively on the `JsonValue` universe (`null | boolean | number | string | JsonValue[] | { [k: string]: JsonValue }`).
|
||||
|
||||
The path-navigation helpers implement the RFC 6902 semantics for `add`, `remove`, `replace`, `move`, and `copy` at the document level, freeing the higher-level patch engine from the mechanics of array splice and object key management.
|
||||
|
||||
## Features
|
||||
|
||||
- **`deepClone`** — recursive JSON-safe clone without `JSON.parse/stringify` overhead
|
||||
- **`deepEqual`** — structural equality for arbitrarily nested JSON values
|
||||
- **`hashValue`** — FNV-1a 32-bit hash of JSON serialisation (for LCS bucketing)
|
||||
- **`isObject` / `isPrimitive`** — type-guard predicates
|
||||
- **`getValueAtPath`** — safe read at a path; returns `undefined` on missing keys
|
||||
- **`setValueAtPath` / `setValueAtPathMut`** — immutable and in-place write
|
||||
- **`insertValueAtPath` / `insertValueAtPathMut`** — immutable and in-place insert (array splice)
|
||||
- **`deleteValueAtPath` / `deleteValueAtPathMut`** — immutable and in-place delete
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { deepClone, deepEqual, getValueAtPath } from "@suaveplan/diff/utils";
|
||||
import type { JsonValue } from "@suaveplan/diff/utils";
|
||||
|
||||
const doc: JsonValue = { users: [{ name: "Alice" }, { name: "Bob" }] };
|
||||
|
||||
const clone = deepClone(doc);
|
||||
clone.users[0].name = "Carol";
|
||||
console.log(doc.users[0].name); // "Alice" — original unchanged
|
||||
|
||||
deepEqual(doc, clone); // false — they now differ
|
||||
|
||||
const name = getValueAtPath(doc, ["users", 1, "name"]);
|
||||
// "Bob"
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
setValueAtPath, insertValueAtPath, deleteValueAtPath,
|
||||
setValueAtPathMut, hashValue,
|
||||
} from "@suaveplan/diff/utils";
|
||||
import type { JsonValue } from "@suaveplan/diff/utils";
|
||||
|
||||
const doc: JsonValue = { items: ["a", "b", "c"] };
|
||||
|
||||
// Immutable set — original unchanged
|
||||
const updated = setValueAtPath(doc, ["items", 1], "B");
|
||||
// doc still: { items: ["a", "b", "c"] }
|
||||
// updated: { items: ["a", "B", "c"] }
|
||||
|
||||
// Array insert — existing elements shift right
|
||||
const inserted = insertValueAtPath(doc, ["items", 1], "X");
|
||||
// { items: ["a", "X", "b", "c"] }
|
||||
|
||||
// Append via "-" token
|
||||
const appended = insertValueAtPath(doc, ["items", "-"], "Z");
|
||||
// { items: ["a", "b", "c", "Z"] }
|
||||
|
||||
// Delete
|
||||
const deleted = deleteValueAtPath(doc, ["items", 0]);
|
||||
// { items: ["b", "c"] }
|
||||
|
||||
// In-place mutation (used internally by the patch engine)
|
||||
const mutable = deepClone(doc) as { items: string[] };
|
||||
setValueAtPathMut(mutable as JsonValue, ["items", 2], "C");
|
||||
|
||||
// Hash for LCS bucketing
|
||||
hashValue({ x: 1 }); // e.g. "3a2b1c4d"
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `JsonValue`
|
||||
|
||||
```ts
|
||||
type JsonValue = null | boolean | number | string | JsonValue[] | { [k: string]: JsonValue };
|
||||
```
|
||||
|
||||
### `isObject(v: unknown): v is Record<string, unknown>`
|
||||
|
||||
Returns `true` for non-null, non-array plain objects.
|
||||
|
||||
### `isPrimitive(v: unknown): v is null | boolean | number | string`
|
||||
|
||||
Returns `true` for all primitive JSON value types.
|
||||
|
||||
### `deepClone<T>(value: T): T`
|
||||
|
||||
Recursively clones arrays and objects; returns primitives as-is. Does not use `JSON.parse/stringify`.
|
||||
|
||||
### `deepEqual(a: unknown, b: unknown): boolean`
|
||||
|
||||
Structural equality: handles `null`, same-type primitives, same-length arrays with equal elements, and objects with equal key sets and equal values. Returns `false` for type mismatches.
|
||||
|
||||
### `hashValue(value: unknown): string`
|
||||
|
||||
FNV-1a 32-bit hash of `JSON.stringify(value)`. Returns an 8-character lowercase hex string.
|
||||
|
||||
### `getValueAtPath(doc: JsonValue, path: Path): JsonValue | undefined`
|
||||
|
||||
Traverses `doc` following `path`. Returns `undefined` if any step fails (missing key, index out of range, traversing through a primitive).
|
||||
|
||||
### `setValueAtPath(doc: JsonValue, path: Path, value: JsonValue): JsonValue`
|
||||
|
||||
Returns a new deep clone of `doc` with `value` written at `path`. The original `doc` is not modified.
|
||||
|
||||
### `setValueAtPathMut(doc: JsonValue, path: Path, value: JsonValue): void`
|
||||
|
||||
Sets `value` at `path` in `doc` in-place.
|
||||
|
||||
### `insertValueAtPath(doc: JsonValue, path: Path, value: JsonValue): JsonValue`
|
||||
|
||||
Returns a new deep clone with `value` inserted. For arrays, elements at `>= index` shift right. The `"-"` segment appends to the array.
|
||||
|
||||
### `insertValueAtPathMut(doc: JsonValue, path: Path, value: JsonValue): void`
|
||||
|
||||
Inserts `value` in-place using array splice or object key assignment.
|
||||
|
||||
### `deleteValueAtPath(doc: JsonValue, path: Path): JsonValue`
|
||||
|
||||
Returns a new deep clone with the element at `path` removed. Array elements shift left.
|
||||
|
||||
### `deleteValueAtPathMut(doc: JsonValue, path: Path): void`
|
||||
|
||||
Removes the element at `path` in-place.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `deepClone` avoids `JSON.parse/stringify` to preserve type fidelity for values that would be mangled by JSON roundtrip (e.g., `undefined` in arrays becomes `null`). This also avoids the overhead of two serialisation passes.
|
||||
- `resolveParent` is a shared internal helper that walks to the parent of the target key, returning a `{ parent, key }` pair. This eliminates duplication between the six set/insert/delete variants.
|
||||
- The `"-"` append token is handled in both `setValueAtPathMut` and `insertValueAtPathMut` to match RFC 6902 §4.1 semantics for the `add` operation.
|
||||
- `hashValue` deliberately uses FNV-1a rather than a cryptographic hash for performance in the LCS hot path. Collision probability over realistic array sizes is negligible.
|
||||
Reference in New Issue
Block a user