json-patch-to-crdt 0.0.0 → 0.1.1

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.
@@ -194,6 +194,19 @@ const ROOT_KEY = "@@crdt/root";
194
194
 
195
195
  //#endregion
196
196
  //#region src/patch.ts
197
+ /** Structured compile error used to map patch validation failures to typed reasons. */
198
+ var PatchCompileError = class extends Error {
199
+ reason;
200
+ path;
201
+ opIndex;
202
+ constructor(reason, message, path, opIndex) {
203
+ super(message);
204
+ this.name = "PatchCompileError";
205
+ this.reason = reason;
206
+ this.path = path;
207
+ this.opIndex = opIndex;
208
+ }
209
+ };
197
210
  /**
198
211
  * Parse an RFC 6901 JSON Pointer into a path array, unescaping `~1` and `~0`.
199
212
  * @param ptr - A JSON Pointer string (e.g. `"/a/b"` or `""`).
@@ -202,13 +215,37 @@ const ROOT_KEY = "@@crdt/root";
202
215
  function parseJsonPointer(ptr) {
203
216
  if (ptr === "") return [];
204
217
  if (!ptr.startsWith("/")) throw new Error(`Invalid pointer: ${ptr}`);
205
- return ptr.slice(1).split("/").map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
218
+ return ptr.slice(1).split("/").map(unescapeJsonPointerToken);
206
219
  }
207
220
  /** Convert a path array back to an RFC 6901 JSON Pointer string. */
208
221
  function stringifyJsonPointer(path) {
209
222
  if (path.length === 0) return "";
210
223
  return `/${path.map(escapeJsonPointer).join("/")}`;
211
224
  }
225
+ function unescapeJsonPointerToken(token) {
226
+ let out = "";
227
+ for (let i = 0; i < token.length; i++) {
228
+ const ch = token[i];
229
+ if (ch !== "~") {
230
+ out += ch;
231
+ continue;
232
+ }
233
+ const esc = token[i + 1];
234
+ if (esc === "0") {
235
+ out += "~";
236
+ i += 1;
237
+ continue;
238
+ }
239
+ if (esc === "1") {
240
+ out += "/";
241
+ i += 1;
242
+ continue;
243
+ }
244
+ const sequence = esc === void 0 ? "~" : `~${esc}`;
245
+ throw new Error(`Invalid pointer escape sequence '${sequence}'`);
246
+ }
247
+ return out;
248
+ }
212
249
  /**
213
250
  * Navigate a JSON value by path and return the value at that location.
214
251
  * Throws if the path is invalid, out of bounds, or traverses a non-container.
@@ -216,8 +253,8 @@ function stringifyJsonPointer(path) {
216
253
  function getAtJson(base, path) {
217
254
  let cur = base;
218
255
  for (const seg of path) if (Array.isArray(cur)) {
219
- const idx = seg === "-" ? cur.length : Number(seg);
220
- if (!Number.isInteger(idx)) throw new Error(`Expected array index, got ${seg}`);
256
+ if (!ARRAY_INDEX_TOKEN_PATTERN.test(seg)) throw new Error(`Expected array index, got ${seg}`);
257
+ const idx = Number(seg);
221
258
  if (idx < 0 || idx >= cur.length) throw new Error(`Index out of bounds at ${seg}`);
222
259
  cur = cur[idx];
223
260
  } else if (cur && typeof cur === "object") {
@@ -234,94 +271,15 @@ function getAtJson(base, path) {
234
271
  * @param patch - Array of JSON Patch operations.
235
272
  * @returns An array of `IntentOp` ready for `applyIntentsToCrdt`.
236
273
  */
237
- function compileJsonPatchToIntent(baseJson, patch) {
274
+ function compileJsonPatchToIntent(baseJson, patch, options = {}) {
275
+ const semantics = options.semantics ?? "sequential";
276
+ let workingBase = semantics === "sequential" ? structuredClone(baseJson) : baseJson;
238
277
  const intents = [];
239
- for (const op of patch) {
240
- if (op.op === "test") {
241
- intents.push({
242
- t: "Test",
243
- path: parseJsonPointer(op.path),
244
- value: op.value
245
- });
246
- continue;
247
- }
248
- if (op.op === "copy" || op.op === "move") {
249
- const val = getAtJson(baseJson, parseJsonPointer(op.from));
250
- intents.push(...compileJsonPatchToIntent(baseJson, [{
251
- op: "add",
252
- path: op.path,
253
- value: val
254
- }]));
255
- if (op.op === "move") intents.push(...compileJsonPatchToIntent(baseJson, [{
256
- op: "remove",
257
- path: op.from
258
- }]));
259
- continue;
260
- }
261
- const path = parseJsonPointer(op.path);
262
- const parent = path.slice(0, -1);
263
- const last = path[path.length - 1];
264
- if (path.length === 0) {
265
- if (op.op === "replace" || op.op === "add") intents.push({
266
- t: "ObjSet",
267
- path: [],
268
- key: ROOT_KEY,
269
- value: op.value
270
- });
271
- else if (op.op === "remove") intents.push({
272
- t: "ObjSet",
273
- path: [],
274
- key: ROOT_KEY,
275
- value: null
276
- });
277
- continue;
278
- }
279
- const isIndexLike = (s) => s === "-" || /^[0-9]+$/.test(s);
280
- if (isIndexLike(last)) {
281
- const index = last === "-" ? Number.POSITIVE_INFINITY : Number(last);
282
- if (op.op === "add") intents.push({
283
- t: "ArrInsert",
284
- path: parent,
285
- index,
286
- value: op.value
287
- });
288
- else if (op.op === "remove") intents.push({
289
- t: "ArrDelete",
290
- path: parent,
291
- index
292
- });
293
- else if (op.op === "replace") intents.push({
294
- t: "ArrReplace",
295
- path: parent,
296
- index,
297
- value: op.value
298
- });
299
- else assertNever$1(op, "Unsupported op at array index path");
300
- } else {
301
- const parentValue = pathValueAt(baseJson, parent);
302
- if (!isPlainObject(parentValue)) throw new Error(`Expected object parent at ${stringifyJsonPointer(parent)}`);
303
- if ((op.op === "replace" || op.op === "remove") && !hasOwn(parentValue, last)) throw new Error(`Missing key ${last} at ${stringifyJsonPointer(parent)}`);
304
- if (op.op === "add") intents.push({
305
- t: "ObjSet",
306
- path: parent,
307
- key: last,
308
- value: op.value,
309
- mode: "add"
310
- });
311
- else if (op.op === "replace") intents.push({
312
- t: "ObjSet",
313
- path: parent,
314
- key: last,
315
- value: op.value,
316
- mode: "replace"
317
- });
318
- else if (op.op === "remove") intents.push({
319
- t: "ObjRemove",
320
- path: parent,
321
- key: last
322
- });
323
- else assertNever$1(op, "Unsupported op");
324
- }
278
+ for (let opIndex = 0; opIndex < patch.length; opIndex++) {
279
+ const op = patch[opIndex];
280
+ const compileBase = semantics === "sequential" ? workingBase : baseJson;
281
+ intents.push(...compileSingleOp(compileBase, op, opIndex, semantics));
282
+ if (semantics === "sequential") workingBase = applyPatchOpToJson(workingBase, op, opIndex);
325
283
  }
326
284
  return intents;
327
285
  }
@@ -463,6 +421,7 @@ function jsonEquals(a, b) {
463
421
  function isPlainObject(value) {
464
422
  return typeof value === "object" && value !== null && !Array.isArray(value);
465
423
  }
424
+ const ARRAY_INDEX_TOKEN_PATTERN = /^(0|[1-9][0-9]*)$/;
466
425
  function hasOwn(value, key) {
467
426
  return Object.prototype.hasOwnProperty.call(value, key);
468
427
  }
@@ -473,6 +432,223 @@ function pathValueAt(base, path) {
473
432
  function assertNever$1(_value, message) {
474
433
  throw new Error(message);
475
434
  }
435
+ function compileSingleOp(baseJson, op, opIndex, semantics) {
436
+ if (op.op === "test") return [{
437
+ t: "Test",
438
+ path: parsePointerOrThrow(op.path, op.path, opIndex),
439
+ value: op.value
440
+ }];
441
+ if (op.op === "copy" || op.op === "move") {
442
+ const fromPath = parsePointerOrThrow(op.from, op.from, opIndex);
443
+ const toPath = parsePointerOrThrow(op.path, op.path, opIndex);
444
+ 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);
445
+ const val = lookupValueOrThrow(baseJson, fromPath, op.from, opIndex);
446
+ if (op.op === "move" && isSamePath(fromPath, toPath)) return [];
447
+ if (op.op === "move" && semantics === "sequential") {
448
+ const removeOp = {
449
+ op: "remove",
450
+ path: op.from
451
+ };
452
+ const addOp = {
453
+ op: "add",
454
+ path: op.path,
455
+ value: val
456
+ };
457
+ const baseAfterRemove = applyPatchOpToJson(baseJson, removeOp, opIndex);
458
+ return [...compileSingleOp(baseJson, removeOp, opIndex, semantics), ...compileSingleOp(baseAfterRemove, addOp, opIndex, semantics)];
459
+ }
460
+ const out = compileSingleOp(baseJson, {
461
+ op: "add",
462
+ path: op.path,
463
+ value: val
464
+ }, opIndex, semantics);
465
+ if (op.op === "move") out.push(...compileSingleOp(baseJson, {
466
+ op: "remove",
467
+ path: op.from
468
+ }, opIndex, semantics));
469
+ return out;
470
+ }
471
+ const path = parsePointerOrThrow(op.path, op.path, opIndex);
472
+ if (path.length === 0) {
473
+ if (op.op === "replace" || op.op === "add") return [{
474
+ t: "ObjSet",
475
+ path: [],
476
+ key: ROOT_KEY,
477
+ value: op.value
478
+ }];
479
+ throw compileError("INVALID_TARGET", "remove at root path is not supported in RFC-compliant mode", op.path, opIndex);
480
+ }
481
+ const parent = path.slice(0, -1);
482
+ const token = path[path.length - 1];
483
+ const parentPath = stringifyJsonPointer(parent);
484
+ const parentValue = getParentValue(baseJson, parent, opIndex);
485
+ if (Array.isArray(parentValue)) {
486
+ const index = parseArrayIndexToken(token, op.op, parentValue.length, op.path, opIndex);
487
+ if (op.op === "add") return [{
488
+ t: "ArrInsert",
489
+ path: parent,
490
+ index,
491
+ value: op.value
492
+ }];
493
+ if (op.op === "remove") return [{
494
+ t: "ArrDelete",
495
+ path: parent,
496
+ index
497
+ }];
498
+ if (op.op === "replace") return [{
499
+ t: "ArrReplace",
500
+ path: parent,
501
+ index,
502
+ value: op.value
503
+ }];
504
+ return assertNever$1(op, "Unsupported op at array path");
505
+ }
506
+ if (!isPlainObject(parentValue)) throw compileError("INVALID_TARGET", `expected object or array parent at ${parentPath}`, parentPath, opIndex);
507
+ if ((op.op === "replace" || op.op === "remove") && !hasOwn(parentValue, token)) throw compileError("MISSING_TARGET", `missing key ${token} at ${parentPath}`, op.path, opIndex);
508
+ if (op.op === "add") return [{
509
+ t: "ObjSet",
510
+ path: parent,
511
+ key: token,
512
+ value: op.value,
513
+ mode: "add"
514
+ }];
515
+ if (op.op === "replace") return [{
516
+ t: "ObjSet",
517
+ path: parent,
518
+ key: token,
519
+ value: op.value,
520
+ mode: "replace"
521
+ }];
522
+ if (op.op === "remove") return [{
523
+ t: "ObjRemove",
524
+ path: parent,
525
+ key: token
526
+ }];
527
+ return assertNever$1(op, "Unsupported op");
528
+ }
529
+ function applyPatchOpToJson(baseJson, op, opIndex) {
530
+ let doc = structuredClone(baseJson);
531
+ if (op.op === "test") return doc;
532
+ if (op.op === "copy" || op.op === "move") {
533
+ const fromPath = parsePointerOrThrow(op.from, op.from, opIndex);
534
+ const value = structuredClone(lookupValueOrThrow(doc, fromPath, op.from, opIndex));
535
+ if (op.op === "move") doc = applyPatchOpToJson(doc, {
536
+ op: "remove",
537
+ path: op.from
538
+ }, opIndex);
539
+ return applyPatchOpToJson(doc, {
540
+ op: "add",
541
+ path: op.path,
542
+ value
543
+ }, opIndex);
544
+ }
545
+ const path = parsePointerOrThrow(op.path, op.path, opIndex);
546
+ if (path.length === 0) {
547
+ if (op.op === "add" || op.op === "replace") return structuredClone(op.value);
548
+ throw compileError("INVALID_TARGET", "remove at root path is not supported in RFC-compliant mode", op.path, opIndex);
549
+ }
550
+ const parentPath = path.slice(0, -1);
551
+ const token = path[path.length - 1];
552
+ let parent;
553
+ if (parentPath.length === 0) parent = doc;
554
+ else parent = lookupValueOrThrow(doc, parentPath, op.path, opIndex);
555
+ if (Array.isArray(parent)) {
556
+ const index = parseArrayIndexToken(token, op.op, parent.length, op.path, opIndex);
557
+ if (op.op === "add") {
558
+ const insertAt = index === Number.POSITIVE_INFINITY ? parent.length : index;
559
+ parent.splice(insertAt, 0, structuredClone(op.value));
560
+ return doc;
561
+ }
562
+ if (op.op === "replace") {
563
+ parent[index] = structuredClone(op.value);
564
+ return doc;
565
+ }
566
+ parent.splice(index, 1);
567
+ return doc;
568
+ }
569
+ if (!isPlainObject(parent)) throw compileError("INVALID_TARGET", `expected object or array parent at ${stringifyJsonPointer(parentPath)}`, op.path, opIndex);
570
+ if (op.op === "add" || op.op === "replace") {
571
+ parent[token] = structuredClone(op.value);
572
+ return doc;
573
+ }
574
+ delete parent[token];
575
+ return doc;
576
+ }
577
+ function parsePointerOrThrow(ptr, path, opIndex) {
578
+ try {
579
+ return parseJsonPointer(ptr);
580
+ } catch (error) {
581
+ throw compileError("INVALID_POINTER", error instanceof Error ? error.message : "invalid pointer", path, opIndex);
582
+ }
583
+ }
584
+ function lookupValueOrThrow(baseJson, path, pointer, opIndex) {
585
+ try {
586
+ return getAtJson(baseJson, path);
587
+ } catch (error) {
588
+ throw compileErrorFromLookup(error, pointer, opIndex);
589
+ }
590
+ }
591
+ function getParentValue(baseJson, parent, opIndex) {
592
+ if (parent.length === 0) return baseJson;
593
+ try {
594
+ return pathValueAt(baseJson, parent);
595
+ } catch (error) {
596
+ throw compileErrorFromLookup(error, stringifyJsonPointer(parent), opIndex);
597
+ }
598
+ }
599
+ function parseArrayIndexToken(token, op, arrLength, path, opIndex) {
600
+ if (token === "-") {
601
+ if (op !== "add") throw compileError("INVALID_POINTER", `'-' index is only valid for add at ${path}`, path, opIndex);
602
+ return Number.POSITIVE_INFINITY;
603
+ }
604
+ if (!ARRAY_INDEX_TOKEN_PATTERN.test(token)) throw compileError("INVALID_POINTER", `expected array index at ${path}`, path, opIndex);
605
+ const index = Number(token);
606
+ if (!Number.isSafeInteger(index)) throw compileError("OUT_OF_BOUNDS", `array index is too large at ${path}`, path, opIndex);
607
+ if (op === "add") {
608
+ if (index > arrLength) throw compileError("OUT_OF_BOUNDS", `index out of bounds at ${path}; expected 0..${arrLength}`, path, opIndex);
609
+ } else if (index >= arrLength) throw compileError("OUT_OF_BOUNDS", `index out of bounds at ${path}; expected 0..${Math.max(arrLength - 1, 0)}`, path, opIndex);
610
+ return index;
611
+ }
612
+ function compileErrorFromLookup(error, path, opIndex) {
613
+ const mapped = mapLookupErrorToPatchReason(error);
614
+ return compileError(mapped.reason, mapped.message, path, opIndex);
615
+ }
616
+ function mapLookupErrorToPatchReason(error) {
617
+ const message = error instanceof Error ? error.message : "invalid path";
618
+ if (message.includes("Expected array index")) return {
619
+ reason: "INVALID_POINTER",
620
+ message
621
+ };
622
+ if (message.includes("Index out of bounds")) return {
623
+ reason: "OUT_OF_BOUNDS",
624
+ message
625
+ };
626
+ if (message.includes("Missing key")) return {
627
+ reason: "MISSING_PARENT",
628
+ message
629
+ };
630
+ if (message.includes("Cannot traverse into non-container")) return {
631
+ reason: "INVALID_TARGET",
632
+ message
633
+ };
634
+ return {
635
+ reason: "INVALID_PATCH",
636
+ message
637
+ };
638
+ }
639
+ function compileError(reason, message, path, opIndex) {
640
+ return new PatchCompileError(reason, message, path, opIndex);
641
+ }
642
+ function isStrictDescendantPath(from, to) {
643
+ if (to.length <= from.length) return false;
644
+ for (let i = 0; i < from.length; i++) if (from[i] !== to[i]) return false;
645
+ return true;
646
+ }
647
+ function isSamePath(a, b) {
648
+ if (a.length !== b.length) return false;
649
+ for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
650
+ return true;
651
+ }
476
652
 
477
653
  //#endregion
478
654
  //#region src/doc.ts
@@ -673,13 +849,17 @@ function applyTest(base, head, it, evalTestAgainst) {
673
849
  return {
674
850
  ok: false,
675
851
  code: 409,
676
- message: `test path missing at /${it.path.join("/")}`
852
+ reason: "MISSING_TARGET",
853
+ message: `test path missing at /${it.path.join("/")}`,
854
+ path: `/${it.path.join("/")}`
677
855
  };
678
856
  }
679
857
  if (!jsonEquals(got, it.value)) return {
680
858
  ok: false,
681
859
  code: 409,
682
- message: `test failed at /${it.path.join("/")}`
860
+ reason: "TEST_FAILED",
861
+ message: `test failed at /${it.path.join("/")}`,
862
+ path: `/${it.path.join("/")}`
683
863
  };
684
864
  return null;
685
865
  }
@@ -692,12 +872,16 @@ function applyObjSet(head, it, newDot) {
692
872
  if (!parentRes.ok) return {
693
873
  ok: false,
694
874
  code: 409,
695
- message: parentRes.message
875
+ reason: "MISSING_PARENT",
876
+ message: parentRes.message,
877
+ path: `/${it.path.join("/")}`
696
878
  };
697
879
  if (it.mode === "replace" && !parentRes.obj.entries.has(it.key)) return {
698
880
  ok: false,
699
881
  code: 409,
700
- message: `no value at /${[...it.path, it.key].join("/")}`
882
+ reason: "MISSING_TARGET",
883
+ message: `no value at /${[...it.path, it.key].join("/")}`,
884
+ path: `/${[...it.path, it.key].join("/")}`
701
885
  };
702
886
  const d = newDot();
703
887
  const parentObj = parentRes.obj;
@@ -709,12 +893,16 @@ function applyObjRemove(head, it, newDot) {
709
893
  if (!parentRes.ok) return {
710
894
  ok: false,
711
895
  code: 409,
712
- message: parentRes.message
896
+ reason: "MISSING_PARENT",
897
+ message: parentRes.message,
898
+ path: `/${it.path.join("/")}`
713
899
  };
714
900
  if (!parentRes.obj.entries.has(it.key)) return {
715
901
  ok: false,
716
902
  code: 409,
717
- message: `no value at /${[...it.path, it.key].join("/")}`
903
+ reason: "MISSING_TARGET",
904
+ message: `no value at /${[...it.path, it.key].join("/")}`,
905
+ path: `/${[...it.path, it.key].join("/")}`
718
906
  };
719
907
  const d = newDot();
720
908
  const parentObj = parentRes.obj;
@@ -734,7 +922,9 @@ function applyArrInsert(base, head, it, newDot, bumpCounterAbove) {
734
922
  return {
735
923
  ok: false,
736
924
  code: 409,
737
- message: `base array missing at /${it.path.join("/")}`
925
+ reason: "MISSING_PARENT",
926
+ message: `base array missing at /${it.path.join("/")}`,
927
+ path: `/${it.path.join("/")}`
738
928
  };
739
929
  }
740
930
  const headSeq = ensureSeqAtPath(head, it.path, newDot());
@@ -743,7 +933,9 @@ function applyArrInsert(base, head, it, newDot, bumpCounterAbove) {
743
933
  if (idx < 0 || idx > baseLen) return {
744
934
  ok: false,
745
935
  code: 409,
746
- message: `index out of bounds at /${it.path.join("/")}/${it.index}`
936
+ reason: "OUT_OF_BOUNDS",
937
+ message: `index out of bounds at /${it.path.join("/")}/${it.index}`,
938
+ path: `/${it.path.join("/")}/${it.index}`
747
939
  };
748
940
  const prev = idx === 0 ? HEAD : rgaIdAtIndex(baseSeq, idx - 1) ?? HEAD;
749
941
  const d = nextInsertDotForPrev(headSeq, prev, newDot, bumpCounterAbove);
@@ -767,14 +959,18 @@ function applyArrDelete(base, head, it, newDot) {
767
959
  if (!baseSeq) return {
768
960
  ok: false,
769
961
  code: 409,
770
- message: `base array missing at /${it.path.join("/")}`
962
+ reason: "MISSING_PARENT",
963
+ message: `base array missing at /${it.path.join("/")}`,
964
+ path: `/${it.path.join("/")}`
771
965
  };
772
966
  const headSeq = ensureSeqAtPath(head, it.path, d);
773
967
  const baseId = rgaIdAtIndex(baseSeq, it.index);
774
968
  if (!baseId) return {
775
969
  ok: false,
776
970
  code: 409,
777
- message: `no base element at index ${it.index}`
971
+ reason: "MISSING_TARGET",
972
+ message: `no base element at index ${it.index}`,
973
+ path: `/${it.path.join("/")}/${it.index}`
778
974
  };
779
975
  rgaDelete(headSeq, baseId);
780
976
  return null;
@@ -785,20 +981,26 @@ function applyArrReplace(base, head, it, newDot) {
785
981
  if (!baseSeq) return {
786
982
  ok: false,
787
983
  code: 409,
788
- message: `base array missing at /${it.path.join("/")}`
984
+ reason: "MISSING_PARENT",
985
+ message: `base array missing at /${it.path.join("/")}`,
986
+ path: `/${it.path.join("/")}`
789
987
  };
790
988
  const headSeq = ensureSeqAtPath(head, it.path, d);
791
989
  const baseId = rgaIdAtIndex(baseSeq, it.index);
792
990
  if (!baseId) return {
793
991
  ok: false,
794
992
  code: 409,
795
- message: `no base element at index ${it.index}`
993
+ reason: "MISSING_TARGET",
994
+ message: `no base element at index ${it.index}`,
995
+ path: `/${it.path.join("/")}/${it.index}`
796
996
  };
797
997
  const e = headSeq.elems.get(baseId);
798
998
  if (!e || e.tombstone) return {
799
999
  ok: false,
800
1000
  code: 409,
801
- message: `element already deleted at index ${it.index}`
1001
+ reason: "MISSING_TARGET",
1002
+ message: `element already deleted at index ${it.index}`,
1003
+ path: `/${it.path.join("/")}/${it.index}`
802
1004
  };
803
1005
  e.value = nodeFromJson(it.value, newDot);
804
1006
  return null;
@@ -842,34 +1044,39 @@ function applyIntentsToCrdt(base, head, intents, newDot, evalTestAgainst = "head
842
1044
  }
843
1045
  return { ok: true };
844
1046
  }
845
- /**
846
- * Convenience wrapper: compile a JSON Patch and apply it to a CRDT document.
847
- * @param base - The base document for index resolution.
848
- * @param head - The target document to mutate.
849
- * @param patch - Array of RFC 6902 JSON Patch operations.
850
- * @param newDot - A function that generates a unique `Dot` per mutation.
851
- * @param evalTestAgainst - Whether `test` ops evaluate against `"head"` or `"base"`.
852
- * @param bumpCounterAbove - Optional hook that can fast-forward the underlying counter before inserts.
853
- * @returns `{ ok: true }` on success, or `{ ok: false, code: 409, message }` on conflict.
854
- */
855
- function jsonPatchToCrdt(base, head, patch, newDot, evalTestAgainst = "head", bumpCounterAbove) {
856
- return applyIntentsToCrdt(base, head, compileJsonPatchToIntent(materialize(base.root), patch), newDot, evalTestAgainst, bumpCounterAbove);
1047
+ function jsonPatchToCrdt(baseOrOptions, head, patch, newDot, evalTestAgainst = "head", bumpCounterAbove) {
1048
+ if (isJsonPatchToCrdtOptions(baseOrOptions)) return jsonPatchToCrdtInternal(baseOrOptions);
1049
+ if (!head || !patch || !newDot) return {
1050
+ ok: false,
1051
+ code: 409,
1052
+ reason: "INVALID_PATCH",
1053
+ message: "invalid jsonPatchToCrdt call signature"
1054
+ };
1055
+ return jsonPatchToCrdtInternal({
1056
+ base: baseOrOptions,
1057
+ head,
1058
+ patch,
1059
+ newDot,
1060
+ evalTestAgainst,
1061
+ bumpCounterAbove
1062
+ });
857
1063
  }
858
- /**
859
- * Safe wrapper around `jsonPatchToCrdt` that converts compile-time errors into `409` results.
860
- * This function never throws for malformed/invalid patch paths.
861
- */
862
- function jsonPatchToCrdtSafe(base, head, patch, newDot, evalTestAgainst = "head", bumpCounterAbove) {
1064
+ function jsonPatchToCrdtSafe(baseOrOptions, head, patch, newDot, evalTestAgainst = "head", bumpCounterAbove) {
863
1065
  try {
864
- return jsonPatchToCrdt(base, head, patch, newDot, evalTestAgainst, bumpCounterAbove);
865
- } catch (error) {
866
- return {
1066
+ if (isJsonPatchToCrdtOptions(baseOrOptions)) return jsonPatchToCrdt(baseOrOptions);
1067
+ if (!head || !patch || !newDot) return {
867
1068
  ok: false,
868
1069
  code: 409,
869
- message: error instanceof Error ? error.message : "failed to compile patch"
1070
+ reason: "INVALID_PATCH",
1071
+ message: "invalid jsonPatchToCrdtSafe call signature"
870
1072
  };
1073
+ return jsonPatchToCrdt(baseOrOptions, head, patch, newDot, evalTestAgainst, bumpCounterAbove);
1074
+ } catch (error) {
1075
+ return toApplyError$1(error);
871
1076
  }
872
1077
  }
1078
+ /** Alias for codebases that prefer `try*` naming for non-throwing APIs. */
1079
+ const tryJsonPatchToCrdt = jsonPatchToCrdtSafe;
873
1080
  /**
874
1081
  * Generate a JSON Patch delta between two CRDT documents.
875
1082
  * @param base - The base document snapshot.
@@ -891,19 +1098,130 @@ function crdtToFullReplace(doc) {
891
1098
  value: materialize(doc.root)
892
1099
  }];
893
1100
  }
1101
+ function jsonPatchToCrdtInternal(options) {
1102
+ const evalTestAgainst = options.evalTestAgainst ?? "head";
1103
+ if ((options.semantics ?? "sequential") === "base") {
1104
+ const baseJson = materialize(options.base.root);
1105
+ let intents;
1106
+ try {
1107
+ intents = compileJsonPatchToIntent(baseJson, options.patch, { semantics: "base" });
1108
+ } catch (error) {
1109
+ return toApplyError$1(error);
1110
+ }
1111
+ return applyIntentsToCrdt(options.base, options.head, intents, options.newDot, evalTestAgainst, options.bumpCounterAbove);
1112
+ }
1113
+ let shadowBase = cloneDoc(evalTestAgainst === "base" ? options.base : options.head);
1114
+ let shadowCtr = 0;
1115
+ const shadowDot = () => ({
1116
+ actor: "__shadow__",
1117
+ ctr: ++shadowCtr
1118
+ });
1119
+ const shadowBump = (ctr) => {
1120
+ if (shadowCtr < ctr) shadowCtr = ctr;
1121
+ };
1122
+ const applySequentialOp = (op, opIndex) => {
1123
+ const baseJson = materialize(shadowBase.root);
1124
+ let intents;
1125
+ try {
1126
+ intents = compileJsonPatchToIntent(baseJson, [op], { semantics: "sequential" });
1127
+ } catch (error) {
1128
+ return withOpIndex(toApplyError$1(error), opIndex);
1129
+ }
1130
+ const headStep = applyIntentsToCrdt(shadowBase, options.head, intents, options.newDot, evalTestAgainst, options.bumpCounterAbove);
1131
+ if (!headStep.ok) return withOpIndex(headStep, opIndex);
1132
+ if (evalTestAgainst === "base") {
1133
+ const shadowStep = applyIntentsToCrdt(shadowBase, shadowBase, intents, shadowDot, "base", shadowBump);
1134
+ if (!shadowStep.ok) return withOpIndex(shadowStep, opIndex);
1135
+ } else shadowBase = cloneDoc(options.head);
1136
+ return { ok: true };
1137
+ };
1138
+ for (let opIndex = 0; opIndex < options.patch.length; opIndex++) {
1139
+ const op = options.patch[opIndex];
1140
+ if (op.op === "move") {
1141
+ const baseJson = materialize(shadowBase.root);
1142
+ let fromValue;
1143
+ try {
1144
+ fromValue = structuredClone(getAtJson(baseJson, parseJsonPointer(op.from)));
1145
+ } catch {
1146
+ try {
1147
+ compileJsonPatchToIntent(baseJson, [{
1148
+ op: "remove",
1149
+ path: op.from
1150
+ }], { semantics: "sequential" });
1151
+ } catch (error) {
1152
+ return withOpIndex(toApplyError$1(error), opIndex);
1153
+ }
1154
+ return withOpIndex(toApplyError$1(/* @__PURE__ */ new Error(`failed to resolve move source at ${op.from}`)), opIndex);
1155
+ }
1156
+ if (op.from === op.path) continue;
1157
+ const removeStep = applySequentialOp({
1158
+ op: "remove",
1159
+ path: op.from
1160
+ }, opIndex);
1161
+ if (!removeStep.ok) return removeStep;
1162
+ const addStep = applySequentialOp({
1163
+ op: "add",
1164
+ path: op.path,
1165
+ value: fromValue
1166
+ }, opIndex);
1167
+ if (!addStep.ok) return addStep;
1168
+ continue;
1169
+ }
1170
+ const step = applySequentialOp(op, opIndex);
1171
+ if (!step.ok) return step;
1172
+ }
1173
+ return { ok: true };
1174
+ }
1175
+ function withOpIndex(error, opIndex) {
1176
+ if (error.opIndex !== void 0) return error;
1177
+ return {
1178
+ ...error,
1179
+ opIndex
1180
+ };
1181
+ }
1182
+ function isJsonPatchToCrdtOptions(value) {
1183
+ return typeof value === "object" && value !== null && "base" in value && "head" in value && "patch" in value && "newDot" in value;
1184
+ }
1185
+ function toApplyError$1(error) {
1186
+ if (error instanceof PatchCompileError) return {
1187
+ ok: false,
1188
+ code: 409,
1189
+ reason: error.reason,
1190
+ message: error.message,
1191
+ path: error.path,
1192
+ opIndex: error.opIndex
1193
+ };
1194
+ return {
1195
+ ok: false,
1196
+ code: 409,
1197
+ reason: "INVALID_PATCH",
1198
+ message: error instanceof Error ? error.message : "failed to compile/apply patch"
1199
+ };
1200
+ }
894
1201
  function assertNever(_value, message) {
895
1202
  throw new Error(message);
896
1203
  }
897
1204
 
898
1205
  //#endregion
899
1206
  //#region src/state.ts
900
- /** Error thrown when a JSON Patch cannot be applied. Includes a numeric `.code` (409 for conflicts). */
1207
+ /** Error thrown when a JSON Patch cannot be applied. Includes structured conflict metadata. */
901
1208
  var PatchError = class extends Error {
902
1209
  code;
903
- constructor(message, code = 409) {
904
- super(message);
1210
+ reason;
1211
+ path;
1212
+ opIndex;
1213
+ constructor(errorOrMessage, code = 409, reason = "INVALID_PATCH") {
1214
+ super(typeof errorOrMessage === "string" ? errorOrMessage : errorOrMessage.message);
905
1215
  this.name = "PatchError";
906
- this.code = code;
1216
+ if (typeof errorOrMessage === "string") {
1217
+ this.code = code;
1218
+ this.reason = reason;
1219
+ return;
1220
+ }
1221
+ this.code = errorOrMessage.code;
1222
+ this.reason = errorOrMessage.reason;
1223
+ this.path = errorOrMessage.path;
1224
+ this.opIndex = errorOrMessage.opIndex;
907
1225
  }
908
1226
  };
909
1227
  /**
@@ -920,6 +1238,18 @@ function createState(initial, options) {
920
1238
  };
921
1239
  }
922
1240
  /**
1241
+ * Fork a replica from a shared origin state while assigning a new local actor ID.
1242
+ * The forked state has an independent document clone and clock.
1243
+ * By default this rejects actor reuse to prevent duplicate-dot collisions across peers.
1244
+ */
1245
+ function forkState(origin, actor, options = {}) {
1246
+ if (actor === origin.clock.actor && !options.allowActorReuse) throw new Error(`forkState actor must be unique; refusing to reuse origin actor '${actor}'`);
1247
+ return {
1248
+ doc: cloneDoc(origin.doc),
1249
+ clock: createClock(actor, origin.clock.ctr)
1250
+ };
1251
+ }
1252
+ /**
923
1253
  * Materialize a CRDT document or state back to a plain JSON value.
924
1254
  * @param target - A `Doc` or `CrdtState` to materialize.
925
1255
  * @returns The JSON representation of the current document.
@@ -933,34 +1263,69 @@ function toJson(target) {
933
1263
  * Throws `PatchError` on conflict (e.g. out-of-bounds index, failed test op).
934
1264
  * @param state - The current CRDT state.
935
1265
  * @param patch - Array of RFC 6902 JSON Patch operations.
936
- * @param options - Optional base document and test evaluation mode.
1266
+ * @param options - Optional base state snapshot and patch semantics.
937
1267
  * @returns A new `CrdtState` with the patch applied.
938
1268
  */
939
1269
  function applyPatch(state, patch, options = {}) {
940
- const nextState = {
941
- doc: cloneDoc(state.doc),
942
- clock: cloneClock(state.clock)
943
- };
944
- const result = applyPatchInternal(nextState, patch, options);
945
- if (!result.ok) throw new PatchError(result.message, result.code);
946
- return nextState;
1270
+ const result = tryApplyPatch(state, patch, options);
1271
+ if (!result.ok) throw new PatchError(result.error);
1272
+ return result.state;
947
1273
  }
948
1274
  /**
949
1275
  * Apply a JSON Patch to the state in place, mutating the existing state.
950
1276
  * Throws `PatchError` on conflict.
951
1277
  * @param state - The CRDT state to mutate.
952
1278
  * @param patch - Array of RFC 6902 JSON Patch operations.
953
- * @param options - Optional base document and test evaluation mode.
1279
+ * @param options - Optional base state snapshot, patch semantics, and atomicity.
954
1280
  */
955
1281
  function applyPatchInPlace(state, patch, options = {}) {
956
- if (options.atomic ?? true) {
957
- const next = applyPatch(state, patch, options);
958
- state.doc = next.doc;
959
- state.clock = next.clock;
960
- return;
1282
+ const result = tryApplyPatchInPlace(state, patch, options);
1283
+ if (!result.ok) throw new PatchError(result.error);
1284
+ }
1285
+ /** Non-throwing immutable patch application variant. */
1286
+ function tryApplyPatch(state, patch, options = {}) {
1287
+ const nextState = {
1288
+ doc: cloneDoc(state.doc),
1289
+ clock: cloneClock(state.clock)
1290
+ };
1291
+ const result = applyPatchInternal(nextState, patch, options);
1292
+ if (!result.ok) return {
1293
+ ok: false,
1294
+ error: result
1295
+ };
1296
+ return {
1297
+ ok: true,
1298
+ state: nextState
1299
+ };
1300
+ }
1301
+ /** Non-throwing in-place patch application variant. */
1302
+ function tryApplyPatchInPlace(state, patch, options = {}) {
1303
+ const { atomic = true, ...applyOptions } = options;
1304
+ if (atomic) {
1305
+ const next = tryApplyPatch(state, patch, applyOptions);
1306
+ if (!next.ok) return next;
1307
+ state.doc = next.state.doc;
1308
+ state.clock = next.state.clock;
1309
+ return { ok: true };
961
1310
  }
962
- const result = applyPatchInternal(state, patch, options);
963
- if (!result.ok) throw new PatchError(result.message, result.code);
1311
+ const result = applyPatchInternal(state, patch, applyOptions);
1312
+ if (!result.ok) return {
1313
+ ok: false,
1314
+ error: result
1315
+ };
1316
+ return { ok: true };
1317
+ }
1318
+ /**
1319
+ * Validate whether a patch is applicable against a JSON base value under the chosen options.
1320
+ * Does not mutate caller-provided values.
1321
+ */
1322
+ function validateJsonPatch(base, patch, options = {}) {
1323
+ const result = tryApplyPatch(createState(base, { actor: "__validate__" }), patch, options);
1324
+ if (!result.ok) return {
1325
+ ok: false,
1326
+ error: result.error
1327
+ };
1328
+ return { ok: true };
964
1329
  }
965
1330
  /**
966
1331
  * Apply a JSON Patch as a specific actor while maintaining an external version vector.
@@ -971,7 +1336,7 @@ function applyPatchAsActor(doc, vv, actor, patch, options = {}) {
971
1336
  const state = applyPatch({
972
1337
  doc,
973
1338
  clock: createClock(actor, Math.max(vv[actor] ?? 0, observedCtr))
974
- }, patch, options);
1339
+ }, patch, toApplyPatchOptionsForActor(options));
975
1340
  return {
976
1341
  state,
977
1342
  vv: {
@@ -980,34 +1345,46 @@ function applyPatchAsActor(doc, vv, actor, patch, options = {}) {
980
1345
  }
981
1346
  };
982
1347
  }
1348
+ function toApplyPatchOptionsForActor(options) {
1349
+ return {
1350
+ semantics: options.semantics,
1351
+ testAgainst: options.testAgainst,
1352
+ base: options.base ? {
1353
+ doc: options.base,
1354
+ clock: createClock("__base__", 0)
1355
+ } : void 0
1356
+ };
1357
+ }
983
1358
  function applyPatchInternal(state, patch, options) {
984
- if ((options.semantics ?? "base") === "sequential") {
1359
+ if ((options.semantics ?? "sequential") === "sequential") {
985
1360
  const explicitBaseState = options.base ? {
986
- doc: cloneDoc(options.base),
1361
+ doc: cloneDoc(options.base.doc),
987
1362
  clock: createClock("__base__", 0)
988
1363
  } : null;
989
- for (const op of patch) {
990
- const step = applyPatchOpSequential(state, op, options, explicitBaseState ? explicitBaseState.doc : cloneDoc(state.doc));
1364
+ for (const [opIndex, op] of patch.entries()) {
1365
+ const step = applyPatchOpSequential(state, op, options, explicitBaseState ? explicitBaseState.doc : cloneDoc(state.doc), opIndex);
991
1366
  if (!step.ok) return step;
992
- if (explicitBaseState) {
1367
+ if (explicitBaseState && op.op !== "test") {
993
1368
  const baseStep = applyPatchInternal(explicitBaseState, [op], {
994
1369
  semantics: "sequential",
995
- testAgainst: options.testAgainst
1370
+ testAgainst: "base"
996
1371
  });
997
1372
  if (!baseStep.ok) return baseStep;
998
1373
  }
999
1374
  }
1000
1375
  return { ok: true };
1001
1376
  }
1002
- const baseDoc = options.base ? options.base : cloneDoc(state.doc);
1003
- const compiled = compileIntents(materialize(baseDoc.root), patch);
1377
+ const baseDoc = options.base ? options.base.doc : cloneDoc(state.doc);
1378
+ const compiled = compileIntents(materialize(baseDoc.root), patch, "base");
1004
1379
  if (!compiled.ok) return compiled;
1005
1380
  return applyIntentsToCrdt(baseDoc, state.doc, compiled.intents, () => state.clock.next(), options.testAgainst ?? "head", (ctr) => bumpClockCounter(state, ctr));
1006
1381
  }
1007
- function applyPatchOpSequential(state, op, options, baseDoc) {
1382
+ function applyPatchOpSequential(state, op, options, baseDoc, opIndex) {
1008
1383
  const baseJson = materialize(baseDoc.root);
1009
1384
  if (op.op === "move") {
1010
- const fromValue = getAtJson(baseJson, parseJsonPointer(op.from));
1385
+ const fromResolved = resolveValueAtPointer(baseJson, op.from, opIndex);
1386
+ if (!fromResolved.ok) return fromResolved;
1387
+ const fromValue = fromResolved.value;
1011
1388
  const removeRes = applySinglePatchOp(state, baseDoc, {
1012
1389
  op: "remove",
1013
1390
  path: op.from
@@ -1020,7 +1397,9 @@ function applyPatchOpSequential(state, op, options, baseDoc) {
1020
1397
  }, options);
1021
1398
  }
1022
1399
  if (op.op === "copy") {
1023
- const fromValue = getAtJson(baseJson, parseJsonPointer(op.from));
1400
+ const fromResolved = resolveValueAtPointer(baseJson, op.from, opIndex);
1401
+ if (!fromResolved.ok) return fromResolved;
1402
+ const fromValue = fromResolved.value;
1024
1403
  return applySinglePatchOp(state, baseDoc, {
1025
1404
  op: "add",
1026
1405
  path: op.path,
@@ -1029,26 +1408,38 @@ function applyPatchOpSequential(state, op, options, baseDoc) {
1029
1408
  }
1030
1409
  return applySinglePatchOp(state, baseDoc, op, options);
1031
1410
  }
1411
+ function resolveValueAtPointer(baseJson, pointer, opIndex) {
1412
+ let path;
1413
+ try {
1414
+ path = parseJsonPointer(pointer);
1415
+ } catch (error) {
1416
+ return toPointerParseApplyError(error, pointer, opIndex);
1417
+ }
1418
+ try {
1419
+ return {
1420
+ ok: true,
1421
+ value: getAtJson(baseJson, path)
1422
+ };
1423
+ } catch (error) {
1424
+ return toPointerLookupApplyError(error, pointer, opIndex);
1425
+ }
1426
+ }
1032
1427
  function applySinglePatchOp(state, baseDoc, op, options) {
1033
- const compiled = compileIntents(materialize(baseDoc.root), [op]);
1428
+ const compiled = compileIntents(materialize(baseDoc.root), [op], "sequential");
1034
1429
  if (!compiled.ok) return compiled;
1035
1430
  return applyIntentsToCrdt(baseDoc, state.doc, compiled.intents, () => state.clock.next(), options.testAgainst ?? "head", (ctr) => bumpClockCounter(state, ctr));
1036
1431
  }
1037
1432
  function bumpClockCounter(state, ctr) {
1038
1433
  if (state.clock.ctr < ctr) state.clock.ctr = ctr;
1039
1434
  }
1040
- function compileIntents(baseJson, patch) {
1435
+ function compileIntents(baseJson, patch, semantics = "sequential") {
1041
1436
  try {
1042
1437
  return {
1043
1438
  ok: true,
1044
- intents: compileJsonPatchToIntent(baseJson, patch)
1439
+ intents: compileJsonPatchToIntent(baseJson, patch, { semantics })
1045
1440
  };
1046
1441
  } catch (error) {
1047
- return {
1048
- ok: false,
1049
- code: 409,
1050
- message: error instanceof Error ? error.message : "failed to compile patch"
1051
- };
1442
+ return toApplyError(error);
1052
1443
  }
1053
1444
  }
1054
1445
  function maxCtrInNodeForActor$1(node, actor) {
@@ -1075,6 +1466,43 @@ function maxCtrInNodeForActor$1(node, actor) {
1075
1466
  }
1076
1467
  }
1077
1468
  }
1469
+ function toApplyError(error) {
1470
+ if (error instanceof PatchCompileError) return {
1471
+ ok: false,
1472
+ code: 409,
1473
+ reason: error.reason,
1474
+ message: error.message,
1475
+ path: error.path,
1476
+ opIndex: error.opIndex
1477
+ };
1478
+ return {
1479
+ ok: false,
1480
+ code: 409,
1481
+ reason: "INVALID_PATCH",
1482
+ message: error instanceof Error ? error.message : "failed to compile patch"
1483
+ };
1484
+ }
1485
+ function toPointerParseApplyError(error, pointer, opIndex) {
1486
+ return {
1487
+ ok: false,
1488
+ code: 409,
1489
+ reason: "INVALID_POINTER",
1490
+ message: error instanceof Error ? error.message : "invalid pointer",
1491
+ path: pointer,
1492
+ opIndex
1493
+ };
1494
+ }
1495
+ function toPointerLookupApplyError(error, pointer, opIndex) {
1496
+ const mapped = mapLookupErrorToPatchReason(error);
1497
+ return {
1498
+ ok: false,
1499
+ code: 409,
1500
+ reason: mapped.reason,
1501
+ message: mapped.message,
1502
+ path: pointer,
1503
+ opIndex
1504
+ };
1505
+ }
1078
1506
 
1079
1507
  //#endregion
1080
1508
  //#region src/serialize.ts
@@ -1197,6 +1625,19 @@ function deserializeNode(node) {
1197
1625
 
1198
1626
  //#endregion
1199
1627
  //#region src/merge.ts
1628
+ /** Error thrown by throwing merge helpers (`mergeDoc` / `mergeState`). */
1629
+ var MergeError = class extends Error {
1630
+ code;
1631
+ reason;
1632
+ path;
1633
+ constructor(error) {
1634
+ super(error.message);
1635
+ this.name = "MergeError";
1636
+ this.code = error.code;
1637
+ this.reason = "LINEAGE_MISMATCH";
1638
+ this.path = error.path;
1639
+ }
1640
+ };
1200
1641
  /**
1201
1642
  * Merge two CRDT documents from different peers into one.
1202
1643
  * By default this requires shared array lineage for non-empty sequences.
@@ -1211,9 +1652,27 @@ function deserializeNode(node) {
1211
1652
  * - **Kind mismatch**: the node with the higher "representative dot" wins and replaces the other entirely.
1212
1653
  */
1213
1654
  function mergeDoc(a, b, options = {}) {
1655
+ const result = tryMergeDoc(a, b, options);
1656
+ if (!result.ok) throw new MergeError(result.error);
1657
+ return result.doc;
1658
+ }
1659
+ /** Non-throwing `mergeDoc` variant with structured conflict details. */
1660
+ function tryMergeDoc(a, b, options = {}) {
1214
1661
  const mismatchPath = options.requireSharedOrigin ?? true ? findSeqLineageMismatch(a.root, b.root, []) : null;
1215
- if (mismatchPath) throw new Error(`merge requires shared array origin at ${mismatchPath}`);
1216
- return { root: mergeNode(a.root, b.root) };
1662
+ if (mismatchPath) return {
1663
+ ok: false,
1664
+ error: {
1665
+ ok: false,
1666
+ code: 409,
1667
+ reason: "LINEAGE_MISMATCH",
1668
+ message: `merge requires shared array origin at ${mismatchPath}`,
1669
+ path: mismatchPath
1670
+ }
1671
+ };
1672
+ return {
1673
+ ok: true,
1674
+ doc: { root: mergeNode(a.root, b.root) }
1675
+ };
1217
1676
  }
1218
1677
  /**
1219
1678
  * Merge two CRDT states.
@@ -1227,11 +1686,22 @@ function mergeDoc(a, b, options = {}) {
1227
1686
  * that actor across both input clocks and the merged document dots.
1228
1687
  */
1229
1688
  function mergeState(a, b, options = {}) {
1230
- const doc = mergeDoc(a.doc, b.doc, { requireSharedOrigin: options.requireSharedOrigin });
1689
+ const result = tryMergeState(a, b, options);
1690
+ if (!result.ok) throw new MergeError(result.error);
1691
+ return result.state;
1692
+ }
1693
+ /** Non-throwing `mergeState` variant with structured conflict details. */
1694
+ function tryMergeState(a, b, options = {}) {
1695
+ const mergedDoc = tryMergeDoc(a.doc, b.doc, { requireSharedOrigin: options.requireSharedOrigin });
1696
+ if (!mergedDoc.ok) return mergedDoc;
1697
+ const doc = mergedDoc.doc;
1231
1698
  const actor = options.actor ?? a.clock.actor;
1232
1699
  return {
1233
- doc,
1234
- clock: createClock(actor, maxObservedCtrForActor(doc, actor, a, b))
1700
+ ok: true,
1701
+ state: {
1702
+ doc,
1703
+ clock: createClock(actor, maxObservedCtrForActor(doc, actor, a, b))
1704
+ }
1235
1705
  };
1236
1706
  }
1237
1707
  function findSeqLineageMismatch(a, b, path) {
@@ -1432,4 +1902,4 @@ function cloneNodeShallow(node) {
1432
1902
  }
1433
1903
 
1434
1904
  //#endregion
1435
- export { newReg as A, rgaPrevForInsertAtIndex as B, getAtJson as C, ROOT_KEY as D, stringifyJsonPointer as E, HEAD as F, cloneClock as G, dotToElemId as H, rgaDelete as I, observeDot as J, createClock as K, rgaIdAtIndex as L, objRemove as M, objSet as N, lwwSet as O, materialize as P, rgaInsertAfter as R, diffJsonPatch as S, parseJsonPointer as T, vvHasDot as U, compareDot as V, vvMerge as W, docFromJson as _, serializeDoc as a, jsonPatchToCrdtSafe as b, applyPatch as c, createState as d, toJson as f, crdtToJsonPatch as g, crdtToFullReplace as h, deserializeState as i, newSeq as j, newObj as k, applyPatchAsActor as l, cloneDoc as m, mergeState as n, serializeState as o, applyIntentsToCrdt as p, nextDotForActor as q, deserializeDoc as r, PatchError as s, mergeDoc as t, applyPatchInPlace as u, docFromJsonWithDot as v, jsonEquals as w, compileJsonPatchToIntent as x, jsonPatchToCrdt as y, rgaLinearizeIds as z };
1905
+ export { vvMerge as $, compileJsonPatchToIntent as A, newSeq as B, crdtToJsonPatch as C, jsonPatchToCrdtSafe as D, jsonPatchToCrdt as E, stringifyJsonPointer as F, rgaDelete as G, objSet as H, ROOT_KEY as I, rgaLinearizeIds as J, rgaIdAtIndex as K, lwwSet as L, getAtJson as M, jsonEquals as N, tryJsonPatchToCrdt as O, parseJsonPointer as P, vvHasDot as Q, newObj as R, crdtToFullReplace as S, docFromJsonWithDot as T, materialize as U, objRemove as V, HEAD as W, compareDot as X, rgaPrevForInsertAtIndex as Y, dotToElemId as Z, tryApplyPatch as _, tryMergeState as a, applyIntentsToCrdt as b, serializeDoc as c, applyPatch as d, cloneClock as et, applyPatchAsActor as f, toJson as g, forkState as h, tryMergeDoc as i, diffJsonPatch as j, PatchCompileError as k, serializeState as l, createState as m, mergeDoc as n, nextDotForActor as nt, deserializeDoc as o, applyPatchInPlace as p, rgaInsertAfter as q, mergeState as r, observeDot as rt, deserializeState as s, MergeError as t, createClock as tt, PatchError as u, tryApplyPatchInPlace as v, docFromJson as w, cloneDoc as x, validateJsonPatch as y, newReg as z };