json-diff-ts 5.0.0-alpha.1 → 5.0.0-alpha.4
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 +613 -318
- package/dist/index.cjs +1063 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +188 -10
- package/dist/index.d.ts +188 -10
- package/dist/index.js +1040 -125
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -35,13 +35,13 @@ function arrayIntersection(first, second) {
|
|
|
35
35
|
}
|
|
36
36
|
function keyBy(arr, getKey2) {
|
|
37
37
|
const result = {};
|
|
38
|
-
for (const item of arr) {
|
|
39
|
-
result[String(getKey2(item))] = item;
|
|
38
|
+
for (const [index, item] of Object.entries(arr)) {
|
|
39
|
+
result[String(getKey2(item, false, Number(index)))] = item;
|
|
40
40
|
}
|
|
41
41
|
return result;
|
|
42
42
|
}
|
|
43
43
|
function setByPath(obj, path, value) {
|
|
44
|
-
const parts = path.
|
|
44
|
+
const parts = path.replaceAll(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
45
45
|
let current = obj;
|
|
46
46
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
47
47
|
const part = parts[i];
|
|
@@ -50,7 +50,7 @@ function setByPath(obj, path, value) {
|
|
|
50
50
|
}
|
|
51
51
|
current = current[part];
|
|
52
52
|
}
|
|
53
|
-
current[parts
|
|
53
|
+
current[parts.at(-1)] = value;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
// src/jsonDiff.ts
|
|
@@ -58,12 +58,11 @@ var Operation = /* @__PURE__ */ ((Operation2) => {
|
|
|
58
58
|
Operation2["REMOVE"] = "REMOVE";
|
|
59
59
|
Operation2["ADD"] = "ADD";
|
|
60
60
|
Operation2["UPDATE"] = "UPDATE";
|
|
61
|
-
Operation2["MOVE"] = "MOVE";
|
|
62
61
|
return Operation2;
|
|
63
62
|
})(Operation || {});
|
|
64
63
|
function diff(oldObj, newObj, options = {}) {
|
|
65
|
-
let
|
|
66
|
-
const { keysToSkip, treatTypeChangeAsReplace
|
|
64
|
+
let embeddedObjKeys = options.arrayIdentityKeys ?? options.embeddedObjKeys;
|
|
65
|
+
const { keysToSkip, treatTypeChangeAsReplace } = options;
|
|
67
66
|
if (embeddedObjKeys instanceof Map) {
|
|
68
67
|
embeddedObjKeys = new Map(
|
|
69
68
|
Array.from(embeddedObjKeys.entries()).map(([key, value]) => [
|
|
@@ -79,8 +78,7 @@ function diff(oldObj, newObj, options = {}) {
|
|
|
79
78
|
return compare(oldObj, newObj, [], [], {
|
|
80
79
|
embeddedObjKeys,
|
|
81
80
|
keysToSkip: keysToSkip ?? [],
|
|
82
|
-
treatTypeChangeAsReplace: treatTypeChangeAsReplace ?? true
|
|
83
|
-
detectArrayMoves: detectArrayMoves2 ?? false
|
|
81
|
+
treatTypeChangeAsReplace: treatTypeChangeAsReplace ?? true
|
|
84
82
|
});
|
|
85
83
|
}
|
|
86
84
|
var applyChangeset = (obj, changeset) => {
|
|
@@ -90,7 +88,7 @@ var applyChangeset = (obj, changeset) => {
|
|
|
90
88
|
if (value !== null && value !== void 0 || type === "REMOVE" /* REMOVE */ || value === null && type === "ADD" /* ADD */ || value === void 0 && type === "ADD" /* ADD */) {
|
|
91
89
|
applyLeafChange(obj, change, embeddedKey);
|
|
92
90
|
} else {
|
|
93
|
-
applyBranchChange(obj[key], change);
|
|
91
|
+
applyBranchChange(key === "$root" ? obj : obj[key], change);
|
|
94
92
|
}
|
|
95
93
|
});
|
|
96
94
|
}
|
|
@@ -103,7 +101,7 @@ var revertChangeset = (obj, changeset) => {
|
|
|
103
101
|
if (!change.changes || value === null && type === "REMOVE" /* REMOVE */) {
|
|
104
102
|
revertLeafChange(obj, change);
|
|
105
103
|
} else {
|
|
106
|
-
revertBranchChange(obj[change.key], change);
|
|
104
|
+
revertBranchChange(change.key === "$root" ? obj : obj[change.key], change);
|
|
107
105
|
}
|
|
108
106
|
});
|
|
109
107
|
}
|
|
@@ -127,22 +125,18 @@ var atomizeChangeset = (obj, path = "$", embeddedKey) => {
|
|
|
127
125
|
const valueType = getTypeOfObj(obj.value);
|
|
128
126
|
let finalPath = path;
|
|
129
127
|
if (!finalPath.endsWith(`[${obj.key}]`)) {
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (filterStartIdx !== -1) {
|
|
138
|
-
const filterValue = path.slice(filterStartIdx + 2, filterEndIdx).replace(/(^'|'$)/g, "");
|
|
139
|
-
endsWithFilterValue = filterValue === String(obj.key);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (!endsWithFilterValue) {
|
|
143
|
-
finalPath = append(path, obj.key);
|
|
128
|
+
let endsWithFilterValue = false;
|
|
129
|
+
const filterEndIdx = path.lastIndexOf(")]");
|
|
130
|
+
if (filterEndIdx !== -1) {
|
|
131
|
+
const filterStartIdx = path.lastIndexOf("==", filterEndIdx);
|
|
132
|
+
if (filterStartIdx !== -1) {
|
|
133
|
+
const filterValue = path.slice(filterStartIdx + 2, filterEndIdx).replaceAll(/(^'|'$)/g, "");
|
|
134
|
+
endsWithFilterValue = filterValue === String(obj.key);
|
|
144
135
|
}
|
|
145
136
|
}
|
|
137
|
+
if (!endsWithFilterValue) {
|
|
138
|
+
finalPath = append(path, obj.key);
|
|
139
|
+
}
|
|
146
140
|
}
|
|
147
141
|
return [
|
|
148
142
|
{
|
|
@@ -192,27 +186,23 @@ var unatomizeChangeset = (changes) => {
|
|
|
192
186
|
ptr.type = change.type;
|
|
193
187
|
ptr.value = change.value;
|
|
194
188
|
ptr.oldValue = change.oldValue;
|
|
195
|
-
if (change.type === "MOVE" /* MOVE */) {
|
|
196
|
-
ptr.oldIndex = change.oldIndex;
|
|
197
|
-
ptr.newIndex = change.newIndex;
|
|
198
|
-
}
|
|
199
189
|
changesArr.push(ptr);
|
|
200
190
|
} else {
|
|
201
191
|
for (let i = 1; i < segments.length; i++) {
|
|
202
192
|
const segment = segments[i];
|
|
203
|
-
const result = /^([^[\]]+)\[\?\(
|
|
193
|
+
const result = /^([^[\]]+)\[\?\(@(?:\.?([^=[]*)|(?:\['([^']*)'\]))=+'([^']+)'\)\]$|^(.+)\[(\d+)\]$/.exec(segment);
|
|
204
194
|
if (result) {
|
|
205
195
|
let key;
|
|
206
196
|
let embeddedKey;
|
|
207
197
|
let arrKey;
|
|
208
198
|
if (result[1]) {
|
|
209
199
|
key = result[1];
|
|
210
|
-
embeddedKey = result[2] || "$value";
|
|
211
|
-
arrKey = result[
|
|
200
|
+
embeddedKey = result[3] || result[2] || "$value";
|
|
201
|
+
arrKey = result[4];
|
|
212
202
|
} else {
|
|
213
|
-
key = result[
|
|
203
|
+
key = result[5];
|
|
214
204
|
embeddedKey = "$index";
|
|
215
|
-
arrKey = Number(result[
|
|
205
|
+
arrKey = Number(result[6]);
|
|
216
206
|
}
|
|
217
207
|
if (i === segments.length - 1) {
|
|
218
208
|
ptr.key = key;
|
|
@@ -223,8 +213,7 @@ var unatomizeChangeset = (changes) => {
|
|
|
223
213
|
type: change.type,
|
|
224
214
|
key: arrKey,
|
|
225
215
|
value: change.value,
|
|
226
|
-
oldValue: change.oldValue
|
|
227
|
-
...change.type === "MOVE" /* MOVE */ && { oldIndex: change.oldIndex, newIndex: change.newIndex }
|
|
216
|
+
oldValue: change.oldValue
|
|
228
217
|
}
|
|
229
218
|
];
|
|
230
219
|
} else {
|
|
@@ -247,10 +236,6 @@ var unatomizeChangeset = (changes) => {
|
|
|
247
236
|
ptr.type = change.type;
|
|
248
237
|
ptr.value = change.value;
|
|
249
238
|
ptr.oldValue = change.oldValue;
|
|
250
|
-
if (change.type === "MOVE" /* MOVE */) {
|
|
251
|
-
ptr.oldIndex = change.oldIndex;
|
|
252
|
-
ptr.newIndex = change.newIndex;
|
|
253
|
-
}
|
|
254
239
|
} else {
|
|
255
240
|
ptr.key = segment;
|
|
256
241
|
ptr.type = "UPDATE" /* UPDATE */;
|
|
@@ -266,7 +251,7 @@ var unatomizeChangeset = (changes) => {
|
|
|
266
251
|
return changesArr;
|
|
267
252
|
};
|
|
268
253
|
var getTypeOfObj = (obj) => {
|
|
269
|
-
if (
|
|
254
|
+
if (obj === void 0) {
|
|
270
255
|
return "undefined";
|
|
271
256
|
}
|
|
272
257
|
if (obj === null) {
|
|
@@ -275,8 +260,8 @@ var getTypeOfObj = (obj) => {
|
|
|
275
260
|
return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
|
|
276
261
|
};
|
|
277
262
|
var getKey = (path) => {
|
|
278
|
-
const left = path
|
|
279
|
-
return left
|
|
263
|
+
const left = path.at(-1);
|
|
264
|
+
return left ?? "$root";
|
|
280
265
|
};
|
|
281
266
|
var compare = (oldObj, newObj, path, keyPath, options) => {
|
|
282
267
|
let changes = [];
|
|
@@ -431,22 +416,17 @@ var compareArray = (oldObj, newObj, path, keyPath, options) => {
|
|
|
431
416
|
return [{ type: "UPDATE" /* UPDATE */, key: getKey(path), value: newObj, oldValue: oldObj }];
|
|
432
417
|
}
|
|
433
418
|
const left = getObjectKey(options.embeddedObjKeys, keyPath);
|
|
434
|
-
const uniqKey = left
|
|
419
|
+
const uniqKey = left ?? "$index";
|
|
435
420
|
const indexedOldObj = convertArrayToObj(oldObj, uniqKey);
|
|
436
421
|
const indexedNewObj = convertArrayToObj(newObj, uniqKey);
|
|
437
422
|
const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options);
|
|
438
|
-
|
|
439
|
-
if (options.detectArrayMoves && uniqKey !== "$index" && left != null) {
|
|
440
|
-
moveDiffs = detectArrayMoves(oldObj, newObj, uniqKey);
|
|
441
|
-
}
|
|
442
|
-
const allDiffs = [...diffs, ...moveDiffs];
|
|
443
|
-
if (allDiffs.length) {
|
|
423
|
+
if (diffs.length) {
|
|
444
424
|
return [
|
|
445
425
|
{
|
|
446
426
|
type: "UPDATE" /* UPDATE */,
|
|
447
427
|
key: getKey(path),
|
|
448
428
|
embeddedKey: typeof uniqKey === "function" && uniqKey.length === 2 ? uniqKey(newObj[0], true) : uniqKey,
|
|
449
|
-
changes:
|
|
429
|
+
changes: diffs
|
|
450
430
|
}
|
|
451
431
|
];
|
|
452
432
|
} else {
|
|
@@ -474,49 +454,20 @@ var getObjectKey = (embeddedObjKeys, keyPath) => {
|
|
|
474
454
|
}
|
|
475
455
|
return void 0;
|
|
476
456
|
};
|
|
477
|
-
var detectArrayMoves = (oldArray, newArray, uniqKey) => {
|
|
478
|
-
const moves = [];
|
|
479
|
-
const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey;
|
|
480
|
-
const oldIndexMap = /* @__PURE__ */ new Map();
|
|
481
|
-
const newIndexMap = /* @__PURE__ */ new Map();
|
|
482
|
-
oldArray.forEach((item, index) => {
|
|
483
|
-
const key = keyFunction(item);
|
|
484
|
-
oldIndexMap.set(key, index);
|
|
485
|
-
});
|
|
486
|
-
newArray.forEach((item, index) => {
|
|
487
|
-
const key = keyFunction(item);
|
|
488
|
-
newIndexMap.set(key, index);
|
|
489
|
-
});
|
|
490
|
-
for (const [key, newIndex] of newIndexMap) {
|
|
491
|
-
if (oldIndexMap.has(key)) {
|
|
492
|
-
const oldIndex = oldIndexMap.get(key);
|
|
493
|
-
if (oldIndex !== newIndex) {
|
|
494
|
-
moves.push({
|
|
495
|
-
type: "MOVE" /* MOVE */,
|
|
496
|
-
key,
|
|
497
|
-
oldIndex,
|
|
498
|
-
newIndex,
|
|
499
|
-
value: newArray[newIndex]
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
return moves;
|
|
505
|
-
};
|
|
506
457
|
var convertArrayToObj = (arr, uniqKey) => {
|
|
507
458
|
let obj = {};
|
|
508
459
|
if (uniqKey === "$value") {
|
|
509
460
|
arr.forEach((value) => {
|
|
510
461
|
obj[value] = value;
|
|
511
462
|
});
|
|
512
|
-
} else if (uniqKey
|
|
513
|
-
const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey;
|
|
514
|
-
obj = keyBy(arr, keyFunction);
|
|
515
|
-
} else {
|
|
463
|
+
} else if (uniqKey === "$index") {
|
|
516
464
|
for (let i = 0; i < arr.length; i++) {
|
|
517
465
|
const value = arr[i];
|
|
518
466
|
obj[i] = value;
|
|
519
467
|
}
|
|
468
|
+
} else {
|
|
469
|
+
const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey;
|
|
470
|
+
obj = keyBy(arr, keyFunction);
|
|
520
471
|
}
|
|
521
472
|
return obj;
|
|
522
473
|
};
|
|
@@ -540,10 +491,10 @@ var removeKey = (obj, key, embeddedKey) => {
|
|
|
540
491
|
}
|
|
541
492
|
const index = indexOfItemInArray(obj, embeddedKey, key);
|
|
542
493
|
if (index === -1) {
|
|
543
|
-
console.warn(`Element with the key '${embeddedKey}' and value '${key}' could not be found in the array
|
|
494
|
+
console.warn(`Element with the key '${embeddedKey}' and value '${key}' could not be found in the array!`);
|
|
544
495
|
return;
|
|
545
496
|
}
|
|
546
|
-
return obj.splice(index
|
|
497
|
+
return obj.splice(index ?? key, 1);
|
|
547
498
|
} else {
|
|
548
499
|
delete obj[key];
|
|
549
500
|
return;
|
|
@@ -562,29 +513,6 @@ var indexOfItemInArray = (arr, key, value) => {
|
|
|
562
513
|
return -1;
|
|
563
514
|
};
|
|
564
515
|
var modifyKeyValue = (obj, key, value) => obj[key] = value;
|
|
565
|
-
var moveArrayElement = (arr, key, oldIndex, newIndex, embeddedKey) => {
|
|
566
|
-
if (!Array.isArray(arr)) {
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
let elementIndex = -1;
|
|
570
|
-
if (embeddedKey === "$index") {
|
|
571
|
-
elementIndex = oldIndex;
|
|
572
|
-
} else if (embeddedKey === "$value") {
|
|
573
|
-
elementIndex = arr.indexOf(key);
|
|
574
|
-
} else {
|
|
575
|
-
elementIndex = arr.findIndex((item) => {
|
|
576
|
-
const keyFunction = typeof embeddedKey === "string" ? (item2) => item2[embeddedKey] : embeddedKey;
|
|
577
|
-
return keyFunction(item)?.toString() === key.toString();
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
if (elementIndex === -1) {
|
|
581
|
-
console.warn(`Element with key '${key}' not found for MOVE operation`);
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
const element = arr.splice(elementIndex, 1)[0];
|
|
585
|
-
arr.splice(newIndex, 0, element);
|
|
586
|
-
return arr;
|
|
587
|
-
};
|
|
588
516
|
var addKeyValue = (obj, key, value, embeddedKey) => {
|
|
589
517
|
if (Array.isArray(obj)) {
|
|
590
518
|
if (embeddedKey === "$index") {
|
|
@@ -597,7 +525,7 @@ var addKeyValue = (obj, key, value, embeddedKey) => {
|
|
|
597
525
|
}
|
|
598
526
|
};
|
|
599
527
|
var applyLeafChange = (obj, change, embeddedKey) => {
|
|
600
|
-
const { type, key, value
|
|
528
|
+
const { type, key, value } = change;
|
|
601
529
|
switch (type) {
|
|
602
530
|
case "ADD" /* ADD */:
|
|
603
531
|
return addKeyValue(obj, key, value, embeddedKey);
|
|
@@ -605,8 +533,6 @@ var applyLeafChange = (obj, change, embeddedKey) => {
|
|
|
605
533
|
return modifyKeyValue(obj, key, value);
|
|
606
534
|
case "REMOVE" /* REMOVE */:
|
|
607
535
|
return removeKey(obj, key, embeddedKey);
|
|
608
|
-
case "MOVE" /* MOVE */:
|
|
609
|
-
return moveArrayElement(obj, key, oldIndex, newIndex, embeddedKey);
|
|
610
536
|
}
|
|
611
537
|
};
|
|
612
538
|
var applyArrayChange = (arr, change) => {
|
|
@@ -622,7 +548,7 @@ var applyArrayChange = (arr, change) => {
|
|
|
622
548
|
});
|
|
623
549
|
}
|
|
624
550
|
for (const subchange of changes) {
|
|
625
|
-
if (subchange.value !== null && subchange.value !== void 0 || subchange.type === "REMOVE" /* REMOVE */ || subchange.value === null && subchange.type === "ADD" /* ADD */ || subchange.
|
|
551
|
+
if (subchange.value !== null && subchange.value !== void 0 || subchange.type === "REMOVE" /* REMOVE */ || subchange.value === null && subchange.type === "ADD" /* ADD */ || subchange.value === void 0 && subchange.type === "ADD" /* ADD */) {
|
|
626
552
|
applyLeafChange(arr, subchange, change.embeddedKey);
|
|
627
553
|
} else {
|
|
628
554
|
let element;
|
|
@@ -651,7 +577,7 @@ var applyBranchChange = (obj, change) => {
|
|
|
651
577
|
}
|
|
652
578
|
};
|
|
653
579
|
var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
654
|
-
const { type, key, value, oldValue
|
|
580
|
+
const { type, key, value, oldValue } = change;
|
|
655
581
|
if (key === "$root") {
|
|
656
582
|
switch (type) {
|
|
657
583
|
case "ADD" /* ADD */:
|
|
@@ -685,13 +611,11 @@ var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
|
685
611
|
return modifyKeyValue(obj, key, oldValue);
|
|
686
612
|
case "REMOVE" /* REMOVE */:
|
|
687
613
|
return addKeyValue(obj, key, value);
|
|
688
|
-
case "MOVE" /* MOVE */:
|
|
689
|
-
return moveArrayElement(obj, key, newIndex, oldIndex, embeddedKey);
|
|
690
614
|
}
|
|
691
615
|
};
|
|
692
616
|
var revertArrayChange = (arr, change) => {
|
|
693
617
|
for (const subchange of change.changes) {
|
|
694
|
-
if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */
|
|
618
|
+
if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */) {
|
|
695
619
|
revertLeafChange(arr, subchange, change.embeddedKey);
|
|
696
620
|
} else {
|
|
697
621
|
let element;
|
|
@@ -722,9 +646,11 @@ var revertBranchChange = (obj, change) => {
|
|
|
722
646
|
function append(basePath, nextSegment) {
|
|
723
647
|
return nextSegment.includes(".") ? `${basePath}[${nextSegment}]` : `${basePath}.${nextSegment}`;
|
|
724
648
|
}
|
|
649
|
+
var IDENT_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
725
650
|
function filterExpression(basePath, filterKey, filterValue) {
|
|
726
651
|
const value = typeof filterValue === "number" ? filterValue : `'${filterValue}'`;
|
|
727
|
-
|
|
652
|
+
const memberAccess = typeof filterKey === "string" && !IDENT_RE.test(filterKey) ? `['${filterKey}']` : `.${filterKey}`;
|
|
653
|
+
return `${basePath}[?(@${memberAccess}==${value})]`;
|
|
728
654
|
}
|
|
729
655
|
|
|
730
656
|
// src/jsonCompare.ts
|
|
@@ -758,18 +684,35 @@ var enrich = (object) => {
|
|
|
758
684
|
return createValue(object);
|
|
759
685
|
}
|
|
760
686
|
};
|
|
687
|
+
var buildEnrichedPath = (atomicPath) => {
|
|
688
|
+
const segments = splitJSONPath(atomicPath);
|
|
689
|
+
let result = "value";
|
|
690
|
+
for (let i = 1; i < segments.length; i++) {
|
|
691
|
+
const seg = segments[i];
|
|
692
|
+
const isLast = i === segments.length - 1;
|
|
693
|
+
const arrayMatch = /^(.+?)\[(\d+)\]$/.exec(seg);
|
|
694
|
+
if (arrayMatch) {
|
|
695
|
+
const [, key, index] = arrayMatch;
|
|
696
|
+
result += `.${key}.value[${index}]`;
|
|
697
|
+
} else {
|
|
698
|
+
result += `.${seg}`;
|
|
699
|
+
}
|
|
700
|
+
if (!isLast) {
|
|
701
|
+
result += ".value";
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return result;
|
|
705
|
+
};
|
|
761
706
|
var applyChangelist = (object, changelist) => {
|
|
762
|
-
changelist.
|
|
763
|
-
|
|
764
|
-
path: entry.path.replace(/(\[(?<array>\d)\]\.)/g, "ARRVAL_START$<array>ARRVAL_END")
|
|
765
|
-
})).map((entry) => ({ ...entry, path: entry.path.replace(/(?<dot>\.)/g, ".value$<dot>") })).map((entry) => ({ ...entry, path: entry.path.replace(/\./, "") })).map((entry) => ({ ...entry, path: entry.path.replace(/ARRVAL_START/g, ".value[") })).map((entry) => ({ ...entry, path: entry.path.replace(/ARRVAL_END/g, "].value.") })).forEach((entry) => {
|
|
707
|
+
changelist.forEach((entry) => {
|
|
708
|
+
const path = buildEnrichedPath(entry.path);
|
|
766
709
|
switch (entry.type) {
|
|
767
710
|
case "ADD" /* ADD */:
|
|
768
711
|
case "UPDATE" /* UPDATE */:
|
|
769
|
-
setByPath(object,
|
|
712
|
+
setByPath(object, path, { type: entry.type, value: entry.value, oldValue: entry.oldValue });
|
|
770
713
|
break;
|
|
771
714
|
case "REMOVE" /* REMOVE */:
|
|
772
|
-
setByPath(object,
|
|
715
|
+
setByPath(object, path, { type: entry.type, value: void 0, oldValue: entry.value });
|
|
773
716
|
break;
|
|
774
717
|
default:
|
|
775
718
|
throw new Error();
|
|
@@ -777,22 +720,994 @@ var applyChangelist = (object, changelist) => {
|
|
|
777
720
|
});
|
|
778
721
|
return object;
|
|
779
722
|
};
|
|
723
|
+
var ARRAY_WRAPPER_KEY = "_$arr";
|
|
780
724
|
var compare2 = (oldObject, newObject) => {
|
|
725
|
+
if (Array.isArray(oldObject) || Array.isArray(newObject)) {
|
|
726
|
+
const wrappedOld = { [ARRAY_WRAPPER_KEY]: oldObject };
|
|
727
|
+
const wrappedNew = { [ARRAY_WRAPPER_KEY]: newObject };
|
|
728
|
+
const enriched = enrich(wrappedOld);
|
|
729
|
+
const changes = atomizeChangeset(diff(wrappedOld, wrappedNew));
|
|
730
|
+
const result = applyChangelist(enriched, changes);
|
|
731
|
+
return result.value[ARRAY_WRAPPER_KEY];
|
|
732
|
+
}
|
|
781
733
|
return applyChangelist(enrich(oldObject), atomizeChangeset(diff(oldObject, newObject)));
|
|
782
734
|
};
|
|
735
|
+
var comparisonToDict = (node) => {
|
|
736
|
+
const result = { type: node.type };
|
|
737
|
+
if (node.type === "CONTAINER" /* CONTAINER */) {
|
|
738
|
+
if (Array.isArray(node.value)) {
|
|
739
|
+
const children = node.value;
|
|
740
|
+
const serialized = new Array(children.length);
|
|
741
|
+
for (let i = 0; i < children.length; i++) {
|
|
742
|
+
const child = children[i];
|
|
743
|
+
serialized[i] = child != null ? comparisonToDict(child) : null;
|
|
744
|
+
}
|
|
745
|
+
result.value = serialized;
|
|
746
|
+
} else if (node.value && typeof node.value === "object") {
|
|
747
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
748
|
+
for (const [key, child] of Object.entries(
|
|
749
|
+
node.value
|
|
750
|
+
)) {
|
|
751
|
+
if (child == null) continue;
|
|
752
|
+
obj[key] = comparisonToDict(child);
|
|
753
|
+
}
|
|
754
|
+
result.value = obj;
|
|
755
|
+
}
|
|
756
|
+
} else {
|
|
757
|
+
if (node.type === "UNCHANGED" /* UNCHANGED */ || node.type === "ADD" /* ADD */ || node.type === "UPDATE" /* UPDATE */) {
|
|
758
|
+
result.value = node.value;
|
|
759
|
+
}
|
|
760
|
+
if (node.type === "REMOVE" /* REMOVE */ || node.type === "UPDATE" /* UPDATE */) {
|
|
761
|
+
result.oldValue = node.oldValue;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return result;
|
|
765
|
+
};
|
|
766
|
+
var IDENT_RE2 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
767
|
+
var comparisonToFlatList = (node, options = {}) => {
|
|
768
|
+
const results = [];
|
|
769
|
+
flattenNode(node, "$", options.includeUnchanged ?? false, results);
|
|
770
|
+
return results;
|
|
771
|
+
};
|
|
772
|
+
function flattenNode(node, path, includeUnchanged, results) {
|
|
773
|
+
if (node.type === "CONTAINER" /* CONTAINER */) {
|
|
774
|
+
if (Array.isArray(node.value)) {
|
|
775
|
+
for (let i = 0; i < node.value.length; i++) {
|
|
776
|
+
const child = node.value[i];
|
|
777
|
+
if (child == null) continue;
|
|
778
|
+
flattenNode(child, `${path}[${i}]`, includeUnchanged, results);
|
|
779
|
+
}
|
|
780
|
+
} else if (node.value && typeof node.value === "object") {
|
|
781
|
+
for (const [key, child] of Object.entries(
|
|
782
|
+
node.value
|
|
783
|
+
)) {
|
|
784
|
+
if (child == null) continue;
|
|
785
|
+
const childPath = IDENT_RE2.test(key) ? `${path}.${key}` : `${path}['${key.replace(/'/g, "''")}']`;
|
|
786
|
+
flattenNode(child, childPath, includeUnchanged, results);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (node.type === "UNCHANGED" /* UNCHANGED */ && !includeUnchanged) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const entry = { path, type: node.type };
|
|
795
|
+
if (node.type === "UNCHANGED" /* UNCHANGED */ || node.type === "ADD" /* ADD */ || node.type === "UPDATE" /* UPDATE */) {
|
|
796
|
+
entry.value = node.value;
|
|
797
|
+
}
|
|
798
|
+
if (node.type === "REMOVE" /* REMOVE */ || node.type === "UPDATE" /* UPDATE */) {
|
|
799
|
+
entry.oldValue = node.oldValue;
|
|
800
|
+
}
|
|
801
|
+
results.push(entry);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/atomPath.ts
|
|
805
|
+
function formatFilterLiteral(value) {
|
|
806
|
+
if (value === null) return "null";
|
|
807
|
+
if (typeof value === "boolean") return String(value);
|
|
808
|
+
if (typeof value === "number") {
|
|
809
|
+
if (!Number.isFinite(value)) throw new Error(`Cannot format non-finite number as filter literal: ${value}`);
|
|
810
|
+
return String(value);
|
|
811
|
+
}
|
|
812
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
|
|
813
|
+
throw new Error(`Cannot format filter literal for type ${typeof value}`);
|
|
814
|
+
}
|
|
815
|
+
function parseFilterLiteral(s) {
|
|
816
|
+
if (s === "true") return true;
|
|
817
|
+
if (s === "false") return false;
|
|
818
|
+
if (s === "null") return null;
|
|
819
|
+
if (s.startsWith("'") && s.endsWith("'")) {
|
|
820
|
+
return s.slice(1, -1).replace(/''/g, "'");
|
|
821
|
+
}
|
|
822
|
+
if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(s)) {
|
|
823
|
+
return Number(s);
|
|
824
|
+
}
|
|
825
|
+
throw new Error(`Invalid filter literal: ${s}`);
|
|
826
|
+
}
|
|
827
|
+
function extractQuotedString(s, start) {
|
|
828
|
+
const result = [];
|
|
829
|
+
let i = start;
|
|
830
|
+
while (i < s.length) {
|
|
831
|
+
if (s[i] === "'") {
|
|
832
|
+
if (i + 1 < s.length && s[i + 1] === "'") {
|
|
833
|
+
result.push("'");
|
|
834
|
+
i += 2;
|
|
835
|
+
} else {
|
|
836
|
+
return [result.join(""), i];
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
result.push(s[i]);
|
|
840
|
+
i += 1;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
throw new Error("Unterminated quoted string");
|
|
844
|
+
}
|
|
845
|
+
function findFilterClose(s, from) {
|
|
846
|
+
let i = from;
|
|
847
|
+
while (i < s.length) {
|
|
848
|
+
if (s[i] === "'") {
|
|
849
|
+
i += 1;
|
|
850
|
+
while (i < s.length) {
|
|
851
|
+
if (s[i] === "'") {
|
|
852
|
+
if (i + 1 < s.length && s[i + 1] === "'") {
|
|
853
|
+
i += 2;
|
|
854
|
+
} else {
|
|
855
|
+
i += 1;
|
|
856
|
+
break;
|
|
857
|
+
}
|
|
858
|
+
} else {
|
|
859
|
+
i += 1;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
} else if (s[i] === ")" && i + 1 < s.length && s[i + 1] === "]") {
|
|
863
|
+
return i;
|
|
864
|
+
} else {
|
|
865
|
+
i += 1;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
return -1;
|
|
869
|
+
}
|
|
870
|
+
function parseFilter(inner) {
|
|
871
|
+
if (inner.startsWith("@.")) {
|
|
872
|
+
const eq = inner.indexOf("==");
|
|
873
|
+
if (eq === -1) throw new Error(`Invalid filter: missing '==' in ${inner}`);
|
|
874
|
+
const key = inner.slice(2, eq);
|
|
875
|
+
if (!key || !SIMPLE_PROPERTY_RE.test(key)) {
|
|
876
|
+
throw new Error(`Invalid property name in filter: '${key}'. Use bracket notation for non-identifier keys: @['${key}']`);
|
|
877
|
+
}
|
|
878
|
+
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(eq + 2)) };
|
|
879
|
+
}
|
|
880
|
+
if (inner.startsWith("@['")) {
|
|
881
|
+
const [key, endIdx] = extractQuotedString(inner, 3);
|
|
882
|
+
const valStart = endIdx + 4;
|
|
883
|
+
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(valStart)) };
|
|
884
|
+
}
|
|
885
|
+
if (inner.startsWith("@==")) {
|
|
886
|
+
return { type: "valueFilter", value: parseFilterLiteral(inner.slice(3)) };
|
|
887
|
+
}
|
|
888
|
+
throw new Error(`Invalid filter expression: ${inner}`);
|
|
889
|
+
}
|
|
890
|
+
function parseAtomPath(path) {
|
|
891
|
+
if (!path.startsWith("$")) {
|
|
892
|
+
throw new Error(`Path must start with '$': ${path}`);
|
|
893
|
+
}
|
|
894
|
+
const segments = [{ type: "root" }];
|
|
895
|
+
let i = 1;
|
|
896
|
+
while (i < path.length) {
|
|
897
|
+
if (path[i] === ".") {
|
|
898
|
+
i += 1;
|
|
899
|
+
const start = i;
|
|
900
|
+
while (i < path.length && /[a-zA-Z0-9_]/.test(path[i])) {
|
|
901
|
+
i += 1;
|
|
902
|
+
}
|
|
903
|
+
if (i === start) throw new Error(`Empty property name at position ${i} in: ${path}`);
|
|
904
|
+
segments.push({ type: "property", name: path.slice(start, i) });
|
|
905
|
+
} else if (path[i] === "[") {
|
|
906
|
+
if (i + 1 >= path.length) throw new Error(`Unexpected end of path after '[': ${path}`);
|
|
907
|
+
if (path[i + 1] === "?") {
|
|
908
|
+
const closingIdx = findFilterClose(path, i + 2);
|
|
909
|
+
if (closingIdx === -1) throw new Error(`Unterminated filter expression in: ${path}`);
|
|
910
|
+
const inner = path.slice(i + 3, closingIdx);
|
|
911
|
+
segments.push(parseFilter(inner));
|
|
912
|
+
i = closingIdx + 2;
|
|
913
|
+
} else if (path[i + 1] === "'") {
|
|
914
|
+
const [key, endIdx] = extractQuotedString(path, i + 2);
|
|
915
|
+
if (path[endIdx + 1] !== "]") throw new Error(`Expected ']' after bracket property in: ${path}`);
|
|
916
|
+
segments.push({ type: "property", name: key });
|
|
917
|
+
i = endIdx + 2;
|
|
918
|
+
} else if (/\d/.test(path[i + 1])) {
|
|
919
|
+
const end = path.indexOf("]", i);
|
|
920
|
+
if (end === -1) throw new Error(`Unterminated array index in: ${path}`);
|
|
921
|
+
const indexStr = path.slice(i + 1, end);
|
|
922
|
+
if (indexStr.length > 1 && indexStr[0] === "0") {
|
|
923
|
+
throw new Error(`Leading zeros not allowed in array index: [${indexStr}]`);
|
|
924
|
+
}
|
|
925
|
+
segments.push({ type: "index", index: Number(indexStr) });
|
|
926
|
+
i = end + 1;
|
|
927
|
+
} else {
|
|
928
|
+
throw new Error(`Unexpected character after '[': '${path[i + 1]}' in: ${path}`);
|
|
929
|
+
}
|
|
930
|
+
} else {
|
|
931
|
+
throw new Error(`Unexpected character '${path[i]}' at position ${i} in: ${path}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return segments;
|
|
935
|
+
}
|
|
936
|
+
var SIMPLE_PROPERTY_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
937
|
+
function formatMemberAccess(name) {
|
|
938
|
+
if (SIMPLE_PROPERTY_RE.test(name)) {
|
|
939
|
+
return `.${name}`;
|
|
940
|
+
}
|
|
941
|
+
return `['${name.replace(/'/g, "''")}']`;
|
|
942
|
+
}
|
|
943
|
+
function buildAtomPath(segments) {
|
|
944
|
+
let result = "";
|
|
945
|
+
for (const seg of segments) {
|
|
946
|
+
switch (seg.type) {
|
|
947
|
+
case "root":
|
|
948
|
+
result += "$";
|
|
949
|
+
break;
|
|
950
|
+
case "property":
|
|
951
|
+
result += formatMemberAccess(seg.name);
|
|
952
|
+
break;
|
|
953
|
+
case "index":
|
|
954
|
+
result += `[${seg.index}]`;
|
|
955
|
+
break;
|
|
956
|
+
case "keyFilter": {
|
|
957
|
+
const memberAccess = SIMPLE_PROPERTY_RE.test(seg.property) ? `.${seg.property}` : `['${seg.property.replace(/'/g, "''")}']`;
|
|
958
|
+
result += `[?(@${memberAccess}==${formatFilterLiteral(seg.value)})]`;
|
|
959
|
+
break;
|
|
960
|
+
}
|
|
961
|
+
case "valueFilter":
|
|
962
|
+
result += `[?(@==${formatFilterLiteral(seg.value)})]`;
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return result;
|
|
967
|
+
}
|
|
968
|
+
function canonicalizeFilterForAtom(inner) {
|
|
969
|
+
if (inner.startsWith("@.")) {
|
|
970
|
+
const eqIdx = inner.indexOf("==");
|
|
971
|
+
if (eqIdx === -1) return `[?(${inner})]`;
|
|
972
|
+
const key = inner.slice(2, eqIdx);
|
|
973
|
+
const valuePart = inner.slice(eqIdx);
|
|
974
|
+
if (SIMPLE_PROPERTY_RE.test(key)) {
|
|
975
|
+
return `[?(@.${key}${valuePart})]`;
|
|
976
|
+
}
|
|
977
|
+
return `[?(@['${key.replace(/'/g, "''")}']${valuePart})]`;
|
|
978
|
+
}
|
|
979
|
+
return `[?(${inner})]`;
|
|
980
|
+
}
|
|
981
|
+
function canonicalizeFilterForV4(inner) {
|
|
982
|
+
if (inner.startsWith("@['")) {
|
|
983
|
+
const [key, endIdx] = extractQuotedString(inner, 3);
|
|
984
|
+
const rest = inner.slice(endIdx + 2);
|
|
985
|
+
if (rest.startsWith("==")) {
|
|
986
|
+
const literal = rest.slice(2);
|
|
987
|
+
const normalizedLiteral = normalizeToStringQuoted(literal);
|
|
988
|
+
return `[?(@.${key}==${normalizedLiteral})]`;
|
|
989
|
+
}
|
|
990
|
+
return `[?(${inner})]`;
|
|
991
|
+
}
|
|
992
|
+
return normalizeFilterToStringLiterals(`[?(${inner})]`);
|
|
993
|
+
}
|
|
994
|
+
function normalizeToStringQuoted(literal) {
|
|
995
|
+
if (literal.startsWith("'") && literal.endsWith("'")) {
|
|
996
|
+
return literal;
|
|
997
|
+
}
|
|
998
|
+
const value = parseFilterLiteral(literal);
|
|
999
|
+
const stringValue = String(value).replace(/'/g, "''");
|
|
1000
|
+
return `'${stringValue}'`;
|
|
1001
|
+
}
|
|
1002
|
+
function atomicPathToAtomPath(atomicPath) {
|
|
1003
|
+
if (atomicPath === "$.$root") return "$";
|
|
1004
|
+
if (atomicPath.startsWith("$.$root.")) return "$" + atomicPath.slice(7);
|
|
1005
|
+
if (!atomicPath.startsWith("$")) {
|
|
1006
|
+
throw new Error(`Atomic path must start with '$': ${atomicPath}`);
|
|
1007
|
+
}
|
|
1008
|
+
let result = "$";
|
|
1009
|
+
let i = 1;
|
|
1010
|
+
while (i < atomicPath.length) {
|
|
1011
|
+
if (atomicPath[i] === ".") {
|
|
1012
|
+
i += 1;
|
|
1013
|
+
const start = i;
|
|
1014
|
+
while (i < atomicPath.length && atomicPath[i] !== "." && atomicPath[i] !== "[") {
|
|
1015
|
+
i += 1;
|
|
1016
|
+
}
|
|
1017
|
+
const name = atomicPath.slice(start, i);
|
|
1018
|
+
result += formatMemberAccess(name);
|
|
1019
|
+
} else if (atomicPath[i] === "[") {
|
|
1020
|
+
if (atomicPath[i + 1] === "?") {
|
|
1021
|
+
const closingIdx = findFilterClose(atomicPath, i + 2);
|
|
1022
|
+
if (closingIdx === -1) throw new Error(`Unterminated filter in: ${atomicPath}`);
|
|
1023
|
+
const inner = atomicPath.slice(i + 3, closingIdx);
|
|
1024
|
+
result += canonicalizeFilterForAtom(inner);
|
|
1025
|
+
i = closingIdx + 2;
|
|
1026
|
+
} else if (atomicPath[i + 1] === "'" || /\d/.test(atomicPath[i + 1])) {
|
|
1027
|
+
const end = atomicPath.indexOf("]", i);
|
|
1028
|
+
if (end === -1) throw new Error(`Unterminated bracket in: ${atomicPath}`);
|
|
1029
|
+
result += atomicPath.slice(i, end + 1);
|
|
1030
|
+
i = end + 1;
|
|
1031
|
+
} else {
|
|
1032
|
+
const end = atomicPath.indexOf("]", i);
|
|
1033
|
+
if (end === -1) throw new Error(`Unterminated bracket in: ${atomicPath}`);
|
|
1034
|
+
const name = atomicPath.slice(i + 1, end);
|
|
1035
|
+
result += `['${name.replace(/'/g, "''")}']`;
|
|
1036
|
+
i = end + 1;
|
|
1037
|
+
}
|
|
1038
|
+
} else {
|
|
1039
|
+
throw new Error(`Unexpected character '${atomicPath[i]}' in atomic path: ${atomicPath}`);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return result;
|
|
1043
|
+
}
|
|
1044
|
+
function atomPathToAtomicPath(atomPath) {
|
|
1045
|
+
if (!atomPath.startsWith("$")) {
|
|
1046
|
+
throw new Error(`Atom path must start with '$': ${atomPath}`);
|
|
1047
|
+
}
|
|
1048
|
+
if (atomPath === "$") {
|
|
1049
|
+
return "$.$root";
|
|
1050
|
+
}
|
|
1051
|
+
let result = "$";
|
|
1052
|
+
let i = 1;
|
|
1053
|
+
while (i < atomPath.length) {
|
|
1054
|
+
if (atomPath[i] === ".") {
|
|
1055
|
+
i += 1;
|
|
1056
|
+
const start = i;
|
|
1057
|
+
while (i < atomPath.length && /[a-zA-Z0-9_]/.test(atomPath[i])) {
|
|
1058
|
+
i += 1;
|
|
1059
|
+
}
|
|
1060
|
+
result += "." + atomPath.slice(start, i);
|
|
1061
|
+
} else if (atomPath[i] === "[") {
|
|
1062
|
+
if (atomPath[i + 1] === "?") {
|
|
1063
|
+
const closingIdx = findFilterClose(atomPath, i + 2);
|
|
1064
|
+
if (closingIdx === -1) throw new Error(`Unterminated filter in: ${atomPath}`);
|
|
1065
|
+
const inner = atomPath.slice(i + 3, closingIdx);
|
|
1066
|
+
result += canonicalizeFilterForV4(inner);
|
|
1067
|
+
i = closingIdx + 2;
|
|
1068
|
+
} else if (atomPath[i + 1] === "'") {
|
|
1069
|
+
const [key, endIdx] = extractQuotedString(atomPath, i + 2);
|
|
1070
|
+
if (atomPath[endIdx + 1] !== "]") throw new Error(`Expected ']' in: ${atomPath}`);
|
|
1071
|
+
result += `[${key}]`;
|
|
1072
|
+
i = endIdx + 2;
|
|
1073
|
+
} else if (/\d/.test(atomPath[i + 1])) {
|
|
1074
|
+
const end = atomPath.indexOf("]", i);
|
|
1075
|
+
if (end === -1) throw new Error(`Unterminated bracket in: ${atomPath}`);
|
|
1076
|
+
result += atomPath.slice(i, end + 1);
|
|
1077
|
+
i = end + 1;
|
|
1078
|
+
} else {
|
|
1079
|
+
throw new Error(`Unexpected character after '[' in: ${atomPath}`);
|
|
1080
|
+
}
|
|
1081
|
+
} else {
|
|
1082
|
+
throw new Error(`Unexpected character '${atomPath[i]}' in atom path: ${atomPath}`);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return result;
|
|
1086
|
+
}
|
|
1087
|
+
function normalizeFilterToStringLiterals(filter) {
|
|
1088
|
+
const eqIdx = filter.indexOf("==");
|
|
1089
|
+
if (eqIdx === -1) return filter;
|
|
1090
|
+
const literalStart = eqIdx + 2;
|
|
1091
|
+
const literalEnd = filter.length - 2;
|
|
1092
|
+
const literal = filter.slice(literalStart, literalEnd);
|
|
1093
|
+
if (literal.startsWith("'") && literal.endsWith("'")) {
|
|
1094
|
+
return filter;
|
|
1095
|
+
}
|
|
1096
|
+
const value = parseFilterLiteral(literal);
|
|
1097
|
+
const stringValue = String(value).replace(/'/g, "''");
|
|
1098
|
+
return filter.slice(0, literalStart) + `'${stringValue}'` + filter.slice(literalEnd);
|
|
1099
|
+
}
|
|
1100
|
+
function extractKeyFromAtomicPath(atomicPath) {
|
|
1101
|
+
if (atomicPath === "$.$root") return "$root";
|
|
1102
|
+
if (atomicPath.endsWith(")]")) {
|
|
1103
|
+
const filterStart = atomicPath.lastIndexOf("[?(");
|
|
1104
|
+
if (filterStart !== -1) {
|
|
1105
|
+
const inner = atomicPath.slice(filterStart + 3, atomicPath.length - 2);
|
|
1106
|
+
if (inner.startsWith("@==")) {
|
|
1107
|
+
const val = parseFilterLiteral(inner.slice(3));
|
|
1108
|
+
return String(val);
|
|
1109
|
+
}
|
|
1110
|
+
const eqIdx = inner.indexOf("==");
|
|
1111
|
+
if (eqIdx !== -1) {
|
|
1112
|
+
const val = parseFilterLiteral(inner.slice(eqIdx + 2));
|
|
1113
|
+
return String(val);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (atomicPath.endsWith("]")) {
|
|
1118
|
+
const bracketStart = atomicPath.lastIndexOf("[");
|
|
1119
|
+
if (bracketStart !== -1) {
|
|
1120
|
+
const inner = atomicPath.slice(bracketStart + 1, atomicPath.length - 1);
|
|
1121
|
+
if (/^\d+$/.test(inner)) return inner;
|
|
1122
|
+
return inner;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
const lastDot = atomicPath.lastIndexOf(".");
|
|
1126
|
+
if (lastDot > 0) {
|
|
1127
|
+
return atomicPath.slice(lastDot + 1);
|
|
1128
|
+
}
|
|
1129
|
+
return atomicPath;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/jsonAtom.ts
|
|
1133
|
+
function validateAtom(atom) {
|
|
1134
|
+
const errors = [];
|
|
1135
|
+
if (typeof atom !== "object" || atom === null) {
|
|
1136
|
+
return { valid: false, errors: ["Atom must be a non-null object"] };
|
|
1137
|
+
}
|
|
1138
|
+
const d = atom;
|
|
1139
|
+
if (d.format !== "json-atom") {
|
|
1140
|
+
errors.push(`Invalid or missing format: expected 'json-atom', got '${d.format}'`);
|
|
1141
|
+
}
|
|
1142
|
+
if (typeof d.version !== "number") {
|
|
1143
|
+
errors.push(`Missing or invalid version: expected number, got '${typeof d.version}'`);
|
|
1144
|
+
}
|
|
1145
|
+
if (!Array.isArray(d.operations)) {
|
|
1146
|
+
errors.push("Missing or invalid operations: expected array");
|
|
1147
|
+
} else {
|
|
1148
|
+
for (let i = 0; i < d.operations.length; i++) {
|
|
1149
|
+
const op = d.operations[i];
|
|
1150
|
+
if (!op || typeof op !== "object") {
|
|
1151
|
+
errors.push(`operations[${i}]: must be an object`);
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
if (!["add", "remove", "replace"].includes(op.op)) {
|
|
1155
|
+
errors.push(`operations[${i}]: invalid op '${op.op}'`);
|
|
1156
|
+
}
|
|
1157
|
+
if (typeof op.path !== "string") {
|
|
1158
|
+
errors.push(`operations[${i}]: path must be a string`);
|
|
1159
|
+
}
|
|
1160
|
+
if (op.op === "add") {
|
|
1161
|
+
if (!("value" in op)) {
|
|
1162
|
+
errors.push(`operations[${i}]: add operation must have value`);
|
|
1163
|
+
}
|
|
1164
|
+
if ("oldValue" in op) {
|
|
1165
|
+
errors.push(`operations[${i}]: add operation must not have oldValue`);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (op.op === "remove" && "value" in op) {
|
|
1169
|
+
errors.push(`operations[${i}]: remove operation must not have value`);
|
|
1170
|
+
}
|
|
1171
|
+
if (op.op === "replace" && !("value" in op)) {
|
|
1172
|
+
errors.push(`operations[${i}]: replace operation must have value`);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
return { valid: errors.length === 0, errors };
|
|
1177
|
+
}
|
|
1178
|
+
function diffAtom(oldObj, newObj, options = {}) {
|
|
1179
|
+
const changeset = diff(oldObj, newObj, {
|
|
1180
|
+
...options,
|
|
1181
|
+
treatTypeChangeAsReplace: true
|
|
1182
|
+
// Always true — merging REMOVE+ADD is more reliable (B.1)
|
|
1183
|
+
});
|
|
1184
|
+
const operations = [];
|
|
1185
|
+
walkChanges(changeset, "$", oldObj, newObj, operations, options);
|
|
1186
|
+
return {
|
|
1187
|
+
format: "json-atom",
|
|
1188
|
+
version: 1,
|
|
1189
|
+
operations
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function mergeTypeChangePairs(changes) {
|
|
1193
|
+
const result = [];
|
|
1194
|
+
let i = 0;
|
|
1195
|
+
while (i < changes.length) {
|
|
1196
|
+
if (i + 1 < changes.length && changes[i].type === "REMOVE" /* REMOVE */ && changes[i + 1].type === "ADD" /* ADD */ && changes[i].key === changes[i + 1].key) {
|
|
1197
|
+
result.push({
|
|
1198
|
+
...changes[i],
|
|
1199
|
+
isMergedReplace: true,
|
|
1200
|
+
removeValue: changes[i].value,
|
|
1201
|
+
addValue: changes[i + 1].value
|
|
1202
|
+
});
|
|
1203
|
+
i += 2;
|
|
1204
|
+
} else {
|
|
1205
|
+
result.push(changes[i]);
|
|
1206
|
+
i += 1;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return result;
|
|
1210
|
+
}
|
|
1211
|
+
var SIMPLE_PROPERTY_RE2 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
1212
|
+
function appendCanonicalProperty(basePath, name) {
|
|
1213
|
+
if (SIMPLE_PROPERTY_RE2.test(name)) {
|
|
1214
|
+
return `${basePath}.${name}`;
|
|
1215
|
+
}
|
|
1216
|
+
return `${basePath}['${name.replace(/'/g, "''")}']`;
|
|
1217
|
+
}
|
|
1218
|
+
function walkChanges(changes, basePath, oldCtx, newCtx, ops, options) {
|
|
1219
|
+
const merged = mergeTypeChangePairs(changes);
|
|
1220
|
+
for (const change of merged) {
|
|
1221
|
+
if (change.isMergedReplace) {
|
|
1222
|
+
const mc = change;
|
|
1223
|
+
const path = mc.key === "$root" ? "$" : appendCanonicalProperty(basePath, mc.key);
|
|
1224
|
+
const op = { op: "replace", path, value: mc.addValue };
|
|
1225
|
+
if (options.reversible !== false) {
|
|
1226
|
+
op.oldValue = mc.removeValue;
|
|
1227
|
+
}
|
|
1228
|
+
ops.push(op);
|
|
1229
|
+
} else if (change.changes) {
|
|
1230
|
+
const childPath = change.key === "$root" ? "$" : appendCanonicalProperty(basePath, change.key);
|
|
1231
|
+
const childOld = change.key === "$root" ? oldCtx : oldCtx?.[change.key];
|
|
1232
|
+
const childNew = change.key === "$root" ? newCtx : newCtx?.[change.key];
|
|
1233
|
+
if (change.embeddedKey) {
|
|
1234
|
+
for (const childChange of change.changes) {
|
|
1235
|
+
const filterPath = buildCanonicalFilterPath(
|
|
1236
|
+
childPath,
|
|
1237
|
+
change.embeddedKey,
|
|
1238
|
+
childChange.key,
|
|
1239
|
+
childOld,
|
|
1240
|
+
childNew,
|
|
1241
|
+
childChange
|
|
1242
|
+
);
|
|
1243
|
+
if (childChange.changes) {
|
|
1244
|
+
const oldEl = findElement(childOld, change.embeddedKey, childChange.key);
|
|
1245
|
+
const newEl = findElement(childNew, change.embeddedKey, childChange.key);
|
|
1246
|
+
walkChanges(childChange.changes, filterPath, oldEl, newEl, ops, options);
|
|
1247
|
+
} else {
|
|
1248
|
+
emitLeafOp(childChange, filterPath, ops, options);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
} else {
|
|
1252
|
+
walkChanges(change.changes, childPath, childOld, childNew, ops, options);
|
|
1253
|
+
}
|
|
1254
|
+
} else {
|
|
1255
|
+
const path = change.key === "$root" ? "$" : appendCanonicalProperty(basePath, change.key);
|
|
1256
|
+
emitLeafOp(change, path, ops, options);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
function emitLeafOp(change, path, ops, options) {
|
|
1261
|
+
switch (change.type) {
|
|
1262
|
+
case "ADD" /* ADD */: {
|
|
1263
|
+
ops.push({ op: "add", path, value: change.value });
|
|
1264
|
+
break;
|
|
1265
|
+
}
|
|
1266
|
+
case "REMOVE" /* REMOVE */: {
|
|
1267
|
+
const op = { op: "remove", path };
|
|
1268
|
+
if (options.reversible !== false) {
|
|
1269
|
+
op.oldValue = change.value;
|
|
1270
|
+
}
|
|
1271
|
+
ops.push(op);
|
|
1272
|
+
break;
|
|
1273
|
+
}
|
|
1274
|
+
case "UPDATE" /* UPDATE */: {
|
|
1275
|
+
const op = { op: "replace", path, value: change.value };
|
|
1276
|
+
if (options.reversible !== false) {
|
|
1277
|
+
op.oldValue = change.oldValue;
|
|
1278
|
+
}
|
|
1279
|
+
ops.push(op);
|
|
1280
|
+
break;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function buildCanonicalFilterPath(basePath, embeddedKey, changeKey, oldArr, newArr, change) {
|
|
1285
|
+
if (embeddedKey === "$index") {
|
|
1286
|
+
return `${basePath}[${changeKey}]`;
|
|
1287
|
+
}
|
|
1288
|
+
if (embeddedKey === "$value") {
|
|
1289
|
+
const typedVal2 = findActualValue(oldArr, newArr, changeKey, change.type);
|
|
1290
|
+
return `${basePath}[?(@==${formatFilterLiteral(typedVal2)})]`;
|
|
1291
|
+
}
|
|
1292
|
+
if (typeof embeddedKey === "function") {
|
|
1293
|
+
const sample = oldArr && oldArr.length > 0 ? oldArr[0] : newArr?.[0];
|
|
1294
|
+
const keyName = sample ? embeddedKey(sample, true) : changeKey;
|
|
1295
|
+
const element2 = findElementByFn(oldArr, newArr, embeddedKey, changeKey, change.type);
|
|
1296
|
+
if (element2 && typeof keyName === "string") {
|
|
1297
|
+
const typedVal2 = element2[keyName];
|
|
1298
|
+
const memberAccess3 = SIMPLE_PROPERTY_RE2.test(keyName) ? `.${keyName}` : `['${keyName.replace(/'/g, "''")}']`;
|
|
1299
|
+
return `${basePath}[?(@${memberAccess3}==${formatFilterLiteral(typedVal2)})]`;
|
|
1300
|
+
}
|
|
1301
|
+
const memberAccess2 = typeof keyName === "string" && SIMPLE_PROPERTY_RE2.test(keyName) ? `.${keyName}` : `.${changeKey}`;
|
|
1302
|
+
return `${basePath}[?(@${memberAccess2}=='${changeKey}')]`;
|
|
1303
|
+
}
|
|
1304
|
+
const element = findElementByKey(oldArr, newArr, embeddedKey, changeKey, change.type);
|
|
1305
|
+
const typedVal = element ? element[embeddedKey] : changeKey;
|
|
1306
|
+
const memberAccess = SIMPLE_PROPERTY_RE2.test(embeddedKey) ? `.${embeddedKey}` : `['${embeddedKey.replace(/'/g, "''")}']`;
|
|
1307
|
+
return `${basePath}[?(@${memberAccess}==${formatFilterLiteral(typedVal)})]`;
|
|
1308
|
+
}
|
|
1309
|
+
function findActualValue(oldArr, newArr, stringKey, opType) {
|
|
1310
|
+
if (opType === "REMOVE" /* REMOVE */ && oldArr) {
|
|
1311
|
+
for (const item of oldArr) {
|
|
1312
|
+
if (String(item) === stringKey) return item;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
if (opType === "ADD" /* ADD */ && newArr) {
|
|
1316
|
+
for (const item of newArr) {
|
|
1317
|
+
if (String(item) === stringKey) return item;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
if (oldArr) {
|
|
1321
|
+
for (const item of oldArr) {
|
|
1322
|
+
if (String(item) === stringKey) return item;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
if (newArr) {
|
|
1326
|
+
for (const item of newArr) {
|
|
1327
|
+
if (String(item) === stringKey) return item;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
return stringKey;
|
|
1331
|
+
}
|
|
1332
|
+
function findElement(arr, embeddedKey, changeKey) {
|
|
1333
|
+
if (!arr || !Array.isArray(arr)) return void 0;
|
|
1334
|
+
if (embeddedKey === "$index") {
|
|
1335
|
+
return arr[Number(changeKey)];
|
|
1336
|
+
}
|
|
1337
|
+
if (embeddedKey === "$value") {
|
|
1338
|
+
return arr.find((item) => String(item) === changeKey);
|
|
1339
|
+
}
|
|
1340
|
+
if (typeof embeddedKey === "function") {
|
|
1341
|
+
return arr.find((item) => String(embeddedKey(item)) === changeKey);
|
|
1342
|
+
}
|
|
1343
|
+
return arr.find((item) => item && String(item[embeddedKey]) === changeKey);
|
|
1344
|
+
}
|
|
1345
|
+
function findElementByKey(oldArr, newArr, embeddedKey, changeKey, opType) {
|
|
1346
|
+
if (opType === "REMOVE" /* REMOVE */ || opType === "UPDATE" /* UPDATE */) {
|
|
1347
|
+
const el = oldArr?.find((item) => item && String(item[embeddedKey]) === changeKey);
|
|
1348
|
+
if (el) return el;
|
|
1349
|
+
}
|
|
1350
|
+
if (opType === "ADD" /* ADD */ || opType === "UPDATE" /* UPDATE */) {
|
|
1351
|
+
const el = newArr?.find((item) => item && String(item[embeddedKey]) === changeKey);
|
|
1352
|
+
if (el) return el;
|
|
1353
|
+
}
|
|
1354
|
+
return void 0;
|
|
1355
|
+
}
|
|
1356
|
+
function findElementByFn(oldArr, newArr, fn, changeKey, opType) {
|
|
1357
|
+
if (opType === "REMOVE" /* REMOVE */ || opType === "UPDATE" /* UPDATE */) {
|
|
1358
|
+
const el = oldArr?.find((item) => String(fn(item)) === changeKey);
|
|
1359
|
+
if (el) return el;
|
|
1360
|
+
}
|
|
1361
|
+
if (opType === "ADD" /* ADD */ || opType === "UPDATE" /* UPDATE */) {
|
|
1362
|
+
const el = newArr?.find((item) => String(fn(item)) === changeKey);
|
|
1363
|
+
if (el) return el;
|
|
1364
|
+
}
|
|
1365
|
+
return void 0;
|
|
1366
|
+
}
|
|
1367
|
+
function toAtom(changeset, options = {}) {
|
|
1368
|
+
let atoms;
|
|
1369
|
+
if (changeset.length === 0) {
|
|
1370
|
+
return { format: "json-atom", version: 1, operations: [] };
|
|
1371
|
+
}
|
|
1372
|
+
if ("path" in changeset[0]) {
|
|
1373
|
+
atoms = changeset;
|
|
1374
|
+
} else {
|
|
1375
|
+
atoms = atomizeChangeset(changeset);
|
|
1376
|
+
}
|
|
1377
|
+
const rawOps = atoms.map((atom) => {
|
|
1378
|
+
const path = atomicPathToAtomPath(atom.path);
|
|
1379
|
+
switch (atom.type) {
|
|
1380
|
+
case "ADD" /* ADD */:
|
|
1381
|
+
return { op: "add", path, value: atom.value };
|
|
1382
|
+
case "REMOVE" /* REMOVE */: {
|
|
1383
|
+
const op = { op: "remove", path };
|
|
1384
|
+
if (options.reversible !== false && atom.value !== void 0) {
|
|
1385
|
+
op.oldValue = atom.value;
|
|
1386
|
+
}
|
|
1387
|
+
return op;
|
|
1388
|
+
}
|
|
1389
|
+
case "UPDATE" /* UPDATE */: {
|
|
1390
|
+
const op = { op: "replace", path, value: atom.value };
|
|
1391
|
+
if (options.reversible !== false && atom.oldValue !== void 0) {
|
|
1392
|
+
op.oldValue = atom.oldValue;
|
|
1393
|
+
}
|
|
1394
|
+
return op;
|
|
1395
|
+
}
|
|
1396
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1397
|
+
default:
|
|
1398
|
+
throw new Error(`Unknown operation type: ${atom.type}`);
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
const operations = mergeConsecutiveOps(rawOps);
|
|
1402
|
+
return { format: "json-atom", version: 1, operations };
|
|
1403
|
+
}
|
|
1404
|
+
function mergeConsecutiveOps(ops) {
|
|
1405
|
+
const result = [];
|
|
1406
|
+
let i = 0;
|
|
1407
|
+
while (i < ops.length) {
|
|
1408
|
+
if (i + 1 < ops.length && ops[i].op === "remove" && ops[i + 1].op === "add" && ops[i].path === ops[i + 1].path) {
|
|
1409
|
+
const merged = {
|
|
1410
|
+
op: "replace",
|
|
1411
|
+
path: ops[i].path,
|
|
1412
|
+
value: ops[i + 1].value
|
|
1413
|
+
};
|
|
1414
|
+
if (ops[i].oldValue !== void 0) {
|
|
1415
|
+
merged.oldValue = ops[i].oldValue;
|
|
1416
|
+
}
|
|
1417
|
+
result.push(merged);
|
|
1418
|
+
i += 2;
|
|
1419
|
+
} else {
|
|
1420
|
+
result.push(ops[i]);
|
|
1421
|
+
i += 1;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
return result;
|
|
1425
|
+
}
|
|
1426
|
+
function fromAtom(atom) {
|
|
1427
|
+
const validation = validateAtom(atom);
|
|
1428
|
+
if (!validation.valid) {
|
|
1429
|
+
throw new Error(`Invalid atom: ${validation.errors.join(", ")}`);
|
|
1430
|
+
}
|
|
1431
|
+
return atom.operations.map((op) => {
|
|
1432
|
+
const atomicPath = atomPathToAtomicPath(op.path);
|
|
1433
|
+
const key = extractKeyFromAtomicPath(atomicPath);
|
|
1434
|
+
switch (op.op) {
|
|
1435
|
+
case "add": {
|
|
1436
|
+
const valueType = getValueType(op.value);
|
|
1437
|
+
return { type: "ADD" /* ADD */, key, path: atomicPath, valueType, value: op.value };
|
|
1438
|
+
}
|
|
1439
|
+
case "remove": {
|
|
1440
|
+
const valueType = op.oldValue !== void 0 ? getValueType(op.oldValue) : null;
|
|
1441
|
+
return { type: "REMOVE" /* REMOVE */, key, path: atomicPath, valueType, value: op.oldValue };
|
|
1442
|
+
}
|
|
1443
|
+
case "replace": {
|
|
1444
|
+
const valueType = getValueType(op.value);
|
|
1445
|
+
const atom2 = { type: "UPDATE" /* UPDATE */, key, path: atomicPath, valueType, value: op.value };
|
|
1446
|
+
if (op.oldValue !== void 0) {
|
|
1447
|
+
atom2.oldValue = op.oldValue;
|
|
1448
|
+
}
|
|
1449
|
+
return atom2;
|
|
1450
|
+
}
|
|
1451
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1452
|
+
default:
|
|
1453
|
+
throw new Error(`Unknown operation: ${op.op}`);
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
function getValueType(value) {
|
|
1458
|
+
if (value === void 0) return "undefined";
|
|
1459
|
+
if (value === null) return null;
|
|
1460
|
+
if (Array.isArray(value)) return "Array";
|
|
1461
|
+
const type = typeof value;
|
|
1462
|
+
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
1463
|
+
}
|
|
1464
|
+
function invertAtom(atom) {
|
|
1465
|
+
const validation = validateAtom(atom);
|
|
1466
|
+
if (!validation.valid) {
|
|
1467
|
+
throw new Error(`Invalid atom: ${validation.errors.join(", ")}`);
|
|
1468
|
+
}
|
|
1469
|
+
for (let i = 0; i < atom.operations.length; i++) {
|
|
1470
|
+
const op = atom.operations[i];
|
|
1471
|
+
if (op.op === "replace" && !("oldValue" in op)) {
|
|
1472
|
+
throw new Error(`operations[${i}]: replace operation missing oldValue \u2014 atom is not reversible`);
|
|
1473
|
+
}
|
|
1474
|
+
if (op.op === "remove" && !("oldValue" in op)) {
|
|
1475
|
+
throw new Error(`operations[${i}]: remove operation missing oldValue \u2014 atom is not reversible`);
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
const invertedOps = [...atom.operations].reverse().map((op) => {
|
|
1479
|
+
const extensions = {};
|
|
1480
|
+
for (const key of Object.keys(op)) {
|
|
1481
|
+
if (!["op", "path", "value", "oldValue"].includes(key)) {
|
|
1482
|
+
extensions[key] = op[key];
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
switch (op.op) {
|
|
1486
|
+
case "add":
|
|
1487
|
+
return { op: "remove", path: op.path, oldValue: op.value, ...extensions };
|
|
1488
|
+
case "remove":
|
|
1489
|
+
return { op: "add", path: op.path, value: op.oldValue, ...extensions };
|
|
1490
|
+
case "replace":
|
|
1491
|
+
return { op: "replace", path: op.path, value: op.oldValue, oldValue: op.value, ...extensions };
|
|
1492
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1493
|
+
default:
|
|
1494
|
+
throw new Error(`Unknown operation: ${op.op}`);
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
1497
|
+
const envelope = { format: "json-atom", version: atom.version, operations: invertedOps };
|
|
1498
|
+
for (const key of Object.keys(atom)) {
|
|
1499
|
+
if (!["format", "version", "operations"].includes(key)) {
|
|
1500
|
+
envelope[key] = atom[key];
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return envelope;
|
|
1504
|
+
}
|
|
1505
|
+
function applyAtom(obj, atom) {
|
|
1506
|
+
const validation = validateAtom(atom);
|
|
1507
|
+
if (!validation.valid) {
|
|
1508
|
+
throw new Error(`Invalid atom: ${validation.errors.join(", ")}`);
|
|
1509
|
+
}
|
|
1510
|
+
let result = obj;
|
|
1511
|
+
for (const op of atom.operations) {
|
|
1512
|
+
if (op.path === "$") {
|
|
1513
|
+
result = applyRootOp(result, op);
|
|
1514
|
+
} else {
|
|
1515
|
+
const atomicChange = atomOpToAtomicChange(op);
|
|
1516
|
+
const miniChangeset = unatomizeChangeset([atomicChange]);
|
|
1517
|
+
applyChangeset(result, miniChangeset);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return result;
|
|
1521
|
+
}
|
|
1522
|
+
function applyRootOp(obj, op) {
|
|
1523
|
+
switch (op.op) {
|
|
1524
|
+
case "add":
|
|
1525
|
+
return op.value;
|
|
1526
|
+
case "remove":
|
|
1527
|
+
return null;
|
|
1528
|
+
case "replace": {
|
|
1529
|
+
if (typeof obj === "object" && obj !== null && !Array.isArray(obj) && typeof op.value === "object" && op.value !== null && !Array.isArray(op.value)) {
|
|
1530
|
+
for (const key of Object.keys(obj)) {
|
|
1531
|
+
delete obj[key];
|
|
1532
|
+
}
|
|
1533
|
+
Object.assign(obj, op.value);
|
|
1534
|
+
return obj;
|
|
1535
|
+
}
|
|
1536
|
+
return op.value;
|
|
1537
|
+
}
|
|
1538
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1539
|
+
default:
|
|
1540
|
+
throw new Error(`Unknown operation: ${op.op}`);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
function atomOpToAtomicChange(op) {
|
|
1544
|
+
const atomicPath = atomPathToAtomicPath(op.path);
|
|
1545
|
+
const key = extractKeyFromAtomicPath(atomicPath);
|
|
1546
|
+
switch (op.op) {
|
|
1547
|
+
case "add":
|
|
1548
|
+
return { type: "ADD" /* ADD */, key, path: atomicPath, valueType: getValueType(op.value), value: op.value };
|
|
1549
|
+
case "remove":
|
|
1550
|
+
return { type: "REMOVE" /* REMOVE */, key, path: atomicPath, valueType: getValueType(op.oldValue), value: op.oldValue };
|
|
1551
|
+
case "replace":
|
|
1552
|
+
return {
|
|
1553
|
+
type: "UPDATE" /* UPDATE */,
|
|
1554
|
+
key,
|
|
1555
|
+
path: atomicPath,
|
|
1556
|
+
valueType: getValueType(op.value),
|
|
1557
|
+
value: op.value,
|
|
1558
|
+
oldValue: op.oldValue
|
|
1559
|
+
};
|
|
1560
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1561
|
+
default:
|
|
1562
|
+
throw new Error(`Unknown operation: ${op.op}`);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
function revertAtom(obj, atom) {
|
|
1566
|
+
const inverse = invertAtom(atom);
|
|
1567
|
+
return applyAtom(obj, inverse);
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
// src/atomHelpers.ts
|
|
1571
|
+
var OP_SPEC_KEYS = /* @__PURE__ */ new Set(["op", "path", "value", "oldValue"]);
|
|
1572
|
+
var ATOM_SPEC_KEYS = /* @__PURE__ */ new Set(["format", "version", "operations"]);
|
|
1573
|
+
function operationSpecDict(op) {
|
|
1574
|
+
const result = { op: op.op, path: op.path };
|
|
1575
|
+
if ("value" in op) result.value = op.value;
|
|
1576
|
+
if ("oldValue" in op) result.oldValue = op.oldValue;
|
|
1577
|
+
return result;
|
|
1578
|
+
}
|
|
1579
|
+
function operationExtensions(op) {
|
|
1580
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
1581
|
+
for (const key of Object.keys(op)) {
|
|
1582
|
+
if (!OP_SPEC_KEYS.has(key)) {
|
|
1583
|
+
result[key] = op[key];
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
return result;
|
|
1587
|
+
}
|
|
1588
|
+
function leafProperty(op) {
|
|
1589
|
+
const segments = parseAtomPath(op.path);
|
|
1590
|
+
if (segments.length === 0) return null;
|
|
1591
|
+
const last = segments[segments.length - 1];
|
|
1592
|
+
return last.type === "property" ? last.name : null;
|
|
1593
|
+
}
|
|
1594
|
+
function atomSpecDict(atom) {
|
|
1595
|
+
return {
|
|
1596
|
+
format: atom.format,
|
|
1597
|
+
version: atom.version,
|
|
1598
|
+
operations: atom.operations.map(operationSpecDict)
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function atomExtensions(atom) {
|
|
1602
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
1603
|
+
for (const key of Object.keys(atom)) {
|
|
1604
|
+
if (!ATOM_SPEC_KEYS.has(key)) {
|
|
1605
|
+
result[key] = atom[key];
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
return result;
|
|
1609
|
+
}
|
|
1610
|
+
function atomMap(atom, fn) {
|
|
1611
|
+
return { ...atom, operations: atom.operations.map((op, i) => fn(op, i)) };
|
|
1612
|
+
}
|
|
1613
|
+
function atomStamp(atom, extensions) {
|
|
1614
|
+
return atomMap(atom, (op) => ({ ...op, ...extensions }));
|
|
1615
|
+
}
|
|
1616
|
+
function atomGroupBy(atom, keyFn) {
|
|
1617
|
+
const groups = /* @__PURE__ */ Object.create(null);
|
|
1618
|
+
for (const op of atom.operations) {
|
|
1619
|
+
const k = keyFn(op);
|
|
1620
|
+
if (!groups[k]) groups[k] = [];
|
|
1621
|
+
groups[k].push(op);
|
|
1622
|
+
}
|
|
1623
|
+
const envelope = /* @__PURE__ */ Object.create(null);
|
|
1624
|
+
for (const key of Object.keys(atom)) {
|
|
1625
|
+
if (key !== "operations") {
|
|
1626
|
+
envelope[key] = atom[key];
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
1630
|
+
for (const [k, ops] of Object.entries(groups)) {
|
|
1631
|
+
result[k] = { ...envelope, operations: ops };
|
|
1632
|
+
}
|
|
1633
|
+
return result;
|
|
1634
|
+
}
|
|
1635
|
+
function deepClone(obj) {
|
|
1636
|
+
return JSON.parse(JSON.stringify(obj));
|
|
1637
|
+
}
|
|
1638
|
+
function squashAtoms(source, atoms, options = {}) {
|
|
1639
|
+
const { target, verifyTarget = true, ...diffOptions } = options;
|
|
1640
|
+
let final;
|
|
1641
|
+
if (target !== void 0 && atoms.length > 0 && verifyTarget) {
|
|
1642
|
+
let computed = deepClone(source);
|
|
1643
|
+
for (const d of atoms) {
|
|
1644
|
+
computed = applyAtom(computed, d);
|
|
1645
|
+
}
|
|
1646
|
+
const verification = diffAtom(computed, target, diffOptions);
|
|
1647
|
+
if (verification.operations.length > 0) {
|
|
1648
|
+
throw new Error(
|
|
1649
|
+
"squashAtoms: provided target does not match sequential application of atoms to source"
|
|
1650
|
+
);
|
|
1651
|
+
}
|
|
1652
|
+
final = target;
|
|
1653
|
+
} else if (target !== void 0) {
|
|
1654
|
+
final = target;
|
|
1655
|
+
} else {
|
|
1656
|
+
final = deepClone(source);
|
|
1657
|
+
for (const d of atoms) {
|
|
1658
|
+
final = applyAtom(final, d);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
const result = diffAtom(source, final, diffOptions);
|
|
1662
|
+
for (const d of atoms) {
|
|
1663
|
+
for (const key of Object.keys(d)) {
|
|
1664
|
+
if (!ATOM_SPEC_KEYS.has(key)) {
|
|
1665
|
+
Object.defineProperty(result, key, {
|
|
1666
|
+
value: d[key],
|
|
1667
|
+
writable: true,
|
|
1668
|
+
enumerable: true,
|
|
1669
|
+
configurable: true
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
return result;
|
|
1675
|
+
}
|
|
783
1676
|
export {
|
|
784
1677
|
CompareOperation,
|
|
785
1678
|
Operation,
|
|
1679
|
+
applyAtom,
|
|
786
1680
|
applyChangelist,
|
|
787
1681
|
applyChangeset,
|
|
1682
|
+
atomExtensions,
|
|
1683
|
+
atomGroupBy,
|
|
1684
|
+
atomMap,
|
|
1685
|
+
atomSpecDict,
|
|
1686
|
+
atomStamp,
|
|
788
1687
|
atomizeChangeset,
|
|
1688
|
+
buildAtomPath,
|
|
789
1689
|
compare2 as compare,
|
|
1690
|
+
comparisonToDict,
|
|
1691
|
+
comparisonToFlatList,
|
|
790
1692
|
createContainer,
|
|
791
1693
|
createValue,
|
|
792
1694
|
diff,
|
|
1695
|
+
diffAtom,
|
|
793
1696
|
enrich,
|
|
1697
|
+
formatFilterLiteral,
|
|
1698
|
+
fromAtom,
|
|
794
1699
|
getTypeOfObj,
|
|
1700
|
+
invertAtom,
|
|
1701
|
+
leafProperty,
|
|
1702
|
+
operationExtensions,
|
|
1703
|
+
operationSpecDict,
|
|
1704
|
+
parseAtomPath,
|
|
1705
|
+
parseFilterLiteral,
|
|
1706
|
+
revertAtom,
|
|
795
1707
|
revertChangeset,
|
|
796
|
-
|
|
1708
|
+
squashAtoms,
|
|
1709
|
+
toAtom,
|
|
1710
|
+
unatomizeChangeset,
|
|
1711
|
+
validateAtom
|
|
797
1712
|
};
|
|
798
1713
|
//# sourceMappingURL=index.js.map
|