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.
- package/README.md +69 -386
- package/dist/{compact-BJBGW9tC.mjs → compact-BE9UsxEo.mjs} +794 -193
- package/dist/{compact-CkLd4Yh5.js → compact-DrmgKiVW.js} +823 -192
- package/dist/{depth-p6fX9Ak7.d.ts → depth-Cd3nyHWy.d.mts} +113 -5
- package/dist/{depth-wDeQ1hO1.d.mts → depth-tcJ8L1dj.d.ts} +113 -5
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -1
- package/dist/index.mjs +2 -2
- package/dist/internals.d.mts +13 -24
- package/dist/internals.d.ts +13 -24
- package/dist/internals.js +6 -1
- package/dist/internals.mjs +2 -2
- package/package.json +1 -1
|
@@ -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
|
|
32
|
-
vv
|
|
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
|
|
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
|
|
119
|
+
return (readVvCounter(vv, d.actor) ?? 0) >= d.ctr;
|
|
78
120
|
}
|
|
79
121
|
function vvMerge(a, b) {
|
|
80
|
-
const out =
|
|
81
|
-
for (const [actor, ctr] of Object.entries(
|
|
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
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
305
|
+
const nextEntry = frame.entries.next();
|
|
306
|
+
if (nextEntry.done) {
|
|
249
307
|
stack.pop();
|
|
250
308
|
continue;
|
|
251
309
|
}
|
|
252
|
-
const [key,
|
|
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
|
|
315
|
+
setMaterializedProperty(frame.out, key, child.value);
|
|
257
316
|
continue;
|
|
258
317
|
}
|
|
259
318
|
if (child.kind === "obj") {
|
|
260
|
-
const outObj =
|
|
261
|
-
frame.out
|
|
319
|
+
const outObj = createMaterializedObject();
|
|
320
|
+
setMaterializedProperty(frame.out, key, outObj);
|
|
262
321
|
stack.push({
|
|
263
322
|
kind: "obj",
|
|
264
323
|
depth: childDepth,
|
|
265
|
-
entries:
|
|
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
|
|
330
|
+
setMaterializedProperty(frame.out, key, outArr);
|
|
273
331
|
stack.push({
|
|
274
332
|
kind: "seq",
|
|
275
333
|
depth: childDepth,
|
|
276
|
-
|
|
277
|
-
index: 0,
|
|
278
|
-
seq: child,
|
|
334
|
+
cursor: rgaCreateLinearCursor(child),
|
|
279
335
|
out: outArr
|
|
280
336
|
});
|
|
281
337
|
continue;
|
|
282
338
|
}
|
|
283
|
-
|
|
339
|
+
const elem = frame.cursor.next();
|
|
340
|
+
if (!elem) {
|
|
284
341
|
stack.pop();
|
|
285
342
|
continue;
|
|
286
343
|
}
|
|
287
|
-
const
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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 =
|
|
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 =
|
|
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([],
|
|
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 (!
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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(
|
|
545
|
-
value:
|
|
786
|
+
path: stringifyJsonPointer(path),
|
|
787
|
+
value: next[nextKey]
|
|
546
788
|
});
|
|
789
|
+
path.pop();
|
|
790
|
+
nextIndex += 1;
|
|
547
791
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
769
|
-
if (
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const
|
|
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 ?
|
|
775
|
-
|
|
776
|
-
return
|
|
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
|
-
|
|
780
|
-
return
|
|
1074
|
+
clonedParent[index] = structuredClone(op.value);
|
|
1075
|
+
return root;
|
|
781
1076
|
}
|
|
782
|
-
|
|
783
|
-
return
|
|
1077
|
+
clonedParent.splice(index, 1);
|
|
1078
|
+
return root;
|
|
784
1079
|
}
|
|
785
|
-
if (!isPlainObject(
|
|
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
|
-
|
|
788
|
-
return
|
|
1085
|
+
clonedParent[token] = structuredClone(op.value);
|
|
1086
|
+
return root;
|
|
789
1087
|
}
|
|
790
|
-
delete
|
|
791
|
-
return
|
|
1088
|
+
delete clonedParent[token];
|
|
1089
|
+
return root;
|
|
792
1090
|
}
|
|
793
|
-
function
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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, {
|
|
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
|
|
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
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
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
|
-
|
|
1715
|
-
|
|
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 =
|
|
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
|
|
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
|
-
}
|
|
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
|
-
|
|
1751
|
-
return applySinglePatchOp(state, baseDoc, baseJson, {
|
|
2182
|
+
return applySinglePatchOpSequentialStep(state, baseDoc, baseJson, headJson, {
|
|
1752
2183
|
op: "add",
|
|
1753
2184
|
path: op.path,
|
|
1754
|
-
value:
|
|
1755
|
-
}, options);
|
|
2185
|
+
value: structuredClone(fromResolved.value)
|
|
2186
|
+
}, options, explicitBaseState);
|
|
1756
2187
|
}
|
|
1757
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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 () {
|