json-patch-to-crdt 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,25 @@ npm install json-patch-to-crdt
30
30
 
31
31
  - Node.js `>= 18` (for package consumers).
32
32
  - TypeScript `^5` when type-checking in your project.
33
- - Bun is optional (used for this repo's own build/test scripts).
33
+ - Bun `1.3.7` is used for this repo's own build/test scripts.
34
+
35
+ ## Testing (Repo)
36
+
37
+ Run all tests:
38
+
39
+ ```bash
40
+ bun run test
41
+ ```
42
+
43
+ Run targeted domain suites:
44
+
45
+ ```bash
46
+ bun run test:state-core
47
+ bun run test:patch-diff-doc
48
+ bun run test:merge-compaction
49
+ bun run test:replica-session
50
+ bun run test:perf-regression
51
+ ```
34
52
 
35
53
  ## Quick Start (Recommended API)
36
54
 
@@ -220,6 +238,7 @@ const fullPatch = crdtToFullReplace(doc);
220
238
  ### Array Delta Strategy
221
239
 
222
240
  By default, arrays are diffed with deterministic LCS edits.
241
+ To prevent pathological `O(n*m)` matrix growth on very large arrays, LCS falls back to atomic array replacement when matrix cells exceed `250_000` by default.
223
242
 
224
243
  If you want atomic array replacement, pass `{ arrayStrategy: "atomic" }`:
225
244
 
@@ -227,10 +246,22 @@ If you want atomic array replacement, pass `{ arrayStrategy: "atomic" }`:
227
246
  const delta = diffJsonPatch(baseJson, nextJson, { arrayStrategy: "atomic" });
228
247
  ```
229
248
 
249
+ If you want to tune the LCS fallback threshold, pass `lcsMaxCells`:
250
+
251
+ ```ts
252
+ const delta = diffJsonPatch(baseJson, nextJson, {
253
+ arrayStrategy: "lcs",
254
+ lcsMaxCells: 500_000,
255
+ });
256
+ ```
257
+
230
258
  Notes:
231
259
 
232
260
  - LCS diffs are deterministic but not necessarily minimal.
233
261
  - Reorders are expressed as remove/add pairs.
262
+ - LCS complexity is `O(n*m)` in time and memory.
263
+ - `lcsMaxCells` sets the matrix cap: `(base.length + 1) * (next.length + 1)`.
264
+ - Set `lcsMaxCells: Number.POSITIVE_INFINITY` to always allow LCS.
234
265
 
235
266
  ## Merging
236
267
 
@@ -264,6 +295,39 @@ Resolution rules:
264
295
  `mergeDoc` is commutative (`merge(a, b)` equals `merge(b, a)`) and idempotent.
265
296
  For `mergeState`, pass the local actor explicitly (or as the first argument) so each peer keeps a stable actor ID.
266
297
 
298
+ ## Tombstone Compaction
299
+
300
+ Long-lived documents can accumulate object/array tombstones.
301
+ You can compact causally-stable tombstones with:
302
+
303
+ ```ts
304
+ import { compactStateTombstones } from "json-patch-to-crdt";
305
+
306
+ const { state: compacted, stats } = compactStateTombstones(state, {
307
+ stable: { A: 120, B: 98, C: 77 },
308
+ });
309
+
310
+ console.log(stats);
311
+ // { objectTombstonesRemoved: number, sequenceTombstonesRemoved: number }
312
+ ```
313
+
314
+ For server-side workflows operating on raw docs, use internals:
315
+
316
+ ```ts
317
+ import { compactDocTombstones } from "json-patch-to-crdt/internals";
318
+
319
+ compactDocTombstones(doc, {
320
+ stable: checkpointVv,
321
+ mutate: true, // optional in-place compaction
322
+ });
323
+ ```
324
+
325
+ Safety conditions:
326
+
327
+ - Only compact at checkpoints that are causally stable across all peers you still merge with.
328
+ - Do not merge compacted replicas with peers that may be behind that checkpoint.
329
+ - Compaction preserves materialized JSON output for the compacted document/state.
330
+
267
331
  ## Serialization
268
332
 
269
333
  ```ts
@@ -389,6 +453,9 @@ Use `crdtToFullReplace(doc)` from `json-patch-to-crdt/internals`, which emits a
389
453
  **Why do array deltas look bigger than expected?**
390
454
  LCS diffs are deterministic, not minimal. If you prefer one-op array replacement, use `{ arrayStrategy: "atomic" }`.
391
455
 
456
+ **Why did my array delta become a full `replace` even with LCS?**
457
+ For scalability, LCS falls back to atomic replacement when arrays exceed the `lcsMaxCells` guardrail (default `250_000` matrix cells). Increase `lcsMaxCells` to allow larger LCS runs.
458
+
392
459
  **Does LCS guarantee the smallest patch?**
393
460
  No. It is deterministic and usually compact, but not guaranteed to be minimal.
394
461
 
@@ -401,6 +468,9 @@ By default, `forkState` blocks reusing `origin.clock.actor` because same-actor f
401
468
  **Why can my local counter jump after a merge?**
402
469
  Array inserts that target an existing predecessor may need to outrank sibling insert dots for deterministic ordering. The library can fast-forward the local counter in constant time to avoid expensive loops, but the resulting counter value may still jump upward when merging with peers that already have high counters.
403
470
 
471
+ **How should I run tombstone compaction in production?**
472
+ Treat compaction as a maintenance step after a causal-stability checkpoint (for example, after all replicas acknowledge processing through a specific version vector), then compact and persist the compacted snapshot.
473
+
404
474
  ## Limitations
405
475
 
406
476
  - The array materialization and insert mapping depend on a base snapshot; concurrent inserts resolve by dot order.