json-patch-to-crdt 0.1.2 → 0.2.0

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.
@@ -1,11 +1,34 @@
1
1
 
2
2
  //#region src/clock.ts
3
+ var ClockValidationError = class extends TypeError {
4
+ reason;
5
+ constructor(reason, message) {
6
+ super(message);
7
+ this.name = "ClockValidationError";
8
+ this.reason = reason;
9
+ }
10
+ };
11
+ function readVvCounter$1(vv, actor) {
12
+ if (!Object.prototype.hasOwnProperty.call(vv, actor)) return 0;
13
+ const counter = vv[actor];
14
+ return typeof counter === "number" ? counter : 0;
15
+ }
16
+ function writeVvCounter$1(vv, actor, counter) {
17
+ Object.defineProperty(vv, actor, {
18
+ configurable: true,
19
+ enumerable: true,
20
+ value: counter,
21
+ writable: true
22
+ });
23
+ }
3
24
  /**
4
25
  * Create a new clock for the given actor. Each call to `clock.next()` yields a fresh `Dot`.
5
26
  * @param actor - Unique identifier for this peer.
6
27
  * @param start - Initial counter value (defaults to 0).
7
28
  */
8
29
  function createClock(actor, start = 0) {
30
+ assertActorId(actor);
31
+ assertCounter(start);
9
32
  const clock = {
10
33
  actor,
11
34
  ctr: start,
@@ -19,6 +42,12 @@ function createClock(actor, start = 0) {
19
42
  };
20
43
  return clock;
21
44
  }
45
+ function assertActorId(actor) {
46
+ if (actor.length === 0) throw new ClockValidationError("INVALID_ACTOR", "actor must not be empty");
47
+ }
48
+ function assertCounter(counter) {
49
+ if (!Number.isSafeInteger(counter) || counter < 0) throw new ClockValidationError("INVALID_COUNTER", "counter must be a non-negative safe integer");
50
+ }
22
51
  /** Create an independent copy of a clock at the same counter position. */
23
52
  function cloneClock(clock) {
24
53
  return createClock(clock.actor, clock.ctr);
@@ -28,8 +57,8 @@ function cloneClock(clock) {
28
57
  * Useful when a server needs to mint dots for many actors.
29
58
  */
30
59
  function nextDotForActor(vv, actor) {
31
- const ctr = (vv[actor] ?? 0) + 1;
32
- vv[actor] = ctr;
60
+ const ctr = readVvCounter$1(vv, actor) + 1;
61
+ writeVvCounter$1(vv, actor, ctr);
33
62
  return {
34
63
  actor,
35
64
  ctr
@@ -37,7 +66,7 @@ function nextDotForActor(vv, actor) {
37
66
  }
38
67
  /** Record an observed dot in a version vector. */
39
68
  function observeDot(vv, dot) {
40
- if ((vv[dot.actor] ?? 0) < dot.ctr) vv[dot.actor] = dot.ctr;
69
+ if (readVvCounter$1(vv, dot.actor) < dot.ctr) writeVvCounter$1(vv, dot.actor, dot.ctr);
41
70
  }
42
71
 
43
72
  //#endregion
@@ -69,16 +98,30 @@ function toDepthApplyError(error) {
69
98
 
70
99
  //#endregion
71
100
  //#region src/dot.ts
101
+ function readVvCounter(vv, actor) {
102
+ if (!Object.prototype.hasOwnProperty.call(vv, actor)) return;
103
+ const counter = vv[actor];
104
+ return typeof counter === "number" ? counter : void 0;
105
+ }
106
+ function writeVvCounter(vv, actor, counter) {
107
+ Object.defineProperty(vv, actor, {
108
+ configurable: true,
109
+ enumerable: true,
110
+ value: counter,
111
+ writable: true
112
+ });
113
+ }
72
114
  function compareDot(a, b) {
73
115
  if (a.ctr !== b.ctr) return a.ctr - b.ctr;
74
116
  return a.actor < b.actor ? -1 : a.actor > b.actor ? 1 : 0;
75
117
  }
76
118
  function vvHasDot(vv, d) {
77
- return (vv[d.actor] ?? 0) >= d.ctr;
119
+ return (readVvCounter(vv, d.actor) ?? 0) >= d.ctr;
78
120
  }
79
121
  function vvMerge(a, b) {
80
- const out = { ...a };
81
- for (const [actor, ctr] of Object.entries(b)) out[actor] = Math.max(out[actor] ?? 0, ctr);
122
+ const out = Object.create(null);
123
+ for (const [actor, ctr] of Object.entries(a)) writeVvCounter(out, actor, ctr);
124
+ for (const [actor, ctr] of Object.entries(b)) writeVvCounter(out, actor, Math.max(readVvCounter(out, actor) ?? 0, ctr));
82
125
  return out;
83
126
  }
84
127
  function dotToElemId(d) {
@@ -106,37 +149,43 @@ function rgaChildrenIndex(seq) {
106
149
  for (const arr of idx.values()) arr.sort((a, b) => compareDot(b.insDot, a.insDot));
107
150
  return idx;
108
151
  }
109
- function rgaLinearizeIds(seq) {
110
- const ver = getVersion(seq);
111
- const cached = linearCache.get(seq);
112
- if (cached && cached.version === ver) return cached.ids;
152
+ function rgaCreateLinearCursor(seq) {
113
153
  const idx = rgaChildrenIndex(seq);
114
- const out = [];
115
154
  const stack = [];
116
155
  const rootChildren = idx.get(HEAD);
117
156
  if (rootChildren) stack.push({
118
157
  children: rootChildren,
119
158
  index: 0
120
159
  });
121
- while (stack.length > 0) {
122
- const frame = stack[stack.length - 1];
123
- if (frame.index >= frame.children.length) {
124
- stack.pop();
125
- continue;
160
+ return { next() {
161
+ while (stack.length > 0) {
162
+ const frame = stack[stack.length - 1];
163
+ if (frame.index >= frame.children.length) {
164
+ stack.pop();
165
+ continue;
166
+ }
167
+ const child = frame.children[frame.index++];
168
+ const grandchildren = idx.get(child.id);
169
+ if (grandchildren) stack.push({
170
+ children: grandchildren,
171
+ index: 0
172
+ });
173
+ if (!child.tombstone) return child;
126
174
  }
127
- const child = frame.children[frame.index++];
128
- if (!child.tombstone) out.push(child.id);
129
- const grandchildren = idx.get(child.id);
130
- if (grandchildren) stack.push({
131
- children: grandchildren,
132
- index: 0
133
- });
134
- }
175
+ } };
176
+ }
177
+ function rgaLinearizeIds(seq) {
178
+ const ver = getVersion(seq);
179
+ const cached = linearCache.get(seq);
180
+ if (cached && cached.version === ver) return [...cached.ids];
181
+ const out = [];
182
+ const cursor = rgaCreateLinearCursor(seq);
183
+ for (let child = cursor.next(); child; child = cursor.next()) out.push(child.id);
135
184
  linearCache.set(seq, {
136
185
  version: ver,
137
186
  ids: out
138
187
  });
139
- return out;
188
+ return [...out];
140
189
  }
141
190
  function rgaInsertAfter(seq, prev, id, insDot, value) {
142
191
  if (seq.elems.has(id)) return;
@@ -222,70 +271,77 @@ function rgaPrevForInsertAtIndex(seq, index) {
222
271
 
223
272
  //#endregion
224
273
  //#region src/materialize.ts
274
+ function createMaterializedObject() {
275
+ return Object.create(null);
276
+ }
277
+ function setMaterializedProperty(out, key, value) {
278
+ Object.defineProperty(out, key, {
279
+ configurable: true,
280
+ enumerable: true,
281
+ value,
282
+ writable: true
283
+ });
284
+ }
225
285
  /** Convert a CRDT node graph into a plain JSON value using an explicit stack. */
226
286
  function materialize(node) {
227
287
  if (node.kind === "lww") return node.value;
228
- const root = node.kind === "obj" ? {} : [];
288
+ const root = node.kind === "obj" ? createMaterializedObject() : [];
229
289
  const stack = [];
230
290
  if (node.kind === "obj") stack.push({
231
291
  kind: "obj",
232
292
  depth: 0,
233
- entries: Array.from(node.entries.entries(), ([key, value]) => [key, value.node]),
234
- index: 0,
293
+ entries: node.entries.entries(),
235
294
  out: root
236
295
  });
237
296
  else stack.push({
238
297
  kind: "seq",
239
298
  depth: 0,
240
- ids: rgaLinearizeIds(node),
241
- index: 0,
242
- seq: node,
299
+ cursor: rgaCreateLinearCursor(node),
243
300
  out: root
244
301
  });
245
302
  while (stack.length > 0) {
246
303
  const frame = stack[stack.length - 1];
247
304
  if (frame.kind === "obj") {
248
- if (frame.index >= frame.entries.length) {
305
+ const nextEntry = frame.entries.next();
306
+ if (nextEntry.done) {
249
307
  stack.pop();
250
308
  continue;
251
309
  }
252
- const [key, child] = frame.entries[frame.index++];
310
+ const [key, entry] = nextEntry.value;
311
+ const child = entry.node;
253
312
  const childDepth = frame.depth + 1;
254
313
  assertTraversalDepth(childDepth);
255
314
  if (child.kind === "lww") {
256
- frame.out[key] = child.value;
315
+ setMaterializedProperty(frame.out, key, child.value);
257
316
  continue;
258
317
  }
259
318
  if (child.kind === "obj") {
260
- const outObj = {};
261
- frame.out[key] = outObj;
319
+ const outObj = createMaterializedObject();
320
+ setMaterializedProperty(frame.out, key, outObj);
262
321
  stack.push({
263
322
  kind: "obj",
264
323
  depth: childDepth,
265
- entries: Array.from(child.entries.entries(), ([childKey, value]) => [childKey, value.node]),
266
- index: 0,
324
+ entries: child.entries.entries(),
267
325
  out: outObj
268
326
  });
269
327
  continue;
270
328
  }
271
329
  const outArr = [];
272
- frame.out[key] = outArr;
330
+ setMaterializedProperty(frame.out, key, outArr);
273
331
  stack.push({
274
332
  kind: "seq",
275
333
  depth: childDepth,
276
- ids: rgaLinearizeIds(child),
277
- index: 0,
278
- seq: child,
334
+ cursor: rgaCreateLinearCursor(child),
279
335
  out: outArr
280
336
  });
281
337
  continue;
282
338
  }
283
- if (frame.index >= frame.ids.length) {
339
+ const elem = frame.cursor.next();
340
+ if (!elem) {
284
341
  stack.pop();
285
342
  continue;
286
343
  }
287
- const id = frame.ids[frame.index++];
288
- const child = frame.seq.elems.get(id).value;
344
+ const child = elem.value;
289
345
  const childDepth = frame.depth + 1;
290
346
  assertTraversalDepth(childDepth);
291
347
  if (child.kind === "lww") {
@@ -293,13 +349,12 @@ function materialize(node) {
293
349
  continue;
294
350
  }
295
351
  if (child.kind === "obj") {
296
- const outObj = {};
352
+ const outObj = createMaterializedObject();
297
353
  frame.out.push(outObj);
298
354
  stack.push({
299
355
  kind: "obj",
300
356
  depth: childDepth,
301
- entries: Array.from(child.entries.entries(), ([key, value]) => [key, value.node]),
302
- index: 0,
357
+ entries: child.entries.entries(),
303
358
  out: outObj
304
359
  });
305
360
  continue;
@@ -309,9 +364,7 @@ function materialize(node) {
309
364
  stack.push({
310
365
  kind: "seq",
311
366
  depth: childDepth,
312
- ids: rgaLinearizeIds(child),
313
- index: 0,
314
- seq: child,
367
+ cursor: rgaCreateLinearCursor(child),
315
368
  out: outArr
316
369
  });
317
370
  }
@@ -374,6 +427,155 @@ function objCompactTombstones(obj, isStable) {
374
427
  return removed;
375
428
  }
376
429
 
430
+ //#endregion
431
+ //#region src/json-value.ts
432
+ /**
433
+ * Runtime validation error for values that are not JSON-compatible.
434
+ * `path` is an RFC 6901 pointer relative to the validated root.
435
+ */
436
+ var JsonValueValidationError = class extends TypeError {
437
+ path;
438
+ detail;
439
+ constructor(path, detail) {
440
+ super(`invalid JSON value at ${path === "" ? "<root>" : path}: ${detail}`);
441
+ this.name = "JsonValueValidationError";
442
+ this.path = path;
443
+ this.detail = detail;
444
+ }
445
+ };
446
+ /** Assert that a runtime value is JSON-compatible (including finite numbers only). */
447
+ function assertRuntimeJsonValue(value) {
448
+ const stack = [{
449
+ value,
450
+ path: "",
451
+ depth: 0
452
+ }];
453
+ while (stack.length > 0) {
454
+ const frame = stack.pop();
455
+ assertTraversalDepth(frame.depth);
456
+ if (isJsonPrimitive$1(frame.value)) continue;
457
+ if (Array.isArray(frame.value)) {
458
+ for (const [index, child] of frame.value.entries()) stack.push({
459
+ value: child,
460
+ path: appendPointerSegment(frame.path, String(index)),
461
+ depth: frame.depth + 1
462
+ });
463
+ continue;
464
+ }
465
+ if (isJsonObject(frame.value)) {
466
+ for (const [key, child] of Object.entries(frame.value)) stack.push({
467
+ value: child,
468
+ path: appendPointerSegment(frame.path, key),
469
+ depth: frame.depth + 1
470
+ });
471
+ continue;
472
+ }
473
+ throw new JsonValueValidationError(frame.path, describeInvalidValue(frame.value));
474
+ }
475
+ }
476
+ /**
477
+ * Normalize a runtime value to JSON-compatible data.
478
+ * - non-finite numbers -> null
479
+ * - invalid object-property values -> key omitted
480
+ * - invalid root / array values -> null
481
+ */
482
+ function normalizeRuntimeJsonValue(value) {
483
+ const rootHolder = {};
484
+ const stack = [{
485
+ value,
486
+ path: "",
487
+ depth: 0,
488
+ slot: { kind: "root" }
489
+ }];
490
+ while (stack.length > 0) {
491
+ const frame = stack.pop();
492
+ assertTraversalDepth(frame.depth);
493
+ if (isJsonPrimitive$1(frame.value)) {
494
+ assignSlot(frame.slot, frame.value, rootHolder);
495
+ continue;
496
+ }
497
+ if (Array.isArray(frame.value)) {
498
+ const out = [];
499
+ assignSlot(frame.slot, out, rootHolder);
500
+ for (const [index, child] of frame.value.entries()) stack.push({
501
+ value: child,
502
+ path: appendPointerSegment(frame.path, String(index)),
503
+ depth: frame.depth + 1,
504
+ slot: {
505
+ kind: "array",
506
+ out,
507
+ index
508
+ }
509
+ });
510
+ continue;
511
+ }
512
+ if (isJsonObject(frame.value)) {
513
+ const out = Object.create(null);
514
+ assignSlot(frame.slot, out, rootHolder);
515
+ for (const [key, child] of Object.entries(frame.value)) stack.push({
516
+ value: child,
517
+ path: appendPointerSegment(frame.path, key),
518
+ depth: frame.depth + 1,
519
+ slot: {
520
+ kind: "object",
521
+ out,
522
+ key
523
+ }
524
+ });
525
+ continue;
526
+ }
527
+ if (isNonFiniteNumber(frame.value)) {
528
+ assignSlot(frame.slot, null, rootHolder);
529
+ continue;
530
+ }
531
+ if (frame.slot.kind !== "object") assignSlot(frame.slot, null, rootHolder);
532
+ }
533
+ return rootHolder.value ?? null;
534
+ }
535
+ /** Runtime JSON guardrail helper shared by create/apply/diff paths. */
536
+ function coerceRuntimeJsonValue(value, mode) {
537
+ if (mode === "none") return value;
538
+ if (mode === "strict") {
539
+ assertRuntimeJsonValue(value);
540
+ return value;
541
+ }
542
+ return normalizeRuntimeJsonValue(value);
543
+ }
544
+ function assignSlot(slot, value, rootHolder) {
545
+ if (slot.kind === "root") {
546
+ rootHolder.value = value;
547
+ return;
548
+ }
549
+ if (slot.kind === "array") {
550
+ slot.out[slot.index] = value;
551
+ return;
552
+ }
553
+ slot.out[slot.key] = value;
554
+ }
555
+ function appendPointerSegment(path, segment) {
556
+ const escaped = segment.replaceAll("~", "~0").replaceAll("/", "~1");
557
+ if (path === "") return `/${escaped}`;
558
+ return `${path}/${escaped}`;
559
+ }
560
+ function isJsonPrimitive$1(value) {
561
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
562
+ return typeof value === "number" && Number.isFinite(value);
563
+ }
564
+ function isJsonObject(value) {
565
+ return typeof value === "object" && value !== null && !Array.isArray(value);
566
+ }
567
+ function isNonFiniteNumber(value) {
568
+ return typeof value === "number" && !Number.isFinite(value);
569
+ }
570
+ function describeInvalidValue(value) {
571
+ if (typeof value === "number") return `non-finite number (${String(value)})`;
572
+ if (value === void 0) return "undefined is not valid JSON";
573
+ if (typeof value === "bigint") return "bigint is not valid JSON";
574
+ if (typeof value === "symbol") return "symbol is not valid JSON";
575
+ if (typeof value === "function") return "function is not valid JSON";
576
+ return `unsupported value type (${typeof value})`;
577
+ }
578
+
377
579
  //#endregion
378
580
  //#region src/types.ts
379
581
  /**
@@ -460,8 +662,9 @@ function getAtJson(base, path) {
460
662
  if (idx < 0 || idx >= cur.length) throw new JsonLookupError("INDEX_OUT_OF_BOUNDS", seg, `Index out of bounds at '${seg}'`);
461
663
  cur = cur[idx];
462
664
  } else if (cur && typeof cur === "object") {
463
- if (!(seg in cur)) throw new JsonLookupError("MISSING_KEY", seg, `Missing key '${seg}'`);
464
- cur = cur[seg];
665
+ const obj = cur;
666
+ if (!hasOwn(obj, seg)) throw new JsonLookupError("MISSING_KEY", seg, `Missing key '${seg}'`);
667
+ cur = obj[seg];
465
668
  } else throw new JsonLookupError("NON_CONTAINER", seg, `Cannot traverse into non-container at '${seg}'`);
466
669
  return cur;
467
670
  }
@@ -475,13 +678,14 @@ function getAtJson(base, path) {
475
678
  */
476
679
  function compileJsonPatchToIntent(baseJson, patch, options = {}) {
477
680
  const semantics = options.semantics ?? "sequential";
478
- let workingBase = semantics === "sequential" ? structuredClone(baseJson) : baseJson;
681
+ let workingBase = baseJson;
682
+ const pointerCache = /* @__PURE__ */ new Map();
479
683
  const intents = [];
480
684
  for (let opIndex = 0; opIndex < patch.length; opIndex++) {
481
685
  const op = patch[opIndex];
482
686
  const compileBase = semantics === "sequential" ? workingBase : baseJson;
483
- intents.push(...compileSingleOp(compileBase, op, opIndex, semantics));
484
- if (semantics === "sequential") workingBase = applyPatchOpToJson(workingBase, op, opIndex);
687
+ intents.push(...compileSingleOp(compileBase, op, opIndex, semantics, pointerCache));
688
+ if (semantics === "sequential") workingBase = applyPatchOpToJsonWithStructuralSharing(workingBase, op, opIndex, pointerCache);
485
689
  }
486
690
  return intents;
487
691
  }
@@ -495,23 +699,22 @@ function compileJsonPatchToIntent(baseJson, patch, options = {}) {
495
699
  * @returns An array of JSON Patch operations that transform `base` into `next`.
496
700
  */
497
701
  function diffJsonPatch(base, next, options = {}) {
702
+ const runtimeMode = options.jsonValidation ?? "none";
703
+ const runtimeBase = coerceRuntimeJsonValue(base, runtimeMode);
704
+ const runtimeNext = coerceRuntimeJsonValue(next, runtimeMode);
498
705
  const ops = [];
499
- diffValue([], base, next, ops, options);
706
+ diffValue([], runtimeBase, runtimeNext, ops, options);
500
707
  return ops;
501
708
  }
502
709
  function diffValue(path, base, next, ops, options) {
503
710
  if (jsonEquals(base, next)) return;
504
711
  if (Array.isArray(base) || Array.isArray(next)) {
505
712
  if ((options.arrayStrategy ?? "lcs") === "lcs" && Array.isArray(base) && Array.isArray(next)) {
506
- if (!shouldUseLcsDiff(base.length, next.length, options.lcsMaxCells)) {
507
- ops.push({
508
- op: "replace",
509
- path: stringifyJsonPointer(path),
510
- value: next
511
- });
512
- return;
513
- }
514
- diffArray(path, base, next, ops);
713
+ if (!diffArray(path, base, next, ops, options.lcsMaxCells)) ops.push({
714
+ op: "replace",
715
+ path: stringifyJsonPointer(path),
716
+ value: next
717
+ });
515
718
  return;
516
719
  }
517
720
  ops.push({
@@ -531,34 +734,114 @@ function diffValue(path, base, next, ops, options) {
531
734
  }
532
735
  const baseKeys = Object.keys(base).sort();
533
736
  const nextKeys = Object.keys(next).sort();
534
- const baseSet = new Set(baseKeys);
535
- const nextSet = new Set(nextKeys);
536
- for (const key of baseKeys) if (!nextSet.has(key)) ops.push({
537
- op: "remove",
538
- path: stringifyJsonPointer([...path, key])
539
- });
540
- for (const key of nextKeys) if (!baseSet.has(key)) {
541
- const nextValue = next[key];
737
+ let baseIndex = 0;
738
+ let nextIndex = 0;
739
+ while (baseIndex < baseKeys.length && nextIndex < nextKeys.length) {
740
+ const baseKey = baseKeys[baseIndex];
741
+ const nextKey = nextKeys[nextIndex];
742
+ if (baseKey === nextKey) {
743
+ baseIndex += 1;
744
+ nextIndex += 1;
745
+ continue;
746
+ }
747
+ if (baseKey < nextKey) {
748
+ path.push(baseKey);
749
+ ops.push({
750
+ op: "remove",
751
+ path: stringifyJsonPointer(path)
752
+ });
753
+ path.pop();
754
+ baseIndex += 1;
755
+ continue;
756
+ }
757
+ nextIndex += 1;
758
+ }
759
+ while (baseIndex < baseKeys.length) {
760
+ const baseKey = baseKeys[baseIndex];
761
+ path.push(baseKey);
762
+ ops.push({
763
+ op: "remove",
764
+ path: stringifyJsonPointer(path)
765
+ });
766
+ path.pop();
767
+ baseIndex += 1;
768
+ }
769
+ baseIndex = 0;
770
+ nextIndex = 0;
771
+ while (baseIndex < baseKeys.length && nextIndex < nextKeys.length) {
772
+ const baseKey = baseKeys[baseIndex];
773
+ const nextKey = nextKeys[nextIndex];
774
+ if (baseKey === nextKey) {
775
+ baseIndex += 1;
776
+ nextIndex += 1;
777
+ continue;
778
+ }
779
+ if (baseKey < nextKey) {
780
+ baseIndex += 1;
781
+ continue;
782
+ }
783
+ path.push(nextKey);
542
784
  ops.push({
543
785
  op: "add",
544
- path: stringifyJsonPointer([...path, key]),
545
- value: nextValue
786
+ path: stringifyJsonPointer(path),
787
+ value: next[nextKey]
546
788
  });
789
+ path.pop();
790
+ nextIndex += 1;
547
791
  }
548
- for (const key of baseKeys) if (nextSet.has(key)) diffValue([...path, key], base[key], next[key], ops, options);
549
- }
550
- function diffArray(path, base, next, ops) {
551
- const n = base.length;
552
- const m = next.length;
792
+ while (nextIndex < nextKeys.length) {
793
+ const nextKey = nextKeys[nextIndex];
794
+ path.push(nextKey);
795
+ ops.push({
796
+ op: "add",
797
+ path: stringifyJsonPointer(path),
798
+ value: next[nextKey]
799
+ });
800
+ path.pop();
801
+ nextIndex += 1;
802
+ }
803
+ baseIndex = 0;
804
+ nextIndex = 0;
805
+ while (baseIndex < baseKeys.length && nextIndex < nextKeys.length) {
806
+ const baseKey = baseKeys[baseIndex];
807
+ const nextKey = nextKeys[nextIndex];
808
+ if (baseKey === nextKey) {
809
+ path.push(baseKey);
810
+ diffValue(path, base[baseKey], next[nextKey], ops, options);
811
+ path.pop();
812
+ baseIndex += 1;
813
+ nextIndex += 1;
814
+ continue;
815
+ }
816
+ if (baseKey < nextKey) {
817
+ baseIndex += 1;
818
+ continue;
819
+ }
820
+ nextIndex += 1;
821
+ }
822
+ }
823
+ function diffArray(path, base, next, ops, lcsMaxCells) {
824
+ const baseLength = base.length;
825
+ const nextLength = next.length;
826
+ let prefix = 0;
827
+ while (prefix < baseLength && prefix < nextLength && jsonEquals(base[prefix], next[prefix])) prefix += 1;
828
+ let suffix = 0;
829
+ while (suffix < baseLength - prefix && suffix < nextLength - prefix && jsonEquals(base[baseLength - 1 - suffix], next[nextLength - 1 - suffix])) suffix += 1;
830
+ const baseStart = prefix;
831
+ const nextStart = prefix;
832
+ const n = baseLength - prefix - suffix;
833
+ const m = nextLength - prefix - suffix;
834
+ if (!shouldUseLcsDiff(n, m, lcsMaxCells)) return false;
835
+ if (n === 0 && m === 0) return true;
553
836
  const lcs = Array.from({ length: n + 1 }, () => Array(m + 1).fill(0));
554
- for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) if (jsonEquals(base[i], next[j])) lcs[i][j] = 1 + lcs[i + 1][j + 1];
837
+ for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) if (jsonEquals(base[baseStart + i], next[nextStart + j])) lcs[i][j] = 1 + lcs[i + 1][j + 1];
555
838
  else lcs[i][j] = Math.max(lcs[i + 1][j], lcs[i][j + 1]);
556
839
  const localOps = [];
557
840
  let i = 0;
558
841
  let j = 0;
559
- let index = 0;
842
+ let index = prefix;
560
843
  while (i < n || j < m) {
561
- if (i < n && j < m && jsonEquals(base[i], next[j])) {
844
+ if (i < n && j < m && jsonEquals(base[baseStart + i], next[nextStart + j])) {
562
845
  i += 1;
563
846
  j += 1;
564
847
  index += 1;
@@ -567,25 +850,32 @@ function diffArray(path, base, next, ops) {
567
850
  const lcsDown = i < n ? lcs[i + 1][j] : -1;
568
851
  const lcsRight = j < m ? lcs[i][j + 1] : -1;
569
852
  if (j < m && (i === n || lcsRight > lcsDown)) {
853
+ const indexSegment = String(index);
854
+ path.push(indexSegment);
570
855
  localOps.push({
571
856
  op: "add",
572
- path: stringifyJsonPointer([...path, String(index)]),
573
- value: next[j]
857
+ path: stringifyJsonPointer(path),
858
+ value: next[nextStart + j]
574
859
  });
860
+ path.pop();
575
861
  j += 1;
576
862
  index += 1;
577
863
  continue;
578
864
  }
579
865
  if (i < n) {
866
+ const indexSegment = String(index);
867
+ path.push(indexSegment);
580
868
  localOps.push({
581
869
  op: "remove",
582
- path: stringifyJsonPointer([...path, String(index)])
870
+ path: stringifyJsonPointer(path)
583
871
  });
872
+ path.pop();
584
873
  i += 1;
585
874
  continue;
586
875
  }
587
876
  }
588
877
  ops.push(...compactArrayOps(localOps));
878
+ return true;
589
879
  }
590
880
  function shouldUseLcsDiff(baseLength, nextLength, lcsMaxCells) {
591
881
  if (lcsMaxCells === Number.POSITIVE_INFINITY) return true;
@@ -629,7 +919,7 @@ function jsonEquals(a, b) {
629
919
  const bKeys = Object.keys(b);
630
920
  if (aKeys.length !== bKeys.length) return false;
631
921
  for (const key of aKeys) {
632
- if (!(key in b)) return false;
922
+ if (!hasOwn(b, key)) return false;
633
923
  if (!jsonEquals(a[key], b[key])) return false;
634
924
  }
635
925
  return true;
@@ -641,6 +931,9 @@ const ARRAY_INDEX_TOKEN_PATTERN = /^(0|[1-9][0-9]*)$/;
641
931
  function hasOwn(value, key) {
642
932
  return Object.prototype.hasOwnProperty.call(value, key);
643
933
  }
934
+ function isUnsafeObjectKey(key) {
935
+ return key === "__proto__";
936
+ }
644
937
  function pathValueAt(base, path) {
645
938
  if (path.length === 0) return base;
646
939
  return getAtJson(base, path);
@@ -648,17 +941,17 @@ function pathValueAt(base, path) {
648
941
  function assertNever$1(_value, message) {
649
942
  throw new Error(message);
650
943
  }
651
- function compileSingleOp(baseJson, op, opIndex, semantics) {
944
+ function compileSingleOp(baseJson, op, opIndex, semantics, pointerCache) {
652
945
  if (op.op === "test") return [{
653
946
  t: "Test",
654
- path: parsePointerOrThrow(op.path, op.path, opIndex),
947
+ path: parsePointerOrThrow(op.path, op.path, opIndex, pointerCache),
655
948
  value: op.value
656
949
  }];
657
950
  if (op.op === "copy" || op.op === "move") {
658
- const fromPath = parsePointerOrThrow(op.from, op.from, opIndex);
659
- const toPath = parsePointerOrThrow(op.path, op.path, opIndex);
951
+ const fromPath = parsePointerOrThrow(op.from, op.from, opIndex, pointerCache);
952
+ const toPath = parsePointerOrThrow(op.path, op.path, opIndex, pointerCache);
660
953
  if (op.op === "move" && isStrictDescendantPath(fromPath, toPath)) throw compileError("INVALID_MOVE", `cannot move a value into one of its descendants at ${op.path}`, op.path, opIndex);
661
- const val = lookupValueOrThrow(baseJson, fromPath, op.from, opIndex);
954
+ const val = structuredClone(lookupValueOrThrow(baseJson, fromPath, op.from, opIndex));
662
955
  if (op.op === "move" && isSamePath(fromPath, toPath)) return [];
663
956
  if (op.op === "move" && semantics === "sequential") {
664
957
  const removeOp = {
@@ -670,21 +963,21 @@ function compileSingleOp(baseJson, op, opIndex, semantics) {
670
963
  path: op.path,
671
964
  value: val
672
965
  };
673
- const baseAfterRemove = applyPatchOpToJson(baseJson, removeOp, opIndex);
674
- return [...compileSingleOp(baseJson, removeOp, opIndex, semantics), ...compileSingleOp(baseAfterRemove, addOp, opIndex, semantics)];
966
+ const baseAfterRemove = applyPatchOpToJson(baseJson, removeOp, opIndex, pointerCache);
967
+ return [...compileSingleOp(baseJson, removeOp, opIndex, semantics, pointerCache), ...compileSingleOp(baseAfterRemove, addOp, opIndex, semantics, pointerCache)];
675
968
  }
676
969
  const out = compileSingleOp(baseJson, {
677
970
  op: "add",
678
971
  path: op.path,
679
972
  value: val
680
- }, opIndex, semantics);
973
+ }, opIndex, semantics, pointerCache);
681
974
  if (op.op === "move") out.push(...compileSingleOp(baseJson, {
682
975
  op: "remove",
683
976
  path: op.from
684
- }, opIndex, semantics));
977
+ }, opIndex, semantics, pointerCache));
685
978
  return out;
686
979
  }
687
- const path = parsePointerOrThrow(op.path, op.path, opIndex);
980
+ const path = parsePointerOrThrow(op.path, op.path, opIndex, pointerCache);
688
981
  if (path.length === 0) {
689
982
  if (op.op === "replace" || op.op === "add") return [{
690
983
  t: "ObjSet",
@@ -720,6 +1013,7 @@ function compileSingleOp(baseJson, op, opIndex, semantics) {
720
1013
  return assertNever$1(op, "Unsupported op at array path");
721
1014
  }
722
1015
  if (!isPlainObject(parentValue)) throw compileError("INVALID_TARGET", `expected object or array parent at ${parentPath}`, parentPath, opIndex);
1016
+ if (isUnsafeObjectKey(token)) throw compileError("INVALID_POINTER", `unsafe object key at ${op.path}`, op.path, opIndex);
723
1017
  if ((op.op === "replace" || op.op === "remove") && !hasOwn(parentValue, token)) throw compileError("MISSING_TARGET", `missing key ${token} at ${parentPath}`, op.path, opIndex);
724
1018
  if (op.op === "add") return [{
725
1019
  t: "ObjSet",
@@ -742,57 +1036,91 @@ function compileSingleOp(baseJson, op, opIndex, semantics) {
742
1036
  }];
743
1037
  return assertNever$1(op, "Unsupported op");
744
1038
  }
745
- function applyPatchOpToJson(baseJson, op, opIndex) {
746
- let doc = structuredClone(baseJson);
1039
+ function applyPatchOpToJson(baseJson, op, opIndex, pointerCache) {
1040
+ return applyPatchOpToJsonWithStructuralSharing(baseJson, op, opIndex, pointerCache);
1041
+ }
1042
+ function applyPatchOpToJsonWithStructuralSharing(doc, op, opIndex, pointerCache) {
747
1043
  if (op.op === "test") return doc;
748
1044
  if (op.op === "copy" || op.op === "move") {
749
- const fromPath = parsePointerOrThrow(op.from, op.from, opIndex);
1045
+ const fromPath = parsePointerOrThrow(op.from, op.from, opIndex, pointerCache);
750
1046
  const value = structuredClone(lookupValueOrThrow(doc, fromPath, op.from, opIndex));
751
- if (op.op === "move") doc = applyPatchOpToJson(doc, {
1047
+ return applyPatchOpToJsonWithStructuralSharing(op.op === "move" ? applyPatchOpToJsonWithStructuralSharing(doc, {
752
1048
  op: "remove",
753
1049
  path: op.from
754
- }, opIndex);
755
- return applyPatchOpToJson(doc, {
1050
+ }, opIndex, pointerCache) : doc, {
756
1051
  op: "add",
757
1052
  path: op.path,
758
1053
  value
759
- }, opIndex);
1054
+ }, opIndex, pointerCache);
760
1055
  }
761
- const path = parsePointerOrThrow(op.path, op.path, opIndex);
1056
+ const path = parsePointerOrThrow(op.path, op.path, opIndex, pointerCache);
762
1057
  if (path.length === 0) {
763
1058
  if (op.op === "add" || op.op === "replace") return structuredClone(op.value);
764
1059
  throw compileError("INVALID_TARGET", "remove at root path is not supported in RFC-compliant mode", op.path, opIndex);
765
1060
  }
766
1061
  const parentPath = path.slice(0, -1);
767
1062
  const token = path[path.length - 1];
768
- let parent;
769
- if (parentPath.length === 0) parent = doc;
770
- else parent = lookupValueOrThrow(doc, parentPath, op.path, opIndex);
771
- if (Array.isArray(parent)) {
772
- const index = parseArrayIndexToken(token, op.op, parent.length, op.path, opIndex);
1063
+ const parentValue = parentPath.length === 0 ? doc : lookupValueOrThrow(doc, parentPath, op.path, opIndex);
1064
+ if (Array.isArray(parentValue)) {
1065
+ const index = parseArrayIndexToken(token, op.op, parentValue.length, op.path, opIndex);
1066
+ const { root, parent } = cloneJsonPathToParent(doc, parentPath);
1067
+ const clonedParent = parent;
773
1068
  if (op.op === "add") {
774
- const insertAt = index === Number.POSITIVE_INFINITY ? parent.length : index;
775
- parent.splice(insertAt, 0, structuredClone(op.value));
776
- return doc;
1069
+ const insertAt = index === Number.POSITIVE_INFINITY ? clonedParent.length : index;
1070
+ clonedParent.splice(insertAt, 0, structuredClone(op.value));
1071
+ return root;
777
1072
  }
778
1073
  if (op.op === "replace") {
779
- parent[index] = structuredClone(op.value);
780
- return doc;
1074
+ clonedParent[index] = structuredClone(op.value);
1075
+ return root;
781
1076
  }
782
- parent.splice(index, 1);
783
- return doc;
1077
+ clonedParent.splice(index, 1);
1078
+ return root;
784
1079
  }
785
- if (!isPlainObject(parent)) throw compileError("INVALID_TARGET", `expected object or array parent at ${stringifyJsonPointer(parentPath)}`, op.path, opIndex);
1080
+ if (!isPlainObject(parentValue)) throw compileError("INVALID_TARGET", `expected object or array parent at ${stringifyJsonPointer(parentPath)}`, op.path, opIndex);
1081
+ if (isUnsafeObjectKey(token)) throw compileError("INVALID_POINTER", `unsafe object key at ${op.path}`, op.path, opIndex);
1082
+ const { root, parent } = cloneJsonPathToParent(doc, parentPath);
1083
+ const clonedParent = parent;
786
1084
  if (op.op === "add" || op.op === "replace") {
787
- parent[token] = structuredClone(op.value);
788
- return doc;
1085
+ clonedParent[token] = structuredClone(op.value);
1086
+ return root;
789
1087
  }
790
- delete parent[token];
791
- return doc;
1088
+ delete clonedParent[token];
1089
+ return root;
792
1090
  }
793
- function parsePointerOrThrow(ptr, path, opIndex) {
1091
+ function cloneJsonContainerShallow(value) {
1092
+ if (Array.isArray(value)) return value.slice();
1093
+ if (isPlainObject(value)) return { ...value };
1094
+ throw new Error("Expected JSON container");
1095
+ }
1096
+ function cloneJsonPathToParent(doc, parentPath) {
1097
+ const root = cloneJsonContainerShallow(doc);
1098
+ if (parentPath.length === 0) return {
1099
+ root,
1100
+ parent: root
1101
+ };
1102
+ let sourceCur = doc;
1103
+ let targetCur = root;
1104
+ for (const segment of parentPath) {
1105
+ const nextSource = Array.isArray(sourceCur) ? sourceCur[Number(segment)] : sourceCur[segment];
1106
+ const nextTarget = cloneJsonContainerShallow(nextSource);
1107
+ if (Array.isArray(targetCur)) targetCur[Number(segment)] = nextTarget;
1108
+ else targetCur[segment] = nextTarget;
1109
+ sourceCur = nextSource;
1110
+ targetCur = nextTarget;
1111
+ }
1112
+ return {
1113
+ root,
1114
+ parent: targetCur
1115
+ };
1116
+ }
1117
+ function parsePointerOrThrow(ptr, path, opIndex, pointerCache) {
1118
+ const cached = pointerCache.get(ptr);
1119
+ if (cached) return cached.slice();
794
1120
  try {
795
- return parseJsonPointer(ptr);
1121
+ const parsed = parseJsonPointer(ptr);
1122
+ pointerCache.set(ptr, parsed);
1123
+ return parsed.slice();
796
1124
  } catch (error) {
797
1125
  throw compileError("INVALID_POINTER", error instanceof Error ? error.message : "invalid pointer", path, opIndex);
798
1126
  }
@@ -971,6 +1299,38 @@ function ensureSeqAtPath(head, path, dotForCreate) {
971
1299
  if (head.root.kind !== "seq") head.root = newSeq();
972
1300
  return head.root;
973
1301
  }
1302
+ function getNodeAtPath(doc, path) {
1303
+ let cur = doc.root;
1304
+ for (const seg of path) {
1305
+ if (cur.kind !== "obj") return;
1306
+ const ent = cur.entries.get(seg);
1307
+ if (!ent) return;
1308
+ cur = ent.node;
1309
+ }
1310
+ return cur;
1311
+ }
1312
+ function getHeadSeqForBaseArrayIntent(head, path) {
1313
+ const pointer = `/${path.join("/")}`;
1314
+ const headNode = getNodeAtPath(head, path);
1315
+ if (!headNode) return {
1316
+ ok: false,
1317
+ code: 409,
1318
+ reason: "MISSING_PARENT",
1319
+ message: `head array missing at ${pointer}`,
1320
+ path: pointer
1321
+ };
1322
+ if (headNode.kind !== "seq") return {
1323
+ ok: false,
1324
+ code: 409,
1325
+ reason: "INVALID_TARGET",
1326
+ message: `expected array at ${pointer}`,
1327
+ path: pointer
1328
+ };
1329
+ return {
1330
+ ok: true,
1331
+ seq: headNode
1332
+ };
1333
+ }
974
1334
  function deepNodeFromJson(value, dot) {
975
1335
  return deepNodeFromJsonWithDepth(value, dot, 0);
976
1336
  }
@@ -1151,11 +1511,32 @@ function cloneNodeAtDepth(node, depth) {
1151
1511
  function isJsonPrimitive(value) {
1152
1512
  return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1153
1513
  }
1514
+ function getJsonAtDocPathForTest(doc, path) {
1515
+ let cur = doc.root;
1516
+ for (let i = 0; i < path.length; i++) {
1517
+ const seg = path[i];
1518
+ assertTraversalDepth(i + 1);
1519
+ if (cur.kind === "obj") {
1520
+ const ent = cur.entries.get(seg);
1521
+ if (!ent) throw new Error(`Missing key '${seg}'`);
1522
+ cur = ent.node;
1523
+ continue;
1524
+ }
1525
+ if (cur.kind === "seq") {
1526
+ if (!ARRAY_INDEX_TOKEN_PATTERN.test(seg)) throw new Error(`Expected array index, got '${seg}'`);
1527
+ const id = rgaIdAtIndex(cur, Number(seg));
1528
+ if (id === void 0) throw new Error(`Index out of bounds at '${seg}'`);
1529
+ cur = cur.elems.get(id).value;
1530
+ continue;
1531
+ }
1532
+ throw new Error(`Cannot traverse into non-container at '${seg}'`);
1533
+ }
1534
+ return cur.kind === "lww" ? cur.value : materialize(cur);
1535
+ }
1154
1536
  function applyTest(base, head, it, evalTestAgainst) {
1155
- const snapshot = evalTestAgainst === "head" ? materialize(head.root) : materialize(base.root);
1156
1537
  let got;
1157
1538
  try {
1158
- got = getAtJson(snapshot, it.path);
1539
+ got = getJsonAtDocPathForTest(evalTestAgainst === "head" ? head : base, it.path);
1159
1540
  } catch {
1160
1541
  return {
1161
1542
  ok: false,
@@ -1220,10 +1601,17 @@ function applyObjRemove(head, it, newDot) {
1220
1601
  objRemove(parentObj, it.key, d);
1221
1602
  return null;
1222
1603
  }
1223
- function applyArrInsert(base, head, it, newDot, bumpCounterAbove) {
1604
+ function applyArrInsert(base, head, it, newDot, bumpCounterAbove, strictParents = false) {
1224
1605
  const pointer = `/${it.path.join("/")}`;
1225
1606
  const baseSeq = getSeqAtPath(base, it.path);
1226
1607
  if (!baseSeq) {
1608
+ if (strictParents) return {
1609
+ ok: false,
1610
+ code: 409,
1611
+ reason: "MISSING_PARENT",
1612
+ message: `base array missing at /${it.path.join("/")}`,
1613
+ path: pointer
1614
+ };
1227
1615
  if (it.index === 0 || it.index === Number.POSITIVE_INFINITY) {
1228
1616
  const headSeq = ensureSeqAtPath(head, it.path, newDot());
1229
1617
  const prev = it.index === 0 ? HEAD : rgaPrevForInsertAtIndex(headSeq, Number.MAX_SAFE_INTEGER);
@@ -1241,7 +1629,10 @@ function applyArrInsert(base, head, it, newDot, bumpCounterAbove) {
1241
1629
  path: pointer
1242
1630
  };
1243
1631
  }
1244
- const headSeq = ensureSeqAtPath(head, it.path, newDot());
1632
+ newDot();
1633
+ const headSeqRes = getHeadSeqForBaseArrayIntent(head, it.path);
1634
+ if (!headSeqRes.ok) return headSeqRes;
1635
+ const headSeq = headSeqRes.seq;
1245
1636
  const idx = it.index === Number.POSITIVE_INFINITY ? rgaLinearizeIds(baseSeq).length : it.index;
1246
1637
  const baseLen = rgaLinearizeIds(baseSeq).length;
1247
1638
  if (idx < 0 || idx > baseLen) return {
@@ -1286,7 +1677,7 @@ function nextInsertDotForPrev(seq, prev, newDot, path, bumpCounterAbove) {
1286
1677
  };
1287
1678
  }
1288
1679
  function applyArrDelete(base, head, it, newDot) {
1289
- const d = newDot();
1680
+ newDot();
1290
1681
  const baseSeq = getSeqAtPath(base, it.path);
1291
1682
  if (!baseSeq) return {
1292
1683
  ok: false,
@@ -1295,7 +1686,9 @@ function applyArrDelete(base, head, it, newDot) {
1295
1686
  message: `base array missing at /${it.path.join("/")}`,
1296
1687
  path: `/${it.path.join("/")}`
1297
1688
  };
1298
- const headSeq = ensureSeqAtPath(head, it.path, d);
1689
+ const headSeqRes = getHeadSeqForBaseArrayIntent(head, it.path);
1690
+ if (!headSeqRes.ok) return headSeqRes;
1691
+ const headSeq = headSeqRes.seq;
1299
1692
  const baseId = rgaIdAtIndex(baseSeq, it.index);
1300
1693
  if (!baseId) return {
1301
1694
  ok: false,
@@ -1304,11 +1697,18 @@ function applyArrDelete(base, head, it, newDot) {
1304
1697
  message: `no base element at index ${it.index}`,
1305
1698
  path: `/${it.path.join("/")}/${it.index}`
1306
1699
  };
1700
+ if (!headSeq.elems.get(baseId)) return {
1701
+ ok: false,
1702
+ code: 409,
1703
+ reason: "MISSING_TARGET",
1704
+ message: `element missing in head lineage at index ${it.index}`,
1705
+ path: `/${it.path.join("/")}/${it.index}`
1706
+ };
1307
1707
  rgaDelete(headSeq, baseId);
1308
1708
  return null;
1309
1709
  }
1310
1710
  function applyArrReplace(base, head, it, newDot) {
1311
- const d = newDot();
1711
+ newDot();
1312
1712
  const baseSeq = getSeqAtPath(base, it.path);
1313
1713
  if (!baseSeq) return {
1314
1714
  ok: false,
@@ -1317,7 +1717,9 @@ function applyArrReplace(base, head, it, newDot) {
1317
1717
  message: `base array missing at /${it.path.join("/")}`,
1318
1718
  path: `/${it.path.join("/")}`
1319
1719
  };
1320
- const headSeq = ensureSeqAtPath(head, it.path, d);
1720
+ const headSeqRes = getHeadSeqForBaseArrayIntent(head, it.path);
1721
+ if (!headSeqRes.ok) return headSeqRes;
1722
+ const headSeq = headSeqRes.seq;
1321
1723
  const baseId = rgaIdAtIndex(baseSeq, it.index);
1322
1724
  if (!baseId) return {
1323
1725
  ok: false,
@@ -1346,9 +1748,11 @@ function applyArrReplace(base, head, it, newDot) {
1346
1748
  * @param newDot - A function that generates a unique `Dot` per mutation.
1347
1749
  * @param evalTestAgainst - Whether `test` ops are evaluated against `"head"` or `"base"`.
1348
1750
  * @param bumpCounterAbove - Optional hook that can fast-forward the underlying counter before inserts.
1751
+ * @param options - Optional behavior toggles.
1752
+ * @param options.strictParents - When `true`, reject array inserts whose base parent path is missing.
1349
1753
  * @returns `{ ok: true }` on success, or `{ ok: false, code: 409, message }` on conflict.
1350
1754
  */
1351
- function applyIntentsToCrdt(base, head, intents, newDot, evalTestAgainst = "head", bumpCounterAbove) {
1755
+ function applyIntentsToCrdt(base, head, intents, newDot, evalTestAgainst = "head", bumpCounterAbove, options = {}) {
1352
1756
  for (const it of intents) {
1353
1757
  let fail = null;
1354
1758
  switch (it.t) {
@@ -1362,7 +1766,7 @@ function applyIntentsToCrdt(base, head, intents, newDot, evalTestAgainst = "head
1362
1766
  fail = applyObjRemove(head, it, newDot);
1363
1767
  break;
1364
1768
  case "ArrInsert":
1365
- fail = applyArrInsert(base, head, it, newDot, bumpCounterAbove);
1769
+ fail = applyArrInsert(base, head, it, newDot, bumpCounterAbove, options.strictParents ?? false);
1366
1770
  break;
1367
1771
  case "ArrDelete":
1368
1772
  fail = applyArrDelete(base, head, it, newDot);
@@ -1376,7 +1780,7 @@ function applyIntentsToCrdt(base, head, intents, newDot, evalTestAgainst = "head
1376
1780
  }
1377
1781
  return { ok: true };
1378
1782
  }
1379
- function jsonPatchToCrdt(baseOrOptions, head, patch, newDot, evalTestAgainst = "head", bumpCounterAbove) {
1783
+ function jsonPatchToCrdt(baseOrOptions, head, patch, newDot, evalTestAgainst = "head", bumpCounterAbove, strictParents = false) {
1380
1784
  if (isJsonPatchToCrdtOptions(baseOrOptions)) return jsonPatchToCrdtInternal(baseOrOptions);
1381
1785
  if (!head || !patch || !newDot) return {
1382
1786
  ok: false,
@@ -1390,10 +1794,11 @@ function jsonPatchToCrdt(baseOrOptions, head, patch, newDot, evalTestAgainst = "
1390
1794
  patch,
1391
1795
  newDot,
1392
1796
  evalTestAgainst,
1393
- bumpCounterAbove
1797
+ bumpCounterAbove,
1798
+ strictParents
1394
1799
  });
1395
1800
  }
1396
- function jsonPatchToCrdtSafe(baseOrOptions, head, patch, newDot, evalTestAgainst = "head", bumpCounterAbove) {
1801
+ function jsonPatchToCrdtSafe(baseOrOptions, head, patch, newDot, evalTestAgainst = "head", bumpCounterAbove, strictParents = false) {
1397
1802
  try {
1398
1803
  if (isJsonPatchToCrdtOptions(baseOrOptions)) return jsonPatchToCrdt(baseOrOptions);
1399
1804
  if (!head || !patch || !newDot) return {
@@ -1402,7 +1807,7 @@ function jsonPatchToCrdtSafe(baseOrOptions, head, patch, newDot, evalTestAgainst
1402
1807
  reason: "INVALID_PATCH",
1403
1808
  message: "invalid jsonPatchToCrdtSafe call signature"
1404
1809
  };
1405
- return jsonPatchToCrdt(baseOrOptions, head, patch, newDot, evalTestAgainst, bumpCounterAbove);
1810
+ return jsonPatchToCrdt(baseOrOptions, head, patch, newDot, evalTestAgainst, bumpCounterAbove, strictParents);
1406
1811
  } catch (error) {
1407
1812
  return toApplyError$1(error);
1408
1813
  }
@@ -1440,7 +1845,7 @@ function jsonPatchToCrdtInternal(options) {
1440
1845
  } catch (error) {
1441
1846
  return toApplyError$1(error);
1442
1847
  }
1443
- return applyIntentsToCrdt(options.base, options.head, intents, options.newDot, evalTestAgainst, options.bumpCounterAbove);
1848
+ return applyIntentsToCrdt(options.base, options.head, intents, options.newDot, evalTestAgainst, options.bumpCounterAbove, { strictParents: options.strictParents });
1444
1849
  }
1445
1850
  let shadowBase = cloneDoc(evalTestAgainst === "base" ? options.base : options.head);
1446
1851
  let shadowCtr = 0;
@@ -1459,10 +1864,10 @@ function jsonPatchToCrdtInternal(options) {
1459
1864
  } catch (error) {
1460
1865
  return withOpIndex(toApplyError$1(error), opIndex);
1461
1866
  }
1462
- const headStep = applyIntentsToCrdt(shadowBase, options.head, intents, options.newDot, evalTestAgainst, options.bumpCounterAbove);
1867
+ const headStep = applyIntentsToCrdt(shadowBase, options.head, intents, options.newDot, evalTestAgainst, options.bumpCounterAbove, { strictParents: options.strictParents });
1463
1868
  if (!headStep.ok) return withOpIndex(headStep, opIndex);
1464
1869
  if (evalTestAgainst === "base") {
1465
- const shadowStep = applyIntentsToCrdt(shadowBase, shadowBase, intents, shadowDot, "base", shadowBump);
1870
+ const shadowStep = applyIntentsToCrdt(shadowBase, shadowBase, intents, shadowDot, "base", shadowBump, { strictParents: options.strictParents });
1466
1871
  if (!shadowStep.ok) return withOpIndex(shadowStep, opIndex);
1467
1872
  } else shadowBase = cloneDoc(options.head);
1468
1873
  return { ok: true };
@@ -1566,7 +1971,7 @@ var PatchError = class extends Error {
1566
1971
  function createState(initial, options) {
1567
1972
  const clock = createClock(options.actor, options.start ?? 0);
1568
1973
  return {
1569
- doc: docFromJson(initial, clock.next),
1974
+ doc: docFromJson(coerceRuntimeJsonValue(initial, options.jsonValidation ?? "none"), clock.next),
1570
1975
  clock
1571
1976
  };
1572
1977
  }
@@ -1622,7 +2027,7 @@ function tryApplyPatch(state, patch, options = {}) {
1622
2027
  clock: cloneClock(state.clock)
1623
2028
  };
1624
2029
  try {
1625
- const result = applyPatchInternal(nextState, patch, options);
2030
+ const result = applyPatchInternal(nextState, patch, options, "batch");
1626
2031
  if (!result.ok) return {
1627
2032
  ok: false,
1628
2033
  error: result
@@ -1649,7 +2054,7 @@ function tryApplyPatchInPlace(state, patch, options = {}) {
1649
2054
  return { ok: true };
1650
2055
  }
1651
2056
  try {
1652
- const result = applyPatchInternal(state, patch, applyOptions);
2057
+ const result = applyPatchInternal(state, patch, applyOptions, "step");
1653
2058
  if (!result.ok) return {
1654
2059
  ok: false,
1655
2060
  error: result
@@ -1667,7 +2072,10 @@ function tryApplyPatchInPlace(state, patch, options = {}) {
1667
2072
  * Does not mutate caller-provided values.
1668
2073
  */
1669
2074
  function validateJsonPatch(base, patch, options = {}) {
1670
- const result = tryApplyPatch(createState(base, { actor: "__validate__" }), patch, options);
2075
+ const result = tryApplyPatch(createState(base, {
2076
+ actor: "__validate__",
2077
+ jsonValidation: options.jsonValidation
2078
+ }), patch, options);
1671
2079
  if (!result.ok) return {
1672
2080
  ok: false,
1673
2081
  error: result.error
@@ -1679,82 +2087,178 @@ function validateJsonPatch(base, patch, options = {}) {
1679
2087
  * Returns the updated state and a new version vector snapshot.
1680
2088
  */
1681
2089
  function applyPatchAsActor(doc, vv, actor, patch, options = {}) {
2090
+ const result = tryApplyPatchAsActor(doc, vv, actor, patch, options);
2091
+ if (!result.ok) throw new PatchError(result.error);
2092
+ return {
2093
+ state: result.state,
2094
+ vv: result.vv
2095
+ };
2096
+ }
2097
+ /** Non-throwing `applyPatchAsActor` variant for internals sync flows. */
2098
+ function tryApplyPatchAsActor(doc, vv, actor, patch, options = {}) {
1682
2099
  const observedCtr = maxCtrInNodeForActor$1(doc.root, actor);
1683
- const state = applyPatch({
2100
+ const applied = tryApplyPatch({
1684
2101
  doc,
1685
2102
  clock: createClock(actor, Math.max(vv[actor] ?? 0, observedCtr))
1686
2103
  }, patch, toApplyPatchOptionsForActor(options));
2104
+ if (!applied.ok) return applied;
2105
+ const nextVv = {
2106
+ ...vv,
2107
+ [actor]: Math.max(vv[actor] ?? 0, applied.state.clock.ctr)
2108
+ };
1687
2109
  return {
1688
- state,
1689
- vv: {
1690
- ...vv,
1691
- [actor]: Math.max(vv[actor] ?? 0, state.clock.ctr)
1692
- }
2110
+ ok: true,
2111
+ state: applied.state,
2112
+ vv: nextVv
1693
2113
  };
1694
2114
  }
1695
2115
  function toApplyPatchOptionsForActor(options) {
1696
2116
  return {
1697
2117
  semantics: options.semantics,
1698
2118
  testAgainst: options.testAgainst,
2119
+ strictParents: options.strictParents,
2120
+ jsonValidation: options.jsonValidation,
1699
2121
  base: options.base ? {
1700
2122
  doc: options.base,
1701
2123
  clock: createClock("__base__", 0)
1702
2124
  } : void 0
1703
2125
  };
1704
2126
  }
1705
- function applyPatchInternal(state, patch, options) {
2127
+ function applyPatchInternal(state, patch, options, execution) {
1706
2128
  if ((options.semantics ?? "sequential") === "sequential") {
2129
+ if (!options.base && execution === "batch") {
2130
+ const compiled = compileIntents(materialize(state.doc.root), patch, "sequential", options.jsonValidation ?? "none");
2131
+ if (!compiled.ok) return compiled;
2132
+ return applyIntentsToCrdt(state.doc, state.doc, compiled.intents, () => state.clock.next(), options.testAgainst ?? "head", (ctr) => bumpClockCounter(state, ctr), { strictParents: options.strictParents });
2133
+ }
1707
2134
  const explicitBaseState = options.base ? {
1708
2135
  doc: cloneDoc(options.base.doc),
1709
2136
  clock: createClock("__base__", 0)
1710
2137
  } : null;
2138
+ let sequentialHeadJson = materialize(state.doc.root);
2139
+ let sequentialBaseJson = explicitBaseState ? materialize(explicitBaseState.doc.root) : sequentialHeadJson;
1711
2140
  for (const [opIndex, op] of patch.entries()) {
1712
- const step = applyPatchOpSequential(state, op, options, explicitBaseState ? explicitBaseState.doc : state.doc, opIndex);
2141
+ const step = applyPatchOpSequential(state, op, options, explicitBaseState ? explicitBaseState.doc : state.doc, sequentialBaseJson, sequentialHeadJson, explicitBaseState, opIndex);
1713
2142
  if (!step.ok) return step;
1714
- if (explicitBaseState && op.op !== "test") {
1715
- const baseStep = applyPatchInternal(explicitBaseState, [op], {
1716
- semantics: "sequential",
1717
- testAgainst: "base"
1718
- });
1719
- if (!baseStep.ok) return baseStep;
1720
- }
2143
+ sequentialBaseJson = step.baseJson;
2144
+ sequentialHeadJson = step.headJson;
1721
2145
  }
1722
2146
  return { ok: true };
1723
2147
  }
1724
2148
  const baseDoc = options.base ? options.base.doc : cloneDoc(state.doc);
1725
- const compiled = compileIntents(materialize(baseDoc.root), patch, "base");
2149
+ const compiled = compileIntents(materialize(baseDoc.root), patch, "base", options.jsonValidation ?? "none");
1726
2150
  if (!compiled.ok) return compiled;
1727
- return applyIntentsToCrdt(baseDoc, state.doc, compiled.intents, () => state.clock.next(), options.testAgainst ?? "head", (ctr) => bumpClockCounter(state, ctr));
2151
+ return applyIntentsToCrdt(baseDoc, state.doc, compiled.intents, () => state.clock.next(), options.testAgainst ?? "head", (ctr) => bumpClockCounter(state, ctr), { strictParents: options.strictParents });
1728
2152
  }
1729
- function applyPatchOpSequential(state, op, options, baseDoc, opIndex) {
1730
- const baseJson = materialize(baseDoc.root);
2153
+ function applyPatchOpSequential(state, op, options, baseDoc, baseJson, headJson, explicitBaseState, opIndex) {
1731
2154
  if (op.op === "move") {
1732
2155
  const fromResolved = resolveValueAtPointer(baseJson, op.from, opIndex);
1733
2156
  if (!fromResolved.ok) return fromResolved;
1734
- const fromValue = fromResolved.value;
1735
- const removeRes = applySinglePatchOp(state, baseDoc, baseJson, {
2157
+ const fromValue = structuredClone(fromResolved.value);
2158
+ const removeRes = applySinglePatchOpSequentialStep(state, baseDoc, baseJson, headJson, {
1736
2159
  op: "remove",
1737
2160
  path: op.from
1738
- }, options);
2161
+ }, options, explicitBaseState);
1739
2162
  if (!removeRes.ok) return removeRes;
1740
- const addBase = state.doc;
1741
- return applySinglePatchOp(state, addBase, materialize(addBase.root), {
2163
+ const addOp = {
1742
2164
  op: "add",
1743
2165
  path: op.path,
1744
2166
  value: fromValue
1745
- }, options);
2167
+ };
2168
+ if (!explicitBaseState) return applySinglePatchOpSequentialStep(state, state.doc, removeRes.baseJson, removeRes.headJson, addOp, options, null);
2169
+ const headAddRes = applySinglePatchOpSequentialStep(state, state.doc, removeRes.headJson, removeRes.headJson, addOp, options, null);
2170
+ if (!headAddRes.ok) return headAddRes;
2171
+ const shadowAddRes = applySinglePatchOpExplicitShadowStep(explicitBaseState, removeRes.baseJson, addOp, options);
2172
+ if (!shadowAddRes.ok) return shadowAddRes;
2173
+ return {
2174
+ ok: true,
2175
+ baseJson: shadowAddRes.baseJson,
2176
+ headJson: headAddRes.headJson
2177
+ };
1746
2178
  }
1747
2179
  if (op.op === "copy") {
1748
2180
  const fromResolved = resolveValueAtPointer(baseJson, op.from, opIndex);
1749
2181
  if (!fromResolved.ok) return fromResolved;
1750
- const fromValue = fromResolved.value;
1751
- return applySinglePatchOp(state, baseDoc, baseJson, {
2182
+ return applySinglePatchOpSequentialStep(state, baseDoc, baseJson, headJson, {
1752
2183
  op: "add",
1753
2184
  path: op.path,
1754
- value: fromValue
1755
- }, options);
2185
+ value: structuredClone(fromResolved.value)
2186
+ }, options, explicitBaseState);
1756
2187
  }
1757
- return applySinglePatchOp(state, baseDoc, baseJson, op, options);
2188
+ return applySinglePatchOpSequentialStep(state, baseDoc, baseJson, headJson, op, options, explicitBaseState);
2189
+ }
2190
+ function applySinglePatchOpSequentialStep(state, baseDoc, baseJson, headJson, op, options, explicitBaseState) {
2191
+ const compiled = compileIntents(baseJson, [op], "sequential", options.jsonValidation ?? "none");
2192
+ if (!compiled.ok) return compiled;
2193
+ const headStep = applyIntentsToCrdt(baseDoc, state.doc, compiled.intents, () => state.clock.next(), options.testAgainst ?? "head", (ctr) => bumpClockCounter(state, ctr), { strictParents: options.strictParents });
2194
+ if (!headStep.ok) return headStep;
2195
+ if (explicitBaseState && op.op !== "test") {
2196
+ const shadowStep = applyIntentsToCrdt(explicitBaseState.doc, explicitBaseState.doc, compiled.intents, () => explicitBaseState.clock.next(), "base", (ctr) => bumpClockCounter(explicitBaseState, ctr), { strictParents: options.strictParents });
2197
+ if (!shadowStep.ok) return shadowStep;
2198
+ }
2199
+ if (op.op === "test") return {
2200
+ ok: true,
2201
+ baseJson,
2202
+ headJson
2203
+ };
2204
+ const nextBaseJson = applyJsonPatchOpToShadow(baseJson, op);
2205
+ return {
2206
+ ok: true,
2207
+ baseJson: nextBaseJson,
2208
+ headJson: explicitBaseState ? applyJsonPatchOpToShadow(headJson, op) : nextBaseJson
2209
+ };
2210
+ }
2211
+ function applySinglePatchOpExplicitShadowStep(explicitBaseState, baseJson, op, options) {
2212
+ const compiled = compileIntents(baseJson, [op], "sequential", options.jsonValidation ?? "none");
2213
+ if (!compiled.ok) return compiled;
2214
+ const shadowStep = applyIntentsToCrdt(explicitBaseState.doc, explicitBaseState.doc, compiled.intents, () => explicitBaseState.clock.next(), "base", (ctr) => bumpClockCounter(explicitBaseState, ctr), { strictParents: options.strictParents });
2215
+ if (!shadowStep.ok) return shadowStep;
2216
+ if (op.op === "test") return {
2217
+ ok: true,
2218
+ baseJson
2219
+ };
2220
+ return {
2221
+ ok: true,
2222
+ baseJson: applyJsonPatchOpToShadow(baseJson, op)
2223
+ };
2224
+ }
2225
+ function applyJsonPatchOpToShadow(baseJson, op) {
2226
+ const path = parseJsonPointer(op.path);
2227
+ if (path.length === 0) {
2228
+ if (op.op === "test") return baseJson;
2229
+ if (op.op === "remove") return null;
2230
+ return structuredClone(op.value);
2231
+ }
2232
+ const parentPath = path.slice(0, -1);
2233
+ const key = path[path.length - 1];
2234
+ const parent = getAtJson(baseJson, parentPath);
2235
+ if (Array.isArray(parent)) {
2236
+ const idx = key === "-" ? parent.length : Number(key);
2237
+ if (!Number.isInteger(idx)) throw new Error(`Invalid array index ${key}`);
2238
+ if (op.op === "add") {
2239
+ parent.splice(idx, 0, structuredClone(op.value));
2240
+ return baseJson;
2241
+ }
2242
+ if (op.op === "remove") {
2243
+ parent.splice(idx, 1);
2244
+ return baseJson;
2245
+ }
2246
+ if (op.op === "replace") {
2247
+ parent[idx] = structuredClone(op.value);
2248
+ return baseJson;
2249
+ }
2250
+ return baseJson;
2251
+ }
2252
+ const obj = parent;
2253
+ if (op.op === "add" || op.op === "replace") {
2254
+ obj[key] = structuredClone(op.value);
2255
+ return baseJson;
2256
+ }
2257
+ if (op.op === "remove") {
2258
+ delete obj[key];
2259
+ return baseJson;
2260
+ }
2261
+ return baseJson;
1758
2262
  }
1759
2263
  function resolveValueAtPointer(baseJson, pointer, opIndex) {
1760
2264
  let path;
@@ -1772,24 +2276,53 @@ function resolveValueAtPointer(baseJson, pointer, opIndex) {
1772
2276
  return toPointerLookupApplyError(error, pointer, opIndex);
1773
2277
  }
1774
2278
  }
1775
- function applySinglePatchOp(state, baseDoc, baseJson, op, options) {
1776
- const compiled = compileIntents(baseJson, [op], "sequential");
1777
- if (!compiled.ok) return compiled;
1778
- return applyIntentsToCrdt(baseDoc, state.doc, compiled.intents, () => state.clock.next(), options.testAgainst ?? "head", (ctr) => bumpClockCounter(state, ctr));
1779
- }
1780
2279
  function bumpClockCounter(state, ctr) {
1781
2280
  if (state.clock.ctr < ctr) state.clock.ctr = ctr;
1782
2281
  }
1783
- function compileIntents(baseJson, patch, semantics = "sequential") {
2282
+ function compileIntents(baseJson, patch, semantics = "sequential", jsonValidation = "none") {
1784
2283
  try {
1785
2284
  return {
1786
2285
  ok: true,
1787
- intents: compileJsonPatchToIntent(baseJson, patch, { semantics })
2286
+ intents: compileJsonPatchToIntent(baseJson, preparePatchPayloads(patch, jsonValidation), { semantics })
1788
2287
  };
1789
2288
  } catch (error) {
1790
2289
  return toApplyError(error);
1791
2290
  }
1792
2291
  }
2292
+ function preparePatchPayloads(patch, mode) {
2293
+ if (mode === "none") return patch;
2294
+ const out = [];
2295
+ for (const [opIndex, op] of patch.entries()) {
2296
+ if (op.op === "move" || op.op === "copy" || op.op === "remove") {
2297
+ out.push(op);
2298
+ continue;
2299
+ }
2300
+ if (mode === "strict") {
2301
+ try {
2302
+ assertRuntimeJsonValue(op.value);
2303
+ } catch (error) {
2304
+ if (error instanceof JsonValueValidationError) throw patchPayloadCompileError(op, opIndex, error);
2305
+ throw error;
2306
+ }
2307
+ out.push(op);
2308
+ continue;
2309
+ }
2310
+ out.push({
2311
+ ...op,
2312
+ value: coerceRuntimeJsonValue(op.value, mode)
2313
+ });
2314
+ }
2315
+ return out;
2316
+ }
2317
+ function patchPayloadCompileError(op, opIndex, error) {
2318
+ const path = mergePointerPaths(op.path, error.path);
2319
+ return new PatchCompileError("INVALID_PATCH", `invalid JSON value for '${op.op}' at ${path === "" ? "<root>" : path}: ${error.detail}`, path, opIndex);
2320
+ }
2321
+ function mergePointerPaths(basePointer, nestedPointer) {
2322
+ if (nestedPointer === "") return basePointer;
2323
+ if (basePointer === "") return nestedPointer;
2324
+ return `${basePointer}${nestedPointer}`;
2325
+ }
1793
2326
  function maxCtrInNodeForActor$1(node, actor) {
1794
2327
  let best = 0;
1795
2328
  const stack = [{
@@ -1866,6 +2399,17 @@ function toPointerLookupApplyError(error, pointer, opIndex) {
1866
2399
  //#endregion
1867
2400
  //#region src/serialize.ts
1868
2401
  const HEAD_ELEM_ID = "HEAD";
2402
+ function createSerializedRecord() {
2403
+ return Object.create(null);
2404
+ }
2405
+ function setSerializedRecordValue(out, key, value) {
2406
+ Object.defineProperty(out, key, {
2407
+ configurable: true,
2408
+ enumerable: true,
2409
+ value,
2410
+ writable: true
2411
+ });
2412
+ }
1869
2413
  var DeserializeError = class extends Error {
1870
2414
  code = 409;
1871
2415
  reason;
@@ -1887,6 +2431,22 @@ function deserializeDoc(data) {
1887
2431
  if (!("root" in data)) fail("INVALID_SERIALIZED_SHAPE", "/root", "serialized doc is missing root");
1888
2432
  return { root: deserializeNode(data.root, "/root", 0) };
1889
2433
  }
2434
+ /** Non-throwing `deserializeDoc` variant with typed validation details. */
2435
+ function tryDeserializeDoc(data) {
2436
+ try {
2437
+ return {
2438
+ ok: true,
2439
+ doc: deserializeDoc(data)
2440
+ };
2441
+ } catch (error) {
2442
+ const deserializeError = toDeserializeFailure(error);
2443
+ if (deserializeError) return {
2444
+ ok: false,
2445
+ error: deserializeError
2446
+ };
2447
+ throw error;
2448
+ }
2449
+ }
1890
2450
  /** Serialize a full CRDT state (document + clock) to a JSON-safe representation. */
1891
2451
  function serializeState(state) {
1892
2452
  return {
@@ -1909,6 +2469,22 @@ function deserializeState(data) {
1909
2469
  clock
1910
2470
  };
1911
2471
  }
2472
+ /** Non-throwing `deserializeState` variant with typed validation details. */
2473
+ function tryDeserializeState(data) {
2474
+ try {
2475
+ return {
2476
+ ok: true,
2477
+ state: deserializeState(data)
2478
+ };
2479
+ } catch (error) {
2480
+ const deserializeError = toDeserializeFailure(error);
2481
+ if (deserializeError) return {
2482
+ ok: false,
2483
+ error: deserializeError
2484
+ };
2485
+ throw error;
2486
+ }
2487
+ }
1912
2488
  function serializeNode(node) {
1913
2489
  if (node.kind === "lww") return {
1914
2490
  kind: "lww",
@@ -1919,27 +2495,27 @@ function serializeNode(node) {
1919
2495
  }
1920
2496
  };
1921
2497
  if (node.kind === "obj") {
1922
- const entries = {};
1923
- for (const [k, v] of node.entries.entries()) entries[k] = {
2498
+ const entries = createSerializedRecord();
2499
+ for (const [k, v] of node.entries.entries()) setSerializedRecordValue(entries, k, {
1924
2500
  node: serializeNode(v.node),
1925
2501
  dot: {
1926
2502
  actor: v.dot.actor,
1927
2503
  ctr: v.dot.ctr
1928
2504
  }
1929
- };
1930
- const tombstone = {};
1931
- for (const [k, d] of node.tombstone.entries()) tombstone[k] = {
2505
+ });
2506
+ const tombstone = createSerializedRecord();
2507
+ for (const [k, d] of node.tombstone.entries()) setSerializedRecordValue(tombstone, k, {
1932
2508
  actor: d.actor,
1933
2509
  ctr: d.ctr
1934
- };
2510
+ });
1935
2511
  return {
1936
2512
  kind: "obj",
1937
2513
  entries,
1938
2514
  tombstone
1939
2515
  };
1940
2516
  }
1941
- const elems = {};
1942
- for (const [id, e] of node.elems.entries()) elems[id] = {
2517
+ const elems = createSerializedRecord();
2518
+ for (const [id, e] of node.elems.entries()) setSerializedRecordValue(elems, id, {
1943
2519
  id: e.id,
1944
2520
  prev: e.prev,
1945
2521
  tombstone: e.tombstone,
@@ -1948,7 +2524,7 @@ function serializeNode(node) {
1948
2524
  actor: e.insDot.actor,
1949
2525
  ctr: e.insDot.ctr
1950
2526
  }
1951
- };
2527
+ });
1952
2528
  return {
1953
2529
  kind: "seq",
1954
2530
  elems
@@ -2012,11 +2588,32 @@ function deserializeNode(node, path, depth) {
2012
2588
  if (elem.prev === elem.id) fail("INVALID_SERIALIZED_INVARIANT", `${path}/elems/${elem.id}/prev`, "sequence element cannot reference itself as predecessor");
2013
2589
  if (elem.prev !== HEAD_ELEM_ID && !elems.has(elem.prev)) fail("INVALID_SERIALIZED_INVARIANT", `${path}/elems/${elem.id}/prev`, `sequence predecessor '${elem.prev}' does not exist`);
2014
2590
  }
2591
+ assertAcyclicRgaPredecessors(elems, path);
2015
2592
  return {
2016
2593
  kind: "seq",
2017
2594
  elems
2018
2595
  };
2019
2596
  }
2597
+ function assertAcyclicRgaPredecessors(elems, path) {
2598
+ const visitState = /* @__PURE__ */ new Map();
2599
+ for (const startId of elems.keys()) {
2600
+ if (visitState.get(startId) === 2) continue;
2601
+ const trail = [];
2602
+ const trailSet = /* @__PURE__ */ new Set();
2603
+ let currentId = startId;
2604
+ while (currentId) {
2605
+ if (trailSet.has(currentId)) fail("INVALID_SERIALIZED_INVARIANT", `${path}/elems/${currentId}/prev`, `sequence predecessor cycle detected at '${currentId}'`);
2606
+ if (visitState.get(currentId) === 2) break;
2607
+ trail.push(currentId);
2608
+ trailSet.add(currentId);
2609
+ visitState.set(currentId, 1);
2610
+ const elem = elems.get(currentId);
2611
+ if (!elem || elem.prev === HEAD_ELEM_ID) break;
2612
+ currentId = elem.prev;
2613
+ }
2614
+ for (const id of trail) visitState.set(id, 2);
2615
+ }
2616
+ }
2020
2617
  function asRecord(value, path) {
2021
2618
  if (!isRecord(value)) fail("INVALID_SERIALIZED_SHAPE", path, "expected object");
2022
2619
  return value;
@@ -2066,6 +2663,10 @@ function assertJsonValue(value, path, depth) {
2066
2663
  function fail(reason, path, message) {
2067
2664
  throw new DeserializeError(reason, path, message);
2068
2665
  }
2666
+ function toDeserializeFailure(error) {
2667
+ if (error instanceof DeserializeError || error instanceof TraversalDepthError) return error;
2668
+ return null;
2669
+ }
2069
2670
  function isRecord(value) {
2070
2671
  return typeof value === "object" && value !== null && !Array.isArray(value);
2071
2672
  }
@@ -2498,6 +3099,12 @@ function compactStateTombstones(state, options) {
2498
3099
  }
2499
3100
 
2500
3101
  //#endregion
3102
+ Object.defineProperty(exports, 'ClockValidationError', {
3103
+ enumerable: true,
3104
+ get: function () {
3105
+ return ClockValidationError;
3106
+ }
3107
+ });
2501
3108
  Object.defineProperty(exports, 'DeserializeError', {
2502
3109
  enumerable: true,
2503
3110
  get: function () {
@@ -2510,6 +3117,12 @@ Object.defineProperty(exports, 'HEAD', {
2510
3117
  return HEAD;
2511
3118
  }
2512
3119
  });
3120
+ Object.defineProperty(exports, 'JsonValueValidationError', {
3121
+ enumerable: true,
3122
+ get: function () {
3123
+ return JsonValueValidationError;
3124
+ }
3125
+ });
2513
3126
  Object.defineProperty(exports, 'MAX_TRAVERSAL_DEPTH', {
2514
3127
  enumerable: true,
2515
3128
  get: function () {
@@ -2840,12 +3453,30 @@ Object.defineProperty(exports, 'tryApplyPatch', {
2840
3453
  return tryApplyPatch;
2841
3454
  }
2842
3455
  });
3456
+ Object.defineProperty(exports, 'tryApplyPatchAsActor', {
3457
+ enumerable: true,
3458
+ get: function () {
3459
+ return tryApplyPatchAsActor;
3460
+ }
3461
+ });
2843
3462
  Object.defineProperty(exports, 'tryApplyPatchInPlace', {
2844
3463
  enumerable: true,
2845
3464
  get: function () {
2846
3465
  return tryApplyPatchInPlace;
2847
3466
  }
2848
3467
  });
3468
+ Object.defineProperty(exports, 'tryDeserializeDoc', {
3469
+ enumerable: true,
3470
+ get: function () {
3471
+ return tryDeserializeDoc;
3472
+ }
3473
+ });
3474
+ Object.defineProperty(exports, 'tryDeserializeState', {
3475
+ enumerable: true,
3476
+ get: function () {
3477
+ return tryDeserializeState;
3478
+ }
3479
+ });
2849
3480
  Object.defineProperty(exports, 'tryJsonPatchToCrdt', {
2850
3481
  enumerable: true,
2851
3482
  get: function () {