json-diff-ts 5.0.0-alpha.1 → 5.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +613 -318
- package/dist/index.cjs +1008 -107
- 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 +985 -106
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -24,15 +24,37 @@ __export(index_exports, {
|
|
|
24
24
|
Operation: () => Operation,
|
|
25
25
|
applyChangelist: () => applyChangelist,
|
|
26
26
|
applyChangeset: () => applyChangeset,
|
|
27
|
+
applyDelta: () => applyDelta,
|
|
27
28
|
atomizeChangeset: () => atomizeChangeset,
|
|
29
|
+
buildDeltaPath: () => buildDeltaPath,
|
|
28
30
|
compare: () => compare2,
|
|
31
|
+
comparisonToDict: () => comparisonToDict,
|
|
32
|
+
comparisonToFlatList: () => comparisonToFlatList,
|
|
29
33
|
createContainer: () => createContainer,
|
|
30
34
|
createValue: () => createValue,
|
|
35
|
+
deltaExtensions: () => deltaExtensions,
|
|
36
|
+
deltaGroupBy: () => deltaGroupBy,
|
|
37
|
+
deltaMap: () => deltaMap,
|
|
38
|
+
deltaSpecDict: () => deltaSpecDict,
|
|
39
|
+
deltaStamp: () => deltaStamp,
|
|
31
40
|
diff: () => diff,
|
|
41
|
+
diffDelta: () => diffDelta,
|
|
32
42
|
enrich: () => enrich,
|
|
43
|
+
formatFilterLiteral: () => formatFilterLiteral,
|
|
44
|
+
fromDelta: () => fromDelta,
|
|
33
45
|
getTypeOfObj: () => getTypeOfObj,
|
|
46
|
+
invertDelta: () => invertDelta,
|
|
47
|
+
leafProperty: () => leafProperty,
|
|
48
|
+
operationExtensions: () => operationExtensions,
|
|
49
|
+
operationSpecDict: () => operationSpecDict,
|
|
50
|
+
parseDeltaPath: () => parseDeltaPath,
|
|
51
|
+
parseFilterLiteral: () => parseFilterLiteral,
|
|
34
52
|
revertChangeset: () => revertChangeset,
|
|
35
|
-
|
|
53
|
+
revertDelta: () => revertDelta,
|
|
54
|
+
squashDeltas: () => squashDeltas,
|
|
55
|
+
toDelta: () => toDelta,
|
|
56
|
+
unatomizeChangeset: () => unatomizeChangeset,
|
|
57
|
+
validateDelta: () => validateDelta
|
|
36
58
|
});
|
|
37
59
|
module.exports = __toCommonJS(index_exports);
|
|
38
60
|
|
|
@@ -73,13 +95,13 @@ function arrayIntersection(first, second) {
|
|
|
73
95
|
}
|
|
74
96
|
function keyBy(arr, getKey2) {
|
|
75
97
|
const result = {};
|
|
76
|
-
for (const item of arr) {
|
|
77
|
-
result[String(getKey2(item))] = item;
|
|
98
|
+
for (const [index, item] of Object.entries(arr)) {
|
|
99
|
+
result[String(getKey2(item, false, Number(index)))] = item;
|
|
78
100
|
}
|
|
79
101
|
return result;
|
|
80
102
|
}
|
|
81
103
|
function setByPath(obj, path, value) {
|
|
82
|
-
const parts = path.
|
|
104
|
+
const parts = path.replaceAll(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
|
|
83
105
|
let current = obj;
|
|
84
106
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
85
107
|
const part = parts[i];
|
|
@@ -88,7 +110,7 @@ function setByPath(obj, path, value) {
|
|
|
88
110
|
}
|
|
89
111
|
current = current[part];
|
|
90
112
|
}
|
|
91
|
-
current[parts
|
|
113
|
+
current[parts.at(-1)] = value;
|
|
92
114
|
}
|
|
93
115
|
|
|
94
116
|
// src/jsonDiff.ts
|
|
@@ -96,12 +118,11 @@ var Operation = /* @__PURE__ */ ((Operation2) => {
|
|
|
96
118
|
Operation2["REMOVE"] = "REMOVE";
|
|
97
119
|
Operation2["ADD"] = "ADD";
|
|
98
120
|
Operation2["UPDATE"] = "UPDATE";
|
|
99
|
-
Operation2["MOVE"] = "MOVE";
|
|
100
121
|
return Operation2;
|
|
101
122
|
})(Operation || {});
|
|
102
123
|
function diff(oldObj, newObj, options = {}) {
|
|
103
|
-
let
|
|
104
|
-
const { keysToSkip, treatTypeChangeAsReplace
|
|
124
|
+
let embeddedObjKeys = options.arrayIdentityKeys ?? options.embeddedObjKeys;
|
|
125
|
+
const { keysToSkip, treatTypeChangeAsReplace } = options;
|
|
105
126
|
if (embeddedObjKeys instanceof Map) {
|
|
106
127
|
embeddedObjKeys = new Map(
|
|
107
128
|
Array.from(embeddedObjKeys.entries()).map(([key, value]) => [
|
|
@@ -117,8 +138,7 @@ function diff(oldObj, newObj, options = {}) {
|
|
|
117
138
|
return compare(oldObj, newObj, [], [], {
|
|
118
139
|
embeddedObjKeys,
|
|
119
140
|
keysToSkip: keysToSkip ?? [],
|
|
120
|
-
treatTypeChangeAsReplace: treatTypeChangeAsReplace ?? true
|
|
121
|
-
detectArrayMoves: detectArrayMoves2 ?? false
|
|
141
|
+
treatTypeChangeAsReplace: treatTypeChangeAsReplace ?? true
|
|
122
142
|
});
|
|
123
143
|
}
|
|
124
144
|
var applyChangeset = (obj, changeset) => {
|
|
@@ -128,7 +148,7 @@ var applyChangeset = (obj, changeset) => {
|
|
|
128
148
|
if (value !== null && value !== void 0 || type === "REMOVE" /* REMOVE */ || value === null && type === "ADD" /* ADD */ || value === void 0 && type === "ADD" /* ADD */) {
|
|
129
149
|
applyLeafChange(obj, change, embeddedKey);
|
|
130
150
|
} else {
|
|
131
|
-
applyBranchChange(obj[key], change);
|
|
151
|
+
applyBranchChange(key === "$root" ? obj : obj[key], change);
|
|
132
152
|
}
|
|
133
153
|
});
|
|
134
154
|
}
|
|
@@ -141,7 +161,7 @@ var revertChangeset = (obj, changeset) => {
|
|
|
141
161
|
if (!change.changes || value === null && type === "REMOVE" /* REMOVE */) {
|
|
142
162
|
revertLeafChange(obj, change);
|
|
143
163
|
} else {
|
|
144
|
-
revertBranchChange(obj[change.key], change);
|
|
164
|
+
revertBranchChange(change.key === "$root" ? obj : obj[change.key], change);
|
|
145
165
|
}
|
|
146
166
|
});
|
|
147
167
|
}
|
|
@@ -173,7 +193,7 @@ var atomizeChangeset = (obj, path = "$", embeddedKey) => {
|
|
|
173
193
|
if (filterEndIdx !== -1) {
|
|
174
194
|
const filterStartIdx = path.lastIndexOf("==", filterEndIdx);
|
|
175
195
|
if (filterStartIdx !== -1) {
|
|
176
|
-
const filterValue = path.slice(filterStartIdx + 2, filterEndIdx).
|
|
196
|
+
const filterValue = path.slice(filterStartIdx + 2, filterEndIdx).replaceAll(/(^'|'$)/g, "");
|
|
177
197
|
endsWithFilterValue = filterValue === String(obj.key);
|
|
178
198
|
}
|
|
179
199
|
}
|
|
@@ -230,10 +250,6 @@ var unatomizeChangeset = (changes) => {
|
|
|
230
250
|
ptr.type = change.type;
|
|
231
251
|
ptr.value = change.value;
|
|
232
252
|
ptr.oldValue = change.oldValue;
|
|
233
|
-
if (change.type === "MOVE" /* MOVE */) {
|
|
234
|
-
ptr.oldIndex = change.oldIndex;
|
|
235
|
-
ptr.newIndex = change.newIndex;
|
|
236
|
-
}
|
|
237
253
|
changesArr.push(ptr);
|
|
238
254
|
} else {
|
|
239
255
|
for (let i = 1; i < segments.length; i++) {
|
|
@@ -261,8 +277,7 @@ var unatomizeChangeset = (changes) => {
|
|
|
261
277
|
type: change.type,
|
|
262
278
|
key: arrKey,
|
|
263
279
|
value: change.value,
|
|
264
|
-
oldValue: change.oldValue
|
|
265
|
-
...change.type === "MOVE" /* MOVE */ && { oldIndex: change.oldIndex, newIndex: change.newIndex }
|
|
280
|
+
oldValue: change.oldValue
|
|
266
281
|
}
|
|
267
282
|
];
|
|
268
283
|
} else {
|
|
@@ -285,10 +300,6 @@ var unatomizeChangeset = (changes) => {
|
|
|
285
300
|
ptr.type = change.type;
|
|
286
301
|
ptr.value = change.value;
|
|
287
302
|
ptr.oldValue = change.oldValue;
|
|
288
|
-
if (change.type === "MOVE" /* MOVE */) {
|
|
289
|
-
ptr.oldIndex = change.oldIndex;
|
|
290
|
-
ptr.newIndex = change.newIndex;
|
|
291
|
-
}
|
|
292
303
|
} else {
|
|
293
304
|
ptr.key = segment;
|
|
294
305
|
ptr.type = "UPDATE" /* UPDATE */;
|
|
@@ -304,7 +315,7 @@ var unatomizeChangeset = (changes) => {
|
|
|
304
315
|
return changesArr;
|
|
305
316
|
};
|
|
306
317
|
var getTypeOfObj = (obj) => {
|
|
307
|
-
if (
|
|
318
|
+
if (obj === void 0) {
|
|
308
319
|
return "undefined";
|
|
309
320
|
}
|
|
310
321
|
if (obj === null) {
|
|
@@ -313,8 +324,8 @@ var getTypeOfObj = (obj) => {
|
|
|
313
324
|
return Object.prototype.toString.call(obj).match(/^\[object\s(.*)\]$/)[1];
|
|
314
325
|
};
|
|
315
326
|
var getKey = (path) => {
|
|
316
|
-
const left = path
|
|
317
|
-
return left
|
|
327
|
+
const left = path.at(-1);
|
|
328
|
+
return left ?? "$root";
|
|
318
329
|
};
|
|
319
330
|
var compare = (oldObj, newObj, path, keyPath, options) => {
|
|
320
331
|
let changes = [];
|
|
@@ -469,22 +480,17 @@ var compareArray = (oldObj, newObj, path, keyPath, options) => {
|
|
|
469
480
|
return [{ type: "UPDATE" /* UPDATE */, key: getKey(path), value: newObj, oldValue: oldObj }];
|
|
470
481
|
}
|
|
471
482
|
const left = getObjectKey(options.embeddedObjKeys, keyPath);
|
|
472
|
-
const uniqKey = left
|
|
483
|
+
const uniqKey = left ?? "$index";
|
|
473
484
|
const indexedOldObj = convertArrayToObj(oldObj, uniqKey);
|
|
474
485
|
const indexedNewObj = convertArrayToObj(newObj, uniqKey);
|
|
475
486
|
const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options);
|
|
476
|
-
|
|
477
|
-
if (options.detectArrayMoves && uniqKey !== "$index" && left != null) {
|
|
478
|
-
moveDiffs = detectArrayMoves(oldObj, newObj, uniqKey);
|
|
479
|
-
}
|
|
480
|
-
const allDiffs = [...diffs, ...moveDiffs];
|
|
481
|
-
if (allDiffs.length) {
|
|
487
|
+
if (diffs.length) {
|
|
482
488
|
return [
|
|
483
489
|
{
|
|
484
490
|
type: "UPDATE" /* UPDATE */,
|
|
485
491
|
key: getKey(path),
|
|
486
492
|
embeddedKey: typeof uniqKey === "function" && uniqKey.length === 2 ? uniqKey(newObj[0], true) : uniqKey,
|
|
487
|
-
changes:
|
|
493
|
+
changes: diffs
|
|
488
494
|
}
|
|
489
495
|
];
|
|
490
496
|
} else {
|
|
@@ -512,49 +518,20 @@ var getObjectKey = (embeddedObjKeys, keyPath) => {
|
|
|
512
518
|
}
|
|
513
519
|
return void 0;
|
|
514
520
|
};
|
|
515
|
-
var detectArrayMoves = (oldArray, newArray, uniqKey) => {
|
|
516
|
-
const moves = [];
|
|
517
|
-
const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey;
|
|
518
|
-
const oldIndexMap = /* @__PURE__ */ new Map();
|
|
519
|
-
const newIndexMap = /* @__PURE__ */ new Map();
|
|
520
|
-
oldArray.forEach((item, index) => {
|
|
521
|
-
const key = keyFunction(item);
|
|
522
|
-
oldIndexMap.set(key, index);
|
|
523
|
-
});
|
|
524
|
-
newArray.forEach((item, index) => {
|
|
525
|
-
const key = keyFunction(item);
|
|
526
|
-
newIndexMap.set(key, index);
|
|
527
|
-
});
|
|
528
|
-
for (const [key, newIndex] of newIndexMap) {
|
|
529
|
-
if (oldIndexMap.has(key)) {
|
|
530
|
-
const oldIndex = oldIndexMap.get(key);
|
|
531
|
-
if (oldIndex !== newIndex) {
|
|
532
|
-
moves.push({
|
|
533
|
-
type: "MOVE" /* MOVE */,
|
|
534
|
-
key,
|
|
535
|
-
oldIndex,
|
|
536
|
-
newIndex,
|
|
537
|
-
value: newArray[newIndex]
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
return moves;
|
|
543
|
-
};
|
|
544
521
|
var convertArrayToObj = (arr, uniqKey) => {
|
|
545
522
|
let obj = {};
|
|
546
523
|
if (uniqKey === "$value") {
|
|
547
524
|
arr.forEach((value) => {
|
|
548
525
|
obj[value] = value;
|
|
549
526
|
});
|
|
550
|
-
} else if (uniqKey
|
|
551
|
-
const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey;
|
|
552
|
-
obj = keyBy(arr, keyFunction);
|
|
553
|
-
} else {
|
|
527
|
+
} else if (uniqKey === "$index") {
|
|
554
528
|
for (let i = 0; i < arr.length; i++) {
|
|
555
529
|
const value = arr[i];
|
|
556
530
|
obj[i] = value;
|
|
557
531
|
}
|
|
532
|
+
} else {
|
|
533
|
+
const keyFunction = typeof uniqKey === "string" ? (item) => item[uniqKey] : uniqKey;
|
|
534
|
+
obj = keyBy(arr, keyFunction);
|
|
558
535
|
}
|
|
559
536
|
return obj;
|
|
560
537
|
};
|
|
@@ -578,10 +555,10 @@ var removeKey = (obj, key, embeddedKey) => {
|
|
|
578
555
|
}
|
|
579
556
|
const index = indexOfItemInArray(obj, embeddedKey, key);
|
|
580
557
|
if (index === -1) {
|
|
581
|
-
console.warn(`Element with the key '${embeddedKey}' and value '${key}' could not be found in the array
|
|
558
|
+
console.warn(`Element with the key '${embeddedKey}' and value '${key}' could not be found in the array!`);
|
|
582
559
|
return;
|
|
583
560
|
}
|
|
584
|
-
return obj.splice(index
|
|
561
|
+
return obj.splice(index ?? key, 1);
|
|
585
562
|
} else {
|
|
586
563
|
delete obj[key];
|
|
587
564
|
return;
|
|
@@ -600,29 +577,6 @@ var indexOfItemInArray = (arr, key, value) => {
|
|
|
600
577
|
return -1;
|
|
601
578
|
};
|
|
602
579
|
var modifyKeyValue = (obj, key, value) => obj[key] = value;
|
|
603
|
-
var moveArrayElement = (arr, key, oldIndex, newIndex, embeddedKey) => {
|
|
604
|
-
if (!Array.isArray(arr)) {
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
let elementIndex = -1;
|
|
608
|
-
if (embeddedKey === "$index") {
|
|
609
|
-
elementIndex = oldIndex;
|
|
610
|
-
} else if (embeddedKey === "$value") {
|
|
611
|
-
elementIndex = arr.indexOf(key);
|
|
612
|
-
} else {
|
|
613
|
-
elementIndex = arr.findIndex((item) => {
|
|
614
|
-
const keyFunction = typeof embeddedKey === "string" ? (item2) => item2[embeddedKey] : embeddedKey;
|
|
615
|
-
return keyFunction(item)?.toString() === key.toString();
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
if (elementIndex === -1) {
|
|
619
|
-
console.warn(`Element with key '${key}' not found for MOVE operation`);
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
const element = arr.splice(elementIndex, 1)[0];
|
|
623
|
-
arr.splice(newIndex, 0, element);
|
|
624
|
-
return arr;
|
|
625
|
-
};
|
|
626
580
|
var addKeyValue = (obj, key, value, embeddedKey) => {
|
|
627
581
|
if (Array.isArray(obj)) {
|
|
628
582
|
if (embeddedKey === "$index") {
|
|
@@ -635,7 +589,7 @@ var addKeyValue = (obj, key, value, embeddedKey) => {
|
|
|
635
589
|
}
|
|
636
590
|
};
|
|
637
591
|
var applyLeafChange = (obj, change, embeddedKey) => {
|
|
638
|
-
const { type, key, value
|
|
592
|
+
const { type, key, value } = change;
|
|
639
593
|
switch (type) {
|
|
640
594
|
case "ADD" /* ADD */:
|
|
641
595
|
return addKeyValue(obj, key, value, embeddedKey);
|
|
@@ -643,8 +597,6 @@ var applyLeafChange = (obj, change, embeddedKey) => {
|
|
|
643
597
|
return modifyKeyValue(obj, key, value);
|
|
644
598
|
case "REMOVE" /* REMOVE */:
|
|
645
599
|
return removeKey(obj, key, embeddedKey);
|
|
646
|
-
case "MOVE" /* MOVE */:
|
|
647
|
-
return moveArrayElement(obj, key, oldIndex, newIndex, embeddedKey);
|
|
648
600
|
}
|
|
649
601
|
};
|
|
650
602
|
var applyArrayChange = (arr, change) => {
|
|
@@ -660,7 +612,7 @@ var applyArrayChange = (arr, change) => {
|
|
|
660
612
|
});
|
|
661
613
|
}
|
|
662
614
|
for (const subchange of changes) {
|
|
663
|
-
if (subchange.value !== null && subchange.value !== void 0 || subchange.type === "REMOVE" /* REMOVE */ || subchange.value === null && subchange.type === "ADD" /* ADD */ || subchange.
|
|
615
|
+
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 */) {
|
|
664
616
|
applyLeafChange(arr, subchange, change.embeddedKey);
|
|
665
617
|
} else {
|
|
666
618
|
let element;
|
|
@@ -689,7 +641,7 @@ var applyBranchChange = (obj, change) => {
|
|
|
689
641
|
}
|
|
690
642
|
};
|
|
691
643
|
var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
692
|
-
const { type, key, value, oldValue
|
|
644
|
+
const { type, key, value, oldValue } = change;
|
|
693
645
|
if (key === "$root") {
|
|
694
646
|
switch (type) {
|
|
695
647
|
case "ADD" /* ADD */:
|
|
@@ -723,13 +675,11 @@ var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
|
723
675
|
return modifyKeyValue(obj, key, oldValue);
|
|
724
676
|
case "REMOVE" /* REMOVE */:
|
|
725
677
|
return addKeyValue(obj, key, value);
|
|
726
|
-
case "MOVE" /* MOVE */:
|
|
727
|
-
return moveArrayElement(obj, key, newIndex, oldIndex, embeddedKey);
|
|
728
678
|
}
|
|
729
679
|
};
|
|
730
680
|
var revertArrayChange = (arr, change) => {
|
|
731
681
|
for (const subchange of change.changes) {
|
|
732
|
-
if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */
|
|
682
|
+
if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */) {
|
|
733
683
|
revertLeafChange(arr, subchange, change.embeddedKey);
|
|
734
684
|
} else {
|
|
735
685
|
let element;
|
|
@@ -796,18 +746,35 @@ var enrich = (object) => {
|
|
|
796
746
|
return createValue(object);
|
|
797
747
|
}
|
|
798
748
|
};
|
|
749
|
+
var buildEnrichedPath = (atomicPath) => {
|
|
750
|
+
const segments = splitJSONPath(atomicPath);
|
|
751
|
+
let result = "value";
|
|
752
|
+
for (let i = 1; i < segments.length; i++) {
|
|
753
|
+
const seg = segments[i];
|
|
754
|
+
const isLast = i === segments.length - 1;
|
|
755
|
+
const arrayMatch = /^(.+?)\[(\d+)\]$/.exec(seg);
|
|
756
|
+
if (arrayMatch) {
|
|
757
|
+
const [, key, index] = arrayMatch;
|
|
758
|
+
result += `.${key}.value[${index}]`;
|
|
759
|
+
} else {
|
|
760
|
+
result += `.${seg}`;
|
|
761
|
+
}
|
|
762
|
+
if (!isLast) {
|
|
763
|
+
result += ".value";
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return result;
|
|
767
|
+
};
|
|
799
768
|
var applyChangelist = (object, changelist) => {
|
|
800
|
-
changelist.
|
|
801
|
-
|
|
802
|
-
path: entry.path.replace(/(\[(?<array>\d)\]\.)/g, "ARRVAL_START$<array>ARRVAL_END")
|
|
803
|
-
})).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) => {
|
|
769
|
+
changelist.forEach((entry) => {
|
|
770
|
+
const path = buildEnrichedPath(entry.path);
|
|
804
771
|
switch (entry.type) {
|
|
805
772
|
case "ADD" /* ADD */:
|
|
806
773
|
case "UPDATE" /* UPDATE */:
|
|
807
|
-
setByPath(object,
|
|
774
|
+
setByPath(object, path, { type: entry.type, value: entry.value, oldValue: entry.oldValue });
|
|
808
775
|
break;
|
|
809
776
|
case "REMOVE" /* REMOVE */:
|
|
810
|
-
setByPath(object,
|
|
777
|
+
setByPath(object, path, { type: entry.type, value: void 0, oldValue: entry.value });
|
|
811
778
|
break;
|
|
812
779
|
default:
|
|
813
780
|
throw new Error();
|
|
@@ -815,23 +782,957 @@ var applyChangelist = (object, changelist) => {
|
|
|
815
782
|
});
|
|
816
783
|
return object;
|
|
817
784
|
};
|
|
785
|
+
var ARRAY_WRAPPER_KEY = "_$arr";
|
|
818
786
|
var compare2 = (oldObject, newObject) => {
|
|
787
|
+
if (Array.isArray(oldObject) || Array.isArray(newObject)) {
|
|
788
|
+
const wrappedOld = { [ARRAY_WRAPPER_KEY]: oldObject };
|
|
789
|
+
const wrappedNew = { [ARRAY_WRAPPER_KEY]: newObject };
|
|
790
|
+
const enriched = enrich(wrappedOld);
|
|
791
|
+
const changes = atomizeChangeset(diff(wrappedOld, wrappedNew));
|
|
792
|
+
const result = applyChangelist(enriched, changes);
|
|
793
|
+
return result.value[ARRAY_WRAPPER_KEY];
|
|
794
|
+
}
|
|
819
795
|
return applyChangelist(enrich(oldObject), atomizeChangeset(diff(oldObject, newObject)));
|
|
820
796
|
};
|
|
797
|
+
var comparisonToDict = (node) => {
|
|
798
|
+
const result = { type: node.type };
|
|
799
|
+
if (node.type === "CONTAINER" /* CONTAINER */) {
|
|
800
|
+
if (Array.isArray(node.value)) {
|
|
801
|
+
const children = node.value;
|
|
802
|
+
const serialized = new Array(children.length);
|
|
803
|
+
for (let i = 0; i < children.length; i++) {
|
|
804
|
+
const child = children[i];
|
|
805
|
+
serialized[i] = child != null ? comparisonToDict(child) : null;
|
|
806
|
+
}
|
|
807
|
+
result.value = serialized;
|
|
808
|
+
} else if (node.value && typeof node.value === "object") {
|
|
809
|
+
const obj = /* @__PURE__ */ Object.create(null);
|
|
810
|
+
for (const [key, child] of Object.entries(
|
|
811
|
+
node.value
|
|
812
|
+
)) {
|
|
813
|
+
if (child == null) continue;
|
|
814
|
+
obj[key] = comparisonToDict(child);
|
|
815
|
+
}
|
|
816
|
+
result.value = obj;
|
|
817
|
+
}
|
|
818
|
+
} else {
|
|
819
|
+
if (node.type === "UNCHANGED" /* UNCHANGED */ || node.type === "ADD" /* ADD */ || node.type === "UPDATE" /* UPDATE */) {
|
|
820
|
+
result.value = node.value;
|
|
821
|
+
}
|
|
822
|
+
if (node.type === "REMOVE" /* REMOVE */ || node.type === "UPDATE" /* UPDATE */) {
|
|
823
|
+
result.oldValue = node.oldValue;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return result;
|
|
827
|
+
};
|
|
828
|
+
var IDENT_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
829
|
+
var comparisonToFlatList = (node, options = {}) => {
|
|
830
|
+
const results = [];
|
|
831
|
+
flattenNode(node, "$", options.includeUnchanged ?? false, results);
|
|
832
|
+
return results;
|
|
833
|
+
};
|
|
834
|
+
function flattenNode(node, path, includeUnchanged, results) {
|
|
835
|
+
if (node.type === "CONTAINER" /* CONTAINER */) {
|
|
836
|
+
if (Array.isArray(node.value)) {
|
|
837
|
+
for (let i = 0; i < node.value.length; i++) {
|
|
838
|
+
const child = node.value[i];
|
|
839
|
+
if (child == null) continue;
|
|
840
|
+
flattenNode(child, `${path}[${i}]`, includeUnchanged, results);
|
|
841
|
+
}
|
|
842
|
+
} else if (node.value && typeof node.value === "object") {
|
|
843
|
+
for (const [key, child] of Object.entries(
|
|
844
|
+
node.value
|
|
845
|
+
)) {
|
|
846
|
+
if (child == null) continue;
|
|
847
|
+
const childPath = IDENT_RE.test(key) ? `${path}.${key}` : `${path}['${key.replace(/'/g, "''")}']`;
|
|
848
|
+
flattenNode(child, childPath, includeUnchanged, results);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (node.type === "UNCHANGED" /* UNCHANGED */ && !includeUnchanged) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const entry = { path, type: node.type };
|
|
857
|
+
if (node.type === "UNCHANGED" /* UNCHANGED */ || node.type === "ADD" /* ADD */ || node.type === "UPDATE" /* UPDATE */) {
|
|
858
|
+
entry.value = node.value;
|
|
859
|
+
}
|
|
860
|
+
if (node.type === "REMOVE" /* REMOVE */ || node.type === "UPDATE" /* UPDATE */) {
|
|
861
|
+
entry.oldValue = node.oldValue;
|
|
862
|
+
}
|
|
863
|
+
results.push(entry);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/deltaPath.ts
|
|
867
|
+
function formatFilterLiteral(value) {
|
|
868
|
+
if (value === null) return "null";
|
|
869
|
+
if (typeof value === "boolean") return String(value);
|
|
870
|
+
if (typeof value === "number") {
|
|
871
|
+
if (!Number.isFinite(value)) throw new Error(`Cannot format non-finite number as filter literal: ${value}`);
|
|
872
|
+
return String(value);
|
|
873
|
+
}
|
|
874
|
+
if (typeof value === "string") return `'${value.replace(/'/g, "''")}'`;
|
|
875
|
+
throw new Error(`Cannot format filter literal for type ${typeof value}`);
|
|
876
|
+
}
|
|
877
|
+
function parseFilterLiteral(s) {
|
|
878
|
+
if (s === "true") return true;
|
|
879
|
+
if (s === "false") return false;
|
|
880
|
+
if (s === "null") return null;
|
|
881
|
+
if (s.startsWith("'") && s.endsWith("'")) {
|
|
882
|
+
return s.slice(1, -1).replace(/''/g, "'");
|
|
883
|
+
}
|
|
884
|
+
if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(s)) {
|
|
885
|
+
return Number(s);
|
|
886
|
+
}
|
|
887
|
+
throw new Error(`Invalid filter literal: ${s}`);
|
|
888
|
+
}
|
|
889
|
+
function extractQuotedString(s, start) {
|
|
890
|
+
const result = [];
|
|
891
|
+
let i = start;
|
|
892
|
+
while (i < s.length) {
|
|
893
|
+
if (s[i] === "'") {
|
|
894
|
+
if (i + 1 < s.length && s[i + 1] === "'") {
|
|
895
|
+
result.push("'");
|
|
896
|
+
i += 2;
|
|
897
|
+
} else {
|
|
898
|
+
return [result.join(""), i];
|
|
899
|
+
}
|
|
900
|
+
} else {
|
|
901
|
+
result.push(s[i]);
|
|
902
|
+
i += 1;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
throw new Error("Unterminated quoted string");
|
|
906
|
+
}
|
|
907
|
+
function findFilterClose(s, from) {
|
|
908
|
+
let i = from;
|
|
909
|
+
while (i < s.length) {
|
|
910
|
+
if (s[i] === "'") {
|
|
911
|
+
i += 1;
|
|
912
|
+
while (i < s.length) {
|
|
913
|
+
if (s[i] === "'") {
|
|
914
|
+
if (i + 1 < s.length && s[i + 1] === "'") {
|
|
915
|
+
i += 2;
|
|
916
|
+
} else {
|
|
917
|
+
i += 1;
|
|
918
|
+
break;
|
|
919
|
+
}
|
|
920
|
+
} else {
|
|
921
|
+
i += 1;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
} else if (s[i] === ")" && i + 1 < s.length && s[i + 1] === "]") {
|
|
925
|
+
return i;
|
|
926
|
+
} else {
|
|
927
|
+
i += 1;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return -1;
|
|
931
|
+
}
|
|
932
|
+
function parseFilter(inner) {
|
|
933
|
+
if (inner.startsWith("@.")) {
|
|
934
|
+
const eq = inner.indexOf("==");
|
|
935
|
+
if (eq === -1) throw new Error(`Invalid filter: missing '==' in ${inner}`);
|
|
936
|
+
const key = inner.slice(2, eq);
|
|
937
|
+
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(eq + 2)) };
|
|
938
|
+
}
|
|
939
|
+
if (inner.startsWith("@['")) {
|
|
940
|
+
const [key, endIdx] = extractQuotedString(inner, 3);
|
|
941
|
+
const valStart = endIdx + 4;
|
|
942
|
+
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(valStart)) };
|
|
943
|
+
}
|
|
944
|
+
if (inner.startsWith("@==")) {
|
|
945
|
+
return { type: "valueFilter", value: parseFilterLiteral(inner.slice(3)) };
|
|
946
|
+
}
|
|
947
|
+
throw new Error(`Invalid filter expression: ${inner}`);
|
|
948
|
+
}
|
|
949
|
+
function parseDeltaPath(path) {
|
|
950
|
+
if (!path.startsWith("$")) {
|
|
951
|
+
throw new Error(`Path must start with '$': ${path}`);
|
|
952
|
+
}
|
|
953
|
+
const segments = [{ type: "root" }];
|
|
954
|
+
let i = 1;
|
|
955
|
+
while (i < path.length) {
|
|
956
|
+
if (path[i] === ".") {
|
|
957
|
+
i += 1;
|
|
958
|
+
const start = i;
|
|
959
|
+
while (i < path.length && /[a-zA-Z0-9_]/.test(path[i])) {
|
|
960
|
+
i += 1;
|
|
961
|
+
}
|
|
962
|
+
if (i === start) throw new Error(`Empty property name at position ${i} in: ${path}`);
|
|
963
|
+
segments.push({ type: "property", name: path.slice(start, i) });
|
|
964
|
+
} else if (path[i] === "[") {
|
|
965
|
+
if (i + 1 >= path.length) throw new Error(`Unexpected end of path after '[': ${path}`);
|
|
966
|
+
if (path[i + 1] === "?") {
|
|
967
|
+
const closingIdx = findFilterClose(path, i + 2);
|
|
968
|
+
if (closingIdx === -1) throw new Error(`Unterminated filter expression in: ${path}`);
|
|
969
|
+
const inner = path.slice(i + 3, closingIdx);
|
|
970
|
+
segments.push(parseFilter(inner));
|
|
971
|
+
i = closingIdx + 2;
|
|
972
|
+
} else if (path[i + 1] === "'") {
|
|
973
|
+
const [key, endIdx] = extractQuotedString(path, i + 2);
|
|
974
|
+
if (path[endIdx + 1] !== "]") throw new Error(`Expected ']' after bracket property in: ${path}`);
|
|
975
|
+
segments.push({ type: "property", name: key });
|
|
976
|
+
i = endIdx + 2;
|
|
977
|
+
} else if (/\d/.test(path[i + 1])) {
|
|
978
|
+
const end = path.indexOf("]", i);
|
|
979
|
+
if (end === -1) throw new Error(`Unterminated array index in: ${path}`);
|
|
980
|
+
const indexStr = path.slice(i + 1, end);
|
|
981
|
+
if (indexStr.length > 1 && indexStr[0] === "0") {
|
|
982
|
+
throw new Error(`Leading zeros not allowed in array index: [${indexStr}]`);
|
|
983
|
+
}
|
|
984
|
+
segments.push({ type: "index", index: Number(indexStr) });
|
|
985
|
+
i = end + 1;
|
|
986
|
+
} else {
|
|
987
|
+
throw new Error(`Unexpected character after '[': '${path[i + 1]}' in: ${path}`);
|
|
988
|
+
}
|
|
989
|
+
} else {
|
|
990
|
+
throw new Error(`Unexpected character '${path[i]}' at position ${i} in: ${path}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return segments;
|
|
994
|
+
}
|
|
995
|
+
var SIMPLE_PROPERTY_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
996
|
+
function formatMemberAccess(name) {
|
|
997
|
+
if (SIMPLE_PROPERTY_RE.test(name)) {
|
|
998
|
+
return `.${name}`;
|
|
999
|
+
}
|
|
1000
|
+
return `['${name.replace(/'/g, "''")}']`;
|
|
1001
|
+
}
|
|
1002
|
+
function buildDeltaPath(segments) {
|
|
1003
|
+
let result = "";
|
|
1004
|
+
for (const seg of segments) {
|
|
1005
|
+
switch (seg.type) {
|
|
1006
|
+
case "root":
|
|
1007
|
+
result += "$";
|
|
1008
|
+
break;
|
|
1009
|
+
case "property":
|
|
1010
|
+
result += formatMemberAccess(seg.name);
|
|
1011
|
+
break;
|
|
1012
|
+
case "index":
|
|
1013
|
+
result += `[${seg.index}]`;
|
|
1014
|
+
break;
|
|
1015
|
+
case "keyFilter": {
|
|
1016
|
+
const memberAccess = SIMPLE_PROPERTY_RE.test(seg.property) ? `.${seg.property}` : `['${seg.property.replace(/'/g, "''")}']`;
|
|
1017
|
+
result += `[?(@${memberAccess}==${formatFilterLiteral(seg.value)})]`;
|
|
1018
|
+
break;
|
|
1019
|
+
}
|
|
1020
|
+
case "valueFilter":
|
|
1021
|
+
result += `[?(@==${formatFilterLiteral(seg.value)})]`;
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return result;
|
|
1026
|
+
}
|
|
1027
|
+
function atomicPathToDeltaPath(atomicPath) {
|
|
1028
|
+
if (atomicPath === "$.$root") return "$";
|
|
1029
|
+
if (atomicPath.startsWith("$.$root.")) return "$" + atomicPath.slice(7);
|
|
1030
|
+
if (!atomicPath.startsWith("$")) {
|
|
1031
|
+
throw new Error(`Atomic path must start with '$': ${atomicPath}`);
|
|
1032
|
+
}
|
|
1033
|
+
let result = "$";
|
|
1034
|
+
let i = 1;
|
|
1035
|
+
while (i < atomicPath.length) {
|
|
1036
|
+
if (atomicPath[i] === ".") {
|
|
1037
|
+
i += 1;
|
|
1038
|
+
const start = i;
|
|
1039
|
+
while (i < atomicPath.length && atomicPath[i] !== "." && atomicPath[i] !== "[") {
|
|
1040
|
+
i += 1;
|
|
1041
|
+
}
|
|
1042
|
+
const name = atomicPath.slice(start, i);
|
|
1043
|
+
result += formatMemberAccess(name);
|
|
1044
|
+
} else if (atomicPath[i] === "[") {
|
|
1045
|
+
if (atomicPath[i + 1] === "?") {
|
|
1046
|
+
const closingIdx = findFilterClose(atomicPath, i + 2);
|
|
1047
|
+
if (closingIdx === -1) throw new Error(`Unterminated filter in: ${atomicPath}`);
|
|
1048
|
+
result += atomicPath.slice(i, closingIdx + 2);
|
|
1049
|
+
i = closingIdx + 2;
|
|
1050
|
+
} else if (atomicPath[i + 1] === "'" || /\d/.test(atomicPath[i + 1])) {
|
|
1051
|
+
const end = atomicPath.indexOf("]", i);
|
|
1052
|
+
if (end === -1) throw new Error(`Unterminated bracket in: ${atomicPath}`);
|
|
1053
|
+
result += atomicPath.slice(i, end + 1);
|
|
1054
|
+
i = end + 1;
|
|
1055
|
+
} else {
|
|
1056
|
+
const end = atomicPath.indexOf("]", i);
|
|
1057
|
+
if (end === -1) throw new Error(`Unterminated bracket in: ${atomicPath}`);
|
|
1058
|
+
const name = atomicPath.slice(i + 1, end);
|
|
1059
|
+
result += `['${name.replace(/'/g, "''")}']`;
|
|
1060
|
+
i = end + 1;
|
|
1061
|
+
}
|
|
1062
|
+
} else {
|
|
1063
|
+
throw new Error(`Unexpected character '${atomicPath[i]}' in atomic path: ${atomicPath}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return result;
|
|
1067
|
+
}
|
|
1068
|
+
function deltaPathToAtomicPath(deltaPath) {
|
|
1069
|
+
if (!deltaPath.startsWith("$")) {
|
|
1070
|
+
throw new Error(`Delta path must start with '$': ${deltaPath}`);
|
|
1071
|
+
}
|
|
1072
|
+
if (deltaPath === "$") {
|
|
1073
|
+
return "$.$root";
|
|
1074
|
+
}
|
|
1075
|
+
let result = "$";
|
|
1076
|
+
let i = 1;
|
|
1077
|
+
while (i < deltaPath.length) {
|
|
1078
|
+
if (deltaPath[i] === ".") {
|
|
1079
|
+
i += 1;
|
|
1080
|
+
const start = i;
|
|
1081
|
+
while (i < deltaPath.length && /[a-zA-Z0-9_]/.test(deltaPath[i])) {
|
|
1082
|
+
i += 1;
|
|
1083
|
+
}
|
|
1084
|
+
result += "." + deltaPath.slice(start, i);
|
|
1085
|
+
} else if (deltaPath[i] === "[") {
|
|
1086
|
+
if (deltaPath[i + 1] === "?") {
|
|
1087
|
+
const closingIdx = findFilterClose(deltaPath, i + 2);
|
|
1088
|
+
if (closingIdx === -1) throw new Error(`Unterminated filter in: ${deltaPath}`);
|
|
1089
|
+
const filterContent = deltaPath.slice(i, closingIdx + 2);
|
|
1090
|
+
result += normalizeFilterToStringLiterals(filterContent);
|
|
1091
|
+
i = closingIdx + 2;
|
|
1092
|
+
} else if (deltaPath[i + 1] === "'") {
|
|
1093
|
+
const [key, endIdx] = extractQuotedString(deltaPath, i + 2);
|
|
1094
|
+
if (deltaPath[endIdx + 1] !== "]") throw new Error(`Expected ']' in: ${deltaPath}`);
|
|
1095
|
+
result += `[${key}]`;
|
|
1096
|
+
i = endIdx + 2;
|
|
1097
|
+
} else if (/\d/.test(deltaPath[i + 1])) {
|
|
1098
|
+
const end = deltaPath.indexOf("]", i);
|
|
1099
|
+
if (end === -1) throw new Error(`Unterminated bracket in: ${deltaPath}`);
|
|
1100
|
+
result += deltaPath.slice(i, end + 1);
|
|
1101
|
+
i = end + 1;
|
|
1102
|
+
} else {
|
|
1103
|
+
throw new Error(`Unexpected character after '[' in: ${deltaPath}`);
|
|
1104
|
+
}
|
|
1105
|
+
} else {
|
|
1106
|
+
throw new Error(`Unexpected character '${deltaPath[i]}' in delta path: ${deltaPath}`);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return result;
|
|
1110
|
+
}
|
|
1111
|
+
function normalizeFilterToStringLiterals(filter) {
|
|
1112
|
+
const eqIdx = filter.indexOf("==");
|
|
1113
|
+
if (eqIdx === -1) return filter;
|
|
1114
|
+
const literalStart = eqIdx + 2;
|
|
1115
|
+
const literalEnd = filter.length - 2;
|
|
1116
|
+
const literal = filter.slice(literalStart, literalEnd);
|
|
1117
|
+
if (literal.startsWith("'") && literal.endsWith("'")) {
|
|
1118
|
+
return filter;
|
|
1119
|
+
}
|
|
1120
|
+
const value = parseFilterLiteral(literal);
|
|
1121
|
+
const stringValue = String(value).replace(/'/g, "''");
|
|
1122
|
+
return filter.slice(0, literalStart) + `'${stringValue}'` + filter.slice(literalEnd);
|
|
1123
|
+
}
|
|
1124
|
+
function extractKeyFromAtomicPath(atomicPath) {
|
|
1125
|
+
if (atomicPath === "$.$root") return "$root";
|
|
1126
|
+
if (atomicPath.endsWith(")]")) {
|
|
1127
|
+
const filterStart = atomicPath.lastIndexOf("[?(");
|
|
1128
|
+
if (filterStart !== -1) {
|
|
1129
|
+
const inner = atomicPath.slice(filterStart + 3, atomicPath.length - 2);
|
|
1130
|
+
if (inner.startsWith("@==")) {
|
|
1131
|
+
const val = parseFilterLiteral(inner.slice(3));
|
|
1132
|
+
return String(val);
|
|
1133
|
+
}
|
|
1134
|
+
const eqIdx = inner.indexOf("==");
|
|
1135
|
+
if (eqIdx !== -1) {
|
|
1136
|
+
const val = parseFilterLiteral(inner.slice(eqIdx + 2));
|
|
1137
|
+
return String(val);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (atomicPath.endsWith("]")) {
|
|
1142
|
+
const bracketStart = atomicPath.lastIndexOf("[");
|
|
1143
|
+
if (bracketStart !== -1) {
|
|
1144
|
+
const inner = atomicPath.slice(bracketStart + 1, atomicPath.length - 1);
|
|
1145
|
+
if (/^\d+$/.test(inner)) return inner;
|
|
1146
|
+
return inner;
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
const lastDot = atomicPath.lastIndexOf(".");
|
|
1150
|
+
if (lastDot > 0) {
|
|
1151
|
+
return atomicPath.slice(lastDot + 1);
|
|
1152
|
+
}
|
|
1153
|
+
return atomicPath;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// src/jsonDelta.ts
|
|
1157
|
+
function validateDelta(delta) {
|
|
1158
|
+
const errors = [];
|
|
1159
|
+
if (typeof delta !== "object" || delta === null) {
|
|
1160
|
+
return { valid: false, errors: ["Delta must be a non-null object"] };
|
|
1161
|
+
}
|
|
1162
|
+
const d = delta;
|
|
1163
|
+
if (d.format !== "json-delta") {
|
|
1164
|
+
errors.push(`Invalid or missing format: expected 'json-delta', got '${d.format}'`);
|
|
1165
|
+
}
|
|
1166
|
+
if (typeof d.version !== "number") {
|
|
1167
|
+
errors.push(`Missing or invalid version: expected number, got '${typeof d.version}'`);
|
|
1168
|
+
}
|
|
1169
|
+
if (!Array.isArray(d.operations)) {
|
|
1170
|
+
errors.push("Missing or invalid operations: expected array");
|
|
1171
|
+
} else {
|
|
1172
|
+
for (let i = 0; i < d.operations.length; i++) {
|
|
1173
|
+
const op = d.operations[i];
|
|
1174
|
+
if (!op || typeof op !== "object") {
|
|
1175
|
+
errors.push(`operations[${i}]: must be an object`);
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
if (!["add", "remove", "replace"].includes(op.op)) {
|
|
1179
|
+
errors.push(`operations[${i}]: invalid op '${op.op}'`);
|
|
1180
|
+
}
|
|
1181
|
+
if (typeof op.path !== "string") {
|
|
1182
|
+
errors.push(`operations[${i}]: path must be a string`);
|
|
1183
|
+
}
|
|
1184
|
+
if (op.op === "add") {
|
|
1185
|
+
if (!("value" in op)) {
|
|
1186
|
+
errors.push(`operations[${i}]: add operation must have value`);
|
|
1187
|
+
}
|
|
1188
|
+
if ("oldValue" in op) {
|
|
1189
|
+
errors.push(`operations[${i}]: add operation must not have oldValue`);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (op.op === "remove" && "value" in op) {
|
|
1193
|
+
errors.push(`operations[${i}]: remove operation must not have value`);
|
|
1194
|
+
}
|
|
1195
|
+
if (op.op === "replace" && !("value" in op)) {
|
|
1196
|
+
errors.push(`operations[${i}]: replace operation must have value`);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return { valid: errors.length === 0, errors };
|
|
1201
|
+
}
|
|
1202
|
+
function diffDelta(oldObj, newObj, options = {}) {
|
|
1203
|
+
const changeset = diff(oldObj, newObj, {
|
|
1204
|
+
...options,
|
|
1205
|
+
treatTypeChangeAsReplace: true
|
|
1206
|
+
// Always true — merging REMOVE+ADD is more reliable (B.1)
|
|
1207
|
+
});
|
|
1208
|
+
const operations = [];
|
|
1209
|
+
walkChanges(changeset, "$", oldObj, newObj, operations, options);
|
|
1210
|
+
return {
|
|
1211
|
+
format: "json-delta",
|
|
1212
|
+
version: 1,
|
|
1213
|
+
operations
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
function mergeTypeChangePairs(changes) {
|
|
1217
|
+
const result = [];
|
|
1218
|
+
let i = 0;
|
|
1219
|
+
while (i < changes.length) {
|
|
1220
|
+
if (i + 1 < changes.length && changes[i].type === "REMOVE" /* REMOVE */ && changes[i + 1].type === "ADD" /* ADD */ && changes[i].key === changes[i + 1].key) {
|
|
1221
|
+
result.push({
|
|
1222
|
+
...changes[i],
|
|
1223
|
+
isMergedReplace: true,
|
|
1224
|
+
removeValue: changes[i].value,
|
|
1225
|
+
addValue: changes[i + 1].value
|
|
1226
|
+
});
|
|
1227
|
+
i += 2;
|
|
1228
|
+
} else {
|
|
1229
|
+
result.push(changes[i]);
|
|
1230
|
+
i += 1;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return result;
|
|
1234
|
+
}
|
|
1235
|
+
var SIMPLE_PROPERTY_RE2 = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
1236
|
+
function appendCanonicalProperty(basePath, name) {
|
|
1237
|
+
if (SIMPLE_PROPERTY_RE2.test(name)) {
|
|
1238
|
+
return `${basePath}.${name}`;
|
|
1239
|
+
}
|
|
1240
|
+
return `${basePath}['${name.replace(/'/g, "''")}']`;
|
|
1241
|
+
}
|
|
1242
|
+
function walkChanges(changes, basePath, oldCtx, newCtx, ops, options) {
|
|
1243
|
+
const merged = mergeTypeChangePairs(changes);
|
|
1244
|
+
for (const change of merged) {
|
|
1245
|
+
if (change.isMergedReplace) {
|
|
1246
|
+
const mc = change;
|
|
1247
|
+
const path = mc.key === "$root" ? "$" : appendCanonicalProperty(basePath, mc.key);
|
|
1248
|
+
const op = { op: "replace", path, value: mc.addValue };
|
|
1249
|
+
if (options.reversible !== false) {
|
|
1250
|
+
op.oldValue = mc.removeValue;
|
|
1251
|
+
}
|
|
1252
|
+
ops.push(op);
|
|
1253
|
+
} else if (change.changes) {
|
|
1254
|
+
const childPath = change.key === "$root" ? "$" : appendCanonicalProperty(basePath, change.key);
|
|
1255
|
+
const childOld = change.key === "$root" ? oldCtx : oldCtx?.[change.key];
|
|
1256
|
+
const childNew = change.key === "$root" ? newCtx : newCtx?.[change.key];
|
|
1257
|
+
if (change.embeddedKey) {
|
|
1258
|
+
for (const childChange of change.changes) {
|
|
1259
|
+
const filterPath = buildCanonicalFilterPath(
|
|
1260
|
+
childPath,
|
|
1261
|
+
change.embeddedKey,
|
|
1262
|
+
childChange.key,
|
|
1263
|
+
childOld,
|
|
1264
|
+
childNew,
|
|
1265
|
+
childChange
|
|
1266
|
+
);
|
|
1267
|
+
if (childChange.changes) {
|
|
1268
|
+
const oldEl = findElement(childOld, change.embeddedKey, childChange.key);
|
|
1269
|
+
const newEl = findElement(childNew, change.embeddedKey, childChange.key);
|
|
1270
|
+
walkChanges(childChange.changes, filterPath, oldEl, newEl, ops, options);
|
|
1271
|
+
} else {
|
|
1272
|
+
emitLeafOp(childChange, filterPath, ops, options);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
} else {
|
|
1276
|
+
walkChanges(change.changes, childPath, childOld, childNew, ops, options);
|
|
1277
|
+
}
|
|
1278
|
+
} else {
|
|
1279
|
+
const path = change.key === "$root" ? "$" : appendCanonicalProperty(basePath, change.key);
|
|
1280
|
+
emitLeafOp(change, path, ops, options);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
function emitLeafOp(change, path, ops, options) {
|
|
1285
|
+
switch (change.type) {
|
|
1286
|
+
case "ADD" /* ADD */: {
|
|
1287
|
+
ops.push({ op: "add", path, value: change.value });
|
|
1288
|
+
break;
|
|
1289
|
+
}
|
|
1290
|
+
case "REMOVE" /* REMOVE */: {
|
|
1291
|
+
const op = { op: "remove", path };
|
|
1292
|
+
if (options.reversible !== false) {
|
|
1293
|
+
op.oldValue = change.value;
|
|
1294
|
+
}
|
|
1295
|
+
ops.push(op);
|
|
1296
|
+
break;
|
|
1297
|
+
}
|
|
1298
|
+
case "UPDATE" /* UPDATE */: {
|
|
1299
|
+
const op = { op: "replace", path, value: change.value };
|
|
1300
|
+
if (options.reversible !== false) {
|
|
1301
|
+
op.oldValue = change.oldValue;
|
|
1302
|
+
}
|
|
1303
|
+
ops.push(op);
|
|
1304
|
+
break;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
function buildCanonicalFilterPath(basePath, embeddedKey, changeKey, oldArr, newArr, change) {
|
|
1309
|
+
if (embeddedKey === "$index") {
|
|
1310
|
+
return `${basePath}[${changeKey}]`;
|
|
1311
|
+
}
|
|
1312
|
+
if (embeddedKey === "$value") {
|
|
1313
|
+
const typedVal2 = findActualValue(oldArr, newArr, changeKey, change.type);
|
|
1314
|
+
return `${basePath}[?(@==${formatFilterLiteral(typedVal2)})]`;
|
|
1315
|
+
}
|
|
1316
|
+
if (typeof embeddedKey === "function") {
|
|
1317
|
+
const sample = oldArr && oldArr.length > 0 ? oldArr[0] : newArr?.[0];
|
|
1318
|
+
const keyName = sample ? embeddedKey(sample, true) : changeKey;
|
|
1319
|
+
const element2 = findElementByFn(oldArr, newArr, embeddedKey, changeKey, change.type);
|
|
1320
|
+
if (element2 && typeof keyName === "string") {
|
|
1321
|
+
const typedVal2 = element2[keyName];
|
|
1322
|
+
const memberAccess3 = SIMPLE_PROPERTY_RE2.test(keyName) ? `.${keyName}` : `['${keyName.replace(/'/g, "''")}']`;
|
|
1323
|
+
return `${basePath}[?(@${memberAccess3}==${formatFilterLiteral(typedVal2)})]`;
|
|
1324
|
+
}
|
|
1325
|
+
const memberAccess2 = typeof keyName === "string" && SIMPLE_PROPERTY_RE2.test(keyName) ? `.${keyName}` : `.${changeKey}`;
|
|
1326
|
+
return `${basePath}[?(@${memberAccess2}=='${changeKey}')]`;
|
|
1327
|
+
}
|
|
1328
|
+
const element = findElementByKey(oldArr, newArr, embeddedKey, changeKey, change.type);
|
|
1329
|
+
const typedVal = element ? element[embeddedKey] : changeKey;
|
|
1330
|
+
const memberAccess = SIMPLE_PROPERTY_RE2.test(embeddedKey) ? `.${embeddedKey}` : `['${embeddedKey.replace(/'/g, "''")}']`;
|
|
1331
|
+
return `${basePath}[?(@${memberAccess}==${formatFilterLiteral(typedVal)})]`;
|
|
1332
|
+
}
|
|
1333
|
+
function findActualValue(oldArr, newArr, stringKey, opType) {
|
|
1334
|
+
if (opType === "REMOVE" /* REMOVE */ && oldArr) {
|
|
1335
|
+
for (const item of oldArr) {
|
|
1336
|
+
if (String(item) === stringKey) return item;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (opType === "ADD" /* ADD */ && newArr) {
|
|
1340
|
+
for (const item of newArr) {
|
|
1341
|
+
if (String(item) === stringKey) return item;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
if (oldArr) {
|
|
1345
|
+
for (const item of oldArr) {
|
|
1346
|
+
if (String(item) === stringKey) return item;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (newArr) {
|
|
1350
|
+
for (const item of newArr) {
|
|
1351
|
+
if (String(item) === stringKey) return item;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
return stringKey;
|
|
1355
|
+
}
|
|
1356
|
+
function findElement(arr, embeddedKey, changeKey) {
|
|
1357
|
+
if (!arr || !Array.isArray(arr)) return void 0;
|
|
1358
|
+
if (embeddedKey === "$index") {
|
|
1359
|
+
return arr[Number(changeKey)];
|
|
1360
|
+
}
|
|
1361
|
+
if (embeddedKey === "$value") {
|
|
1362
|
+
return arr.find((item) => String(item) === changeKey);
|
|
1363
|
+
}
|
|
1364
|
+
if (typeof embeddedKey === "function") {
|
|
1365
|
+
return arr.find((item) => String(embeddedKey(item)) === changeKey);
|
|
1366
|
+
}
|
|
1367
|
+
return arr.find((item) => item && String(item[embeddedKey]) === changeKey);
|
|
1368
|
+
}
|
|
1369
|
+
function findElementByKey(oldArr, newArr, embeddedKey, changeKey, opType) {
|
|
1370
|
+
if (opType === "REMOVE" /* REMOVE */ || opType === "UPDATE" /* UPDATE */) {
|
|
1371
|
+
const el = oldArr?.find((item) => item && String(item[embeddedKey]) === changeKey);
|
|
1372
|
+
if (el) return el;
|
|
1373
|
+
}
|
|
1374
|
+
if (opType === "ADD" /* ADD */ || opType === "UPDATE" /* UPDATE */) {
|
|
1375
|
+
const el = newArr?.find((item) => item && String(item[embeddedKey]) === changeKey);
|
|
1376
|
+
if (el) return el;
|
|
1377
|
+
}
|
|
1378
|
+
return void 0;
|
|
1379
|
+
}
|
|
1380
|
+
function findElementByFn(oldArr, newArr, fn, changeKey, opType) {
|
|
1381
|
+
if (opType === "REMOVE" /* REMOVE */ || opType === "UPDATE" /* UPDATE */) {
|
|
1382
|
+
const el = oldArr?.find((item) => String(fn(item)) === changeKey);
|
|
1383
|
+
if (el) return el;
|
|
1384
|
+
}
|
|
1385
|
+
if (opType === "ADD" /* ADD */ || opType === "UPDATE" /* UPDATE */) {
|
|
1386
|
+
const el = newArr?.find((item) => String(fn(item)) === changeKey);
|
|
1387
|
+
if (el) return el;
|
|
1388
|
+
}
|
|
1389
|
+
return void 0;
|
|
1390
|
+
}
|
|
1391
|
+
function toDelta(changeset, options = {}) {
|
|
1392
|
+
let atoms;
|
|
1393
|
+
if (changeset.length === 0) {
|
|
1394
|
+
return { format: "json-delta", version: 1, operations: [] };
|
|
1395
|
+
}
|
|
1396
|
+
if ("path" in changeset[0]) {
|
|
1397
|
+
atoms = changeset;
|
|
1398
|
+
} else {
|
|
1399
|
+
atoms = atomizeChangeset(changeset);
|
|
1400
|
+
}
|
|
1401
|
+
const rawOps = atoms.map((atom) => {
|
|
1402
|
+
const path = atomicPathToDeltaPath(atom.path);
|
|
1403
|
+
switch (atom.type) {
|
|
1404
|
+
case "ADD" /* ADD */:
|
|
1405
|
+
return { op: "add", path, value: atom.value };
|
|
1406
|
+
case "REMOVE" /* REMOVE */: {
|
|
1407
|
+
const op = { op: "remove", path };
|
|
1408
|
+
if (options.reversible !== false && atom.value !== void 0) {
|
|
1409
|
+
op.oldValue = atom.value;
|
|
1410
|
+
}
|
|
1411
|
+
return op;
|
|
1412
|
+
}
|
|
1413
|
+
case "UPDATE" /* UPDATE */: {
|
|
1414
|
+
const op = { op: "replace", path, value: atom.value };
|
|
1415
|
+
if (options.reversible !== false && atom.oldValue !== void 0) {
|
|
1416
|
+
op.oldValue = atom.oldValue;
|
|
1417
|
+
}
|
|
1418
|
+
return op;
|
|
1419
|
+
}
|
|
1420
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1421
|
+
default:
|
|
1422
|
+
throw new Error(`Unknown operation type: ${atom.type}`);
|
|
1423
|
+
}
|
|
1424
|
+
});
|
|
1425
|
+
const operations = mergeConsecutiveOps(rawOps);
|
|
1426
|
+
return { format: "json-delta", version: 1, operations };
|
|
1427
|
+
}
|
|
1428
|
+
function mergeConsecutiveOps(ops) {
|
|
1429
|
+
const result = [];
|
|
1430
|
+
let i = 0;
|
|
1431
|
+
while (i < ops.length) {
|
|
1432
|
+
if (i + 1 < ops.length && ops[i].op === "remove" && ops[i + 1].op === "add" && ops[i].path === ops[i + 1].path) {
|
|
1433
|
+
const merged = {
|
|
1434
|
+
op: "replace",
|
|
1435
|
+
path: ops[i].path,
|
|
1436
|
+
value: ops[i + 1].value
|
|
1437
|
+
};
|
|
1438
|
+
if (ops[i].oldValue !== void 0) {
|
|
1439
|
+
merged.oldValue = ops[i].oldValue;
|
|
1440
|
+
}
|
|
1441
|
+
result.push(merged);
|
|
1442
|
+
i += 2;
|
|
1443
|
+
} else {
|
|
1444
|
+
result.push(ops[i]);
|
|
1445
|
+
i += 1;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
return result;
|
|
1449
|
+
}
|
|
1450
|
+
function fromDelta(delta) {
|
|
1451
|
+
const validation = validateDelta(delta);
|
|
1452
|
+
if (!validation.valid) {
|
|
1453
|
+
throw new Error(`Invalid delta: ${validation.errors.join(", ")}`);
|
|
1454
|
+
}
|
|
1455
|
+
return delta.operations.map((op) => {
|
|
1456
|
+
const atomicPath = deltaPathToAtomicPath(op.path);
|
|
1457
|
+
const key = extractKeyFromAtomicPath(atomicPath);
|
|
1458
|
+
switch (op.op) {
|
|
1459
|
+
case "add": {
|
|
1460
|
+
const valueType = getValueType(op.value);
|
|
1461
|
+
return { type: "ADD" /* ADD */, key, path: atomicPath, valueType, value: op.value };
|
|
1462
|
+
}
|
|
1463
|
+
case "remove": {
|
|
1464
|
+
const valueType = op.oldValue !== void 0 ? getValueType(op.oldValue) : null;
|
|
1465
|
+
return { type: "REMOVE" /* REMOVE */, key, path: atomicPath, valueType, value: op.oldValue };
|
|
1466
|
+
}
|
|
1467
|
+
case "replace": {
|
|
1468
|
+
const valueType = getValueType(op.value);
|
|
1469
|
+
const atom = { type: "UPDATE" /* UPDATE */, key, path: atomicPath, valueType, value: op.value };
|
|
1470
|
+
if (op.oldValue !== void 0) {
|
|
1471
|
+
atom.oldValue = op.oldValue;
|
|
1472
|
+
}
|
|
1473
|
+
return atom;
|
|
1474
|
+
}
|
|
1475
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1476
|
+
default:
|
|
1477
|
+
throw new Error(`Unknown operation: ${op.op}`);
|
|
1478
|
+
}
|
|
1479
|
+
});
|
|
1480
|
+
}
|
|
1481
|
+
function getValueType(value) {
|
|
1482
|
+
if (value === void 0) return "undefined";
|
|
1483
|
+
if (value === null) return null;
|
|
1484
|
+
if (Array.isArray(value)) return "Array";
|
|
1485
|
+
const type = typeof value;
|
|
1486
|
+
return type.charAt(0).toUpperCase() + type.slice(1);
|
|
1487
|
+
}
|
|
1488
|
+
function invertDelta(delta) {
|
|
1489
|
+
const validation = validateDelta(delta);
|
|
1490
|
+
if (!validation.valid) {
|
|
1491
|
+
throw new Error(`Invalid delta: ${validation.errors.join(", ")}`);
|
|
1492
|
+
}
|
|
1493
|
+
for (let i = 0; i < delta.operations.length; i++) {
|
|
1494
|
+
const op = delta.operations[i];
|
|
1495
|
+
if (op.op === "replace" && !("oldValue" in op)) {
|
|
1496
|
+
throw new Error(`operations[${i}]: replace operation missing oldValue \u2014 delta is not reversible`);
|
|
1497
|
+
}
|
|
1498
|
+
if (op.op === "remove" && !("oldValue" in op)) {
|
|
1499
|
+
throw new Error(`operations[${i}]: remove operation missing oldValue \u2014 delta is not reversible`);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
const invertedOps = [...delta.operations].reverse().map((op) => {
|
|
1503
|
+
const extensions = {};
|
|
1504
|
+
for (const key of Object.keys(op)) {
|
|
1505
|
+
if (!["op", "path", "value", "oldValue"].includes(key)) {
|
|
1506
|
+
extensions[key] = op[key];
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
switch (op.op) {
|
|
1510
|
+
case "add":
|
|
1511
|
+
return { op: "remove", path: op.path, oldValue: op.value, ...extensions };
|
|
1512
|
+
case "remove":
|
|
1513
|
+
return { op: "add", path: op.path, value: op.oldValue, ...extensions };
|
|
1514
|
+
case "replace":
|
|
1515
|
+
return { op: "replace", path: op.path, value: op.oldValue, oldValue: op.value, ...extensions };
|
|
1516
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1517
|
+
default:
|
|
1518
|
+
throw new Error(`Unknown operation: ${op.op}`);
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
const envelope = { format: "json-delta", version: delta.version, operations: invertedOps };
|
|
1522
|
+
for (const key of Object.keys(delta)) {
|
|
1523
|
+
if (!["format", "version", "operations"].includes(key)) {
|
|
1524
|
+
envelope[key] = delta[key];
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return envelope;
|
|
1528
|
+
}
|
|
1529
|
+
function applyDelta(obj, delta) {
|
|
1530
|
+
const validation = validateDelta(delta);
|
|
1531
|
+
if (!validation.valid) {
|
|
1532
|
+
throw new Error(`Invalid delta: ${validation.errors.join(", ")}`);
|
|
1533
|
+
}
|
|
1534
|
+
let result = obj;
|
|
1535
|
+
for (const op of delta.operations) {
|
|
1536
|
+
if (op.path === "$") {
|
|
1537
|
+
result = applyRootOp(result, op);
|
|
1538
|
+
} else {
|
|
1539
|
+
const atomicChange = deltaOpToAtomicChange(op);
|
|
1540
|
+
const miniChangeset = unatomizeChangeset([atomicChange]);
|
|
1541
|
+
applyChangeset(result, miniChangeset);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return result;
|
|
1545
|
+
}
|
|
1546
|
+
function applyRootOp(obj, op) {
|
|
1547
|
+
switch (op.op) {
|
|
1548
|
+
case "add":
|
|
1549
|
+
return op.value;
|
|
1550
|
+
case "remove":
|
|
1551
|
+
return null;
|
|
1552
|
+
case "replace": {
|
|
1553
|
+
if (typeof obj === "object" && obj !== null && !Array.isArray(obj) && typeof op.value === "object" && op.value !== null && !Array.isArray(op.value)) {
|
|
1554
|
+
for (const key of Object.keys(obj)) {
|
|
1555
|
+
delete obj[key];
|
|
1556
|
+
}
|
|
1557
|
+
Object.assign(obj, op.value);
|
|
1558
|
+
return obj;
|
|
1559
|
+
}
|
|
1560
|
+
return op.value;
|
|
1561
|
+
}
|
|
1562
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1563
|
+
default:
|
|
1564
|
+
throw new Error(`Unknown operation: ${op.op}`);
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
function deltaOpToAtomicChange(op) {
|
|
1568
|
+
const atomicPath = deltaPathToAtomicPath(op.path);
|
|
1569
|
+
const key = extractKeyFromAtomicPath(atomicPath);
|
|
1570
|
+
switch (op.op) {
|
|
1571
|
+
case "add":
|
|
1572
|
+
return { type: "ADD" /* ADD */, key, path: atomicPath, valueType: getValueType(op.value), value: op.value };
|
|
1573
|
+
case "remove":
|
|
1574
|
+
return { type: "REMOVE" /* REMOVE */, key, path: atomicPath, valueType: getValueType(op.oldValue), value: op.oldValue };
|
|
1575
|
+
case "replace":
|
|
1576
|
+
return {
|
|
1577
|
+
type: "UPDATE" /* UPDATE */,
|
|
1578
|
+
key,
|
|
1579
|
+
path: atomicPath,
|
|
1580
|
+
valueType: getValueType(op.value),
|
|
1581
|
+
value: op.value,
|
|
1582
|
+
oldValue: op.oldValue
|
|
1583
|
+
};
|
|
1584
|
+
/* istanbul ignore next -- exhaustive switch */
|
|
1585
|
+
default:
|
|
1586
|
+
throw new Error(`Unknown operation: ${op.op}`);
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
function revertDelta(obj, delta) {
|
|
1590
|
+
const inverse = invertDelta(delta);
|
|
1591
|
+
return applyDelta(obj, inverse);
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
// src/deltaHelpers.ts
|
|
1595
|
+
var OP_SPEC_KEYS = /* @__PURE__ */ new Set(["op", "path", "value", "oldValue"]);
|
|
1596
|
+
var DELTA_SPEC_KEYS = /* @__PURE__ */ new Set(["format", "version", "operations"]);
|
|
1597
|
+
function operationSpecDict(op) {
|
|
1598
|
+
const result = { op: op.op, path: op.path };
|
|
1599
|
+
if ("value" in op) result.value = op.value;
|
|
1600
|
+
if ("oldValue" in op) result.oldValue = op.oldValue;
|
|
1601
|
+
return result;
|
|
1602
|
+
}
|
|
1603
|
+
function operationExtensions(op) {
|
|
1604
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
1605
|
+
for (const key of Object.keys(op)) {
|
|
1606
|
+
if (!OP_SPEC_KEYS.has(key)) {
|
|
1607
|
+
result[key] = op[key];
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return result;
|
|
1611
|
+
}
|
|
1612
|
+
function leafProperty(op) {
|
|
1613
|
+
const segments = parseDeltaPath(op.path);
|
|
1614
|
+
if (segments.length === 0) return null;
|
|
1615
|
+
const last = segments[segments.length - 1];
|
|
1616
|
+
return last.type === "property" ? last.name : null;
|
|
1617
|
+
}
|
|
1618
|
+
function deltaSpecDict(delta) {
|
|
1619
|
+
return {
|
|
1620
|
+
format: delta.format,
|
|
1621
|
+
version: delta.version,
|
|
1622
|
+
operations: delta.operations.map(operationSpecDict)
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
function deltaExtensions(delta) {
|
|
1626
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
1627
|
+
for (const key of Object.keys(delta)) {
|
|
1628
|
+
if (!DELTA_SPEC_KEYS.has(key)) {
|
|
1629
|
+
result[key] = delta[key];
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
return result;
|
|
1633
|
+
}
|
|
1634
|
+
function deltaMap(delta, fn) {
|
|
1635
|
+
return { ...delta, operations: delta.operations.map((op, i) => fn(op, i)) };
|
|
1636
|
+
}
|
|
1637
|
+
function deltaStamp(delta, extensions) {
|
|
1638
|
+
return deltaMap(delta, (op) => ({ ...op, ...extensions }));
|
|
1639
|
+
}
|
|
1640
|
+
function deltaGroupBy(delta, keyFn) {
|
|
1641
|
+
const groups = /* @__PURE__ */ Object.create(null);
|
|
1642
|
+
for (const op of delta.operations) {
|
|
1643
|
+
const k = keyFn(op);
|
|
1644
|
+
if (!groups[k]) groups[k] = [];
|
|
1645
|
+
groups[k].push(op);
|
|
1646
|
+
}
|
|
1647
|
+
const envelope = /* @__PURE__ */ Object.create(null);
|
|
1648
|
+
for (const key of Object.keys(delta)) {
|
|
1649
|
+
if (key !== "operations") {
|
|
1650
|
+
envelope[key] = delta[key];
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
1654
|
+
for (const [k, ops] of Object.entries(groups)) {
|
|
1655
|
+
result[k] = { ...envelope, operations: ops };
|
|
1656
|
+
}
|
|
1657
|
+
return result;
|
|
1658
|
+
}
|
|
1659
|
+
function deepClone(obj) {
|
|
1660
|
+
return JSON.parse(JSON.stringify(obj));
|
|
1661
|
+
}
|
|
1662
|
+
function squashDeltas(source, deltas, options = {}) {
|
|
1663
|
+
const { target, verifyTarget = true, ...diffOptions } = options;
|
|
1664
|
+
let final;
|
|
1665
|
+
if (target !== void 0 && deltas.length > 0 && verifyTarget) {
|
|
1666
|
+
let computed = deepClone(source);
|
|
1667
|
+
for (const d of deltas) {
|
|
1668
|
+
computed = applyDelta(computed, d);
|
|
1669
|
+
}
|
|
1670
|
+
const verification = diffDelta(computed, target, diffOptions);
|
|
1671
|
+
if (verification.operations.length > 0) {
|
|
1672
|
+
throw new Error(
|
|
1673
|
+
"squashDeltas: provided target does not match sequential application of deltas to source"
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
final = target;
|
|
1677
|
+
} else if (target !== void 0) {
|
|
1678
|
+
final = target;
|
|
1679
|
+
} else {
|
|
1680
|
+
final = deepClone(source);
|
|
1681
|
+
for (const d of deltas) {
|
|
1682
|
+
final = applyDelta(final, d);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
const result = diffDelta(source, final, diffOptions);
|
|
1686
|
+
for (const d of deltas) {
|
|
1687
|
+
for (const key of Object.keys(d)) {
|
|
1688
|
+
if (!DELTA_SPEC_KEYS.has(key)) {
|
|
1689
|
+
Object.defineProperty(result, key, {
|
|
1690
|
+
value: d[key],
|
|
1691
|
+
writable: true,
|
|
1692
|
+
enumerable: true,
|
|
1693
|
+
configurable: true
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
return result;
|
|
1699
|
+
}
|
|
821
1700
|
// Annotate the CommonJS export names for ESM import in node:
|
|
822
1701
|
0 && (module.exports = {
|
|
823
1702
|
CompareOperation,
|
|
824
1703
|
Operation,
|
|
825
1704
|
applyChangelist,
|
|
826
1705
|
applyChangeset,
|
|
1706
|
+
applyDelta,
|
|
827
1707
|
atomizeChangeset,
|
|
1708
|
+
buildDeltaPath,
|
|
828
1709
|
compare,
|
|
1710
|
+
comparisonToDict,
|
|
1711
|
+
comparisonToFlatList,
|
|
829
1712
|
createContainer,
|
|
830
1713
|
createValue,
|
|
1714
|
+
deltaExtensions,
|
|
1715
|
+
deltaGroupBy,
|
|
1716
|
+
deltaMap,
|
|
1717
|
+
deltaSpecDict,
|
|
1718
|
+
deltaStamp,
|
|
831
1719
|
diff,
|
|
1720
|
+
diffDelta,
|
|
832
1721
|
enrich,
|
|
1722
|
+
formatFilterLiteral,
|
|
1723
|
+
fromDelta,
|
|
833
1724
|
getTypeOfObj,
|
|
1725
|
+
invertDelta,
|
|
1726
|
+
leafProperty,
|
|
1727
|
+
operationExtensions,
|
|
1728
|
+
operationSpecDict,
|
|
1729
|
+
parseDeltaPath,
|
|
1730
|
+
parseFilterLiteral,
|
|
834
1731
|
revertChangeset,
|
|
835
|
-
|
|
1732
|
+
revertDelta,
|
|
1733
|
+
squashDeltas,
|
|
1734
|
+
toDelta,
|
|
1735
|
+
unatomizeChangeset,
|
|
1736
|
+
validateDelta
|
|
836
1737
|
});
|
|
837
1738
|
//# sourceMappingURL=index.cjs.map
|