json-diff-ts 5.0.0-alpha.5 → 5.0.0-alpha.7
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/dist/index.cjs +175 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +175 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -167,12 +167,12 @@ var revertChangeset = (obj, changeset) => {
|
|
|
167
167
|
}
|
|
168
168
|
return obj;
|
|
169
169
|
};
|
|
170
|
-
var atomizeChangeset = (obj, path = "$", embeddedKey) => {
|
|
170
|
+
var atomizeChangeset = (obj, path = "$", embeddedKey, embeddedKeyIsPath) => {
|
|
171
171
|
if (Array.isArray(obj)) {
|
|
172
|
-
return handleArray(obj, path, embeddedKey);
|
|
172
|
+
return handleArray(obj, path, embeddedKey, embeddedKeyIsPath);
|
|
173
173
|
} else if (obj.changes || embeddedKey) {
|
|
174
174
|
if (embeddedKey) {
|
|
175
|
-
const [updatedPath, atomicChange] = handleEmbeddedKey(embeddedKey, obj, path);
|
|
175
|
+
const [updatedPath, atomicChange] = handleEmbeddedKey(embeddedKey, obj, path, embeddedKeyIsPath);
|
|
176
176
|
path = updatedPath;
|
|
177
177
|
if (atomicChange) {
|
|
178
178
|
return atomicChange;
|
|
@@ -180,7 +180,7 @@ var atomizeChangeset = (obj, path = "$", embeddedKey) => {
|
|
|
180
180
|
} else {
|
|
181
181
|
path = append(path, obj.key);
|
|
182
182
|
}
|
|
183
|
-
return atomizeChangeset(obj.changes || obj, path, obj.embeddedKey);
|
|
183
|
+
return atomizeChangeset(obj.changes || obj, path, obj.embeddedKey, obj.embeddedKeyIsPath);
|
|
184
184
|
} else {
|
|
185
185
|
const valueType = getTypeOfObj(obj.value);
|
|
186
186
|
let finalPath = path;
|
|
@@ -207,7 +207,7 @@ var atomizeChangeset = (obj, path = "$", embeddedKey) => {
|
|
|
207
207
|
];
|
|
208
208
|
}
|
|
209
209
|
};
|
|
210
|
-
function handleEmbeddedKey(embeddedKey, obj, path) {
|
|
210
|
+
function handleEmbeddedKey(embeddedKey, obj, path, isPath) {
|
|
211
211
|
if (embeddedKey === "$index") {
|
|
212
212
|
path = `${path}[${obj.key}]`;
|
|
213
213
|
return [path];
|
|
@@ -225,12 +225,12 @@ function handleEmbeddedKey(embeddedKey, obj, path) {
|
|
|
225
225
|
]
|
|
226
226
|
];
|
|
227
227
|
} else {
|
|
228
|
-
path = filterExpression(path, embeddedKey, obj.key);
|
|
228
|
+
path = filterExpression(path, embeddedKey, obj.key, isPath);
|
|
229
229
|
return [path];
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
|
-
var handleArray = (obj, path, embeddedKey) => {
|
|
233
|
-
return obj.reduce((memo, change) => [...memo, ...atomizeChangeset(change, path, embeddedKey)], []);
|
|
232
|
+
var handleArray = (obj, path, embeddedKey, embeddedKeyIsPath) => {
|
|
233
|
+
return obj.reduce((memo, change) => [...memo, ...atomizeChangeset(change, path, embeddedKey, embeddedKeyIsPath)], []);
|
|
234
234
|
};
|
|
235
235
|
var unatomizeChangeset = (changes) => {
|
|
236
236
|
if (!Array.isArray(changes)) {
|
|
@@ -250,15 +250,17 @@ var unatomizeChangeset = (changes) => {
|
|
|
250
250
|
} else {
|
|
251
251
|
for (let i = 1; i < segments.length; i++) {
|
|
252
252
|
const segment = segments[i];
|
|
253
|
-
const result = /^([^[\]]+)\[\?\(@(?:\.?([^=[]*)|(?:\['([^']*)'\]))=+'([^']
|
|
253
|
+
const result = /^([^[\]]+)\[\?\(@(?:\.?([^=[]*)|(?:\['([^']*(?:''[^']*)*)'\]))=+'([^']*(?:''[^']*)*)'\)\]$|^(.+)\[(\d+)\]$/.exec(segment);
|
|
254
254
|
if (result) {
|
|
255
255
|
let key;
|
|
256
256
|
let embeddedKey;
|
|
257
257
|
let arrKey;
|
|
258
|
+
let isPath;
|
|
258
259
|
if (result[1]) {
|
|
259
260
|
key = result[1];
|
|
260
|
-
embeddedKey = result[3] || result[2] || "$value";
|
|
261
|
-
|
|
261
|
+
embeddedKey = result[3]?.replace(/''/g, "'") || result[2] || "$value";
|
|
262
|
+
isPath = !result[3] && !!result[2] && result[2].includes(".") && NESTED_PATH_RE.test(result[2]) ? true : void 0;
|
|
263
|
+
arrKey = result[4]?.replace(/''/g, "'");
|
|
262
264
|
} else {
|
|
263
265
|
key = result[5];
|
|
264
266
|
embeddedKey = "$index";
|
|
@@ -267,6 +269,7 @@ var unatomizeChangeset = (changes) => {
|
|
|
267
269
|
if (i === segments.length - 1) {
|
|
268
270
|
ptr.key = key;
|
|
269
271
|
ptr.embeddedKey = embeddedKey;
|
|
272
|
+
if (isPath) ptr.embeddedKeyIsPath = true;
|
|
270
273
|
ptr.type = "UPDATE" /* UPDATE */;
|
|
271
274
|
ptr.changes = [
|
|
272
275
|
{
|
|
@@ -279,6 +282,7 @@ var unatomizeChangeset = (changes) => {
|
|
|
279
282
|
} else {
|
|
280
283
|
ptr.key = key;
|
|
281
284
|
ptr.embeddedKey = embeddedKey;
|
|
285
|
+
if (isPath) ptr.embeddedKeyIsPath = true;
|
|
282
286
|
ptr.type = "UPDATE" /* UPDATE */;
|
|
283
287
|
const newPtr = {};
|
|
284
288
|
ptr.changes = [
|
|
@@ -480,12 +484,15 @@ var compareArray = (oldObj, newObj, path, keyPath, options) => {
|
|
|
480
484
|
const indexedOldObj = convertArrayToObj(oldObj, uniqKey);
|
|
481
485
|
const indexedNewObj = convertArrayToObj(newObj, uniqKey);
|
|
482
486
|
const diffs = compareObject(indexedOldObj, indexedNewObj, path, keyPath, true, options);
|
|
487
|
+
const isFunctionKey = typeof uniqKey === "function" && uniqKey.length === 2;
|
|
483
488
|
if (diffs.length) {
|
|
489
|
+
const resolvedKey = isFunctionKey ? uniqKey(newObj[0] ?? oldObj[0], true) : uniqKey;
|
|
484
490
|
return [
|
|
485
491
|
{
|
|
486
492
|
type: "UPDATE" /* UPDATE */,
|
|
487
493
|
key: getKey(path),
|
|
488
|
-
embeddedKey:
|
|
494
|
+
embeddedKey: resolvedKey,
|
|
495
|
+
...isFunctionKey && typeof resolvedKey === "string" && NESTED_PATH_RE.test(resolvedKey) && resolvedKey.includes(".") ? { embeddedKeyIsPath: true } : {},
|
|
489
496
|
changes: diffs
|
|
490
497
|
}
|
|
491
498
|
];
|
|
@@ -543,13 +550,13 @@ var comparePrimitives = (oldObj, newObj, path) => {
|
|
|
543
550
|
}
|
|
544
551
|
return changes;
|
|
545
552
|
};
|
|
546
|
-
var removeKey = (obj, key, embeddedKey) => {
|
|
553
|
+
var removeKey = (obj, key, embeddedKey, isPath) => {
|
|
547
554
|
if (Array.isArray(obj)) {
|
|
548
555
|
if (embeddedKey === "$index") {
|
|
549
556
|
obj.splice(Number(key), 1);
|
|
550
557
|
return;
|
|
551
558
|
}
|
|
552
|
-
const index = indexOfItemInArray(obj, embeddedKey, key);
|
|
559
|
+
const index = indexOfItemInArray(obj, embeddedKey, key, isPath);
|
|
553
560
|
if (index === -1) {
|
|
554
561
|
console.warn(`Element with the key '${embeddedKey}' and value '${key}' could not be found in the array!`);
|
|
555
562
|
return;
|
|
@@ -560,13 +567,20 @@ var removeKey = (obj, key, embeddedKey) => {
|
|
|
560
567
|
return;
|
|
561
568
|
}
|
|
562
569
|
};
|
|
563
|
-
var
|
|
570
|
+
var resolveProperty = (obj, key, isPath) => {
|
|
571
|
+
if (obj == null) return void 0;
|
|
572
|
+
if (typeof key !== "string" || !isPath || !key.includes(".")) return obj[key];
|
|
573
|
+
return key.split(".").reduce((cur, seg) => cur?.[seg], obj);
|
|
574
|
+
};
|
|
575
|
+
var indexOfItemInArray = (arr, key, value, isPath) => {
|
|
564
576
|
if (key === "$value") {
|
|
565
577
|
return arr.indexOf(value);
|
|
566
578
|
}
|
|
567
579
|
for (let i = 0; i < arr.length; i++) {
|
|
568
580
|
const item = arr[i];
|
|
569
|
-
if (item
|
|
581
|
+
if (item == null) continue;
|
|
582
|
+
const resolved = resolveProperty(item, key, isPath);
|
|
583
|
+
if (resolved != null && String(resolved) === String(value)) {
|
|
570
584
|
return i;
|
|
571
585
|
}
|
|
572
586
|
}
|
|
@@ -584,7 +598,7 @@ var addKeyValue = (obj, key, value, embeddedKey) => {
|
|
|
584
598
|
return obj ? obj[key] = value : null;
|
|
585
599
|
}
|
|
586
600
|
};
|
|
587
|
-
var applyLeafChange = (obj, change, embeddedKey) => {
|
|
601
|
+
var applyLeafChange = (obj, change, embeddedKey, isPath) => {
|
|
588
602
|
const { type, key, value } = change;
|
|
589
603
|
switch (type) {
|
|
590
604
|
case "ADD" /* ADD */:
|
|
@@ -592,7 +606,7 @@ var applyLeafChange = (obj, change, embeddedKey) => {
|
|
|
592
606
|
case "UPDATE" /* UPDATE */:
|
|
593
607
|
return modifyKeyValue(obj, key, value);
|
|
594
608
|
case "REMOVE" /* REMOVE */:
|
|
595
|
-
return removeKey(obj, key, embeddedKey);
|
|
609
|
+
return removeKey(obj, key, embeddedKey, isPath);
|
|
596
610
|
}
|
|
597
611
|
};
|
|
598
612
|
var applyArrayChange = (arr, change) => {
|
|
@@ -609,7 +623,7 @@ var applyArrayChange = (arr, change) => {
|
|
|
609
623
|
}
|
|
610
624
|
for (const subchange of changes) {
|
|
611
625
|
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 */) {
|
|
612
|
-
applyLeafChange(arr, subchange, change.embeddedKey);
|
|
626
|
+
applyLeafChange(arr, subchange, change.embeddedKey, change.embeddedKeyIsPath);
|
|
613
627
|
} else {
|
|
614
628
|
let element;
|
|
615
629
|
if (change.embeddedKey === "$index") {
|
|
@@ -620,7 +634,10 @@ var applyArrayChange = (arr, change) => {
|
|
|
620
634
|
element = arr[index];
|
|
621
635
|
}
|
|
622
636
|
} else {
|
|
623
|
-
element = arr.find((el) =>
|
|
637
|
+
element = arr.find((el) => {
|
|
638
|
+
const resolved = resolveProperty(el, change.embeddedKey, change.embeddedKeyIsPath);
|
|
639
|
+
return resolved != null && String(resolved) === String(subchange.key);
|
|
640
|
+
});
|
|
624
641
|
}
|
|
625
642
|
if (element) {
|
|
626
643
|
applyChangeset(element, subchange.changes);
|
|
@@ -636,7 +653,7 @@ var applyBranchChange = (obj, change) => {
|
|
|
636
653
|
return applyChangeset(obj, change.changes);
|
|
637
654
|
}
|
|
638
655
|
};
|
|
639
|
-
var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
656
|
+
var revertLeafChange = (obj, change, embeddedKey = "$index", isPath) => {
|
|
640
657
|
const { type, key, value, oldValue } = change;
|
|
641
658
|
if (key === "$root") {
|
|
642
659
|
switch (type) {
|
|
@@ -666,7 +683,7 @@ var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
|
666
683
|
}
|
|
667
684
|
switch (type) {
|
|
668
685
|
case "ADD" /* ADD */:
|
|
669
|
-
return removeKey(obj, key, embeddedKey);
|
|
686
|
+
return removeKey(obj, key, embeddedKey, isPath);
|
|
670
687
|
case "UPDATE" /* UPDATE */:
|
|
671
688
|
return modifyKeyValue(obj, key, oldValue);
|
|
672
689
|
case "REMOVE" /* REMOVE */:
|
|
@@ -676,7 +693,7 @@ var revertLeafChange = (obj, change, embeddedKey = "$index") => {
|
|
|
676
693
|
var revertArrayChange = (arr, change) => {
|
|
677
694
|
for (const subchange of change.changes) {
|
|
678
695
|
if (subchange.value != null || subchange.type === "REMOVE" /* REMOVE */) {
|
|
679
|
-
revertLeafChange(arr, subchange, change.embeddedKey);
|
|
696
|
+
revertLeafChange(arr, subchange, change.embeddedKey, change.embeddedKeyIsPath);
|
|
680
697
|
} else {
|
|
681
698
|
let element;
|
|
682
699
|
if (change.embeddedKey === "$index") {
|
|
@@ -687,7 +704,10 @@ var revertArrayChange = (arr, change) => {
|
|
|
687
704
|
element = arr[index];
|
|
688
705
|
}
|
|
689
706
|
} else {
|
|
690
|
-
element = arr.find((el) =>
|
|
707
|
+
element = arr.find((el) => {
|
|
708
|
+
const resolved = resolveProperty(el, change.embeddedKey, change.embeddedKeyIsPath);
|
|
709
|
+
return resolved != null && String(resolved) === String(subchange.key);
|
|
710
|
+
});
|
|
691
711
|
}
|
|
692
712
|
if (element) {
|
|
693
713
|
revertChangeset(element, subchange.changes);
|
|
@@ -707,9 +727,17 @@ function append(basePath, nextSegment) {
|
|
|
707
727
|
return nextSegment.includes(".") ? `${basePath}[${nextSegment}]` : `${basePath}.${nextSegment}`;
|
|
708
728
|
}
|
|
709
729
|
var IDENT_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
710
|
-
|
|
730
|
+
var NESTED_PATH_RE = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
731
|
+
function filterExpression(basePath, filterKey, filterValue, isPath) {
|
|
711
732
|
const escapedValue = `'${filterValue.replace(/'/g, "''")}'`;
|
|
712
|
-
|
|
733
|
+
let memberAccess;
|
|
734
|
+
if (isPath && NESTED_PATH_RE.test(filterKey)) {
|
|
735
|
+
memberAccess = "." + filterKey;
|
|
736
|
+
} else if (IDENT_RE.test(filterKey)) {
|
|
737
|
+
memberAccess = `.${filterKey}`;
|
|
738
|
+
} else {
|
|
739
|
+
memberAccess = `['${filterKey.replace(/'/g, "''")}']`;
|
|
740
|
+
}
|
|
713
741
|
return `${basePath}[?(@${memberAccess}==${escapedValue})]`;
|
|
714
742
|
}
|
|
715
743
|
|
|
@@ -927,12 +955,13 @@ function findFilterClose(s, from) {
|
|
|
927
955
|
}
|
|
928
956
|
return -1;
|
|
929
957
|
}
|
|
958
|
+
var NESTED_PATH_RE2 = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
930
959
|
function parseFilter(inner) {
|
|
931
960
|
if (inner.startsWith("@.")) {
|
|
932
961
|
const eq = inner.indexOf("==");
|
|
933
962
|
if (eq === -1) throw new Error(`Invalid filter: missing '==' in ${inner}`);
|
|
934
963
|
const key = inner.slice(2, eq);
|
|
935
|
-
if (!key || !
|
|
964
|
+
if (!key || !NESTED_PATH_RE2.test(key)) {
|
|
936
965
|
throw new Error(`Invalid property name in filter: '${key}'. Use bracket notation for non-identifier keys: @['${key}']`);
|
|
937
966
|
}
|
|
938
967
|
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(eq + 2)) };
|
|
@@ -940,7 +969,7 @@ function parseFilter(inner) {
|
|
|
940
969
|
if (inner.startsWith("@['")) {
|
|
941
970
|
const [key, endIdx] = extractQuotedString(inner, 3);
|
|
942
971
|
const valStart = endIdx + 4;
|
|
943
|
-
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(valStart)) };
|
|
972
|
+
return { type: "keyFilter", property: key, value: parseFilterLiteral(inner.slice(valStart)), literalKey: true };
|
|
944
973
|
}
|
|
945
974
|
if (inner.startsWith("@==")) {
|
|
946
975
|
return { type: "valueFilter", value: parseFilterLiteral(inner.slice(3)) };
|
|
@@ -1014,7 +1043,14 @@ function buildAtomPath(segments) {
|
|
|
1014
1043
|
result += `[${seg.index}]`;
|
|
1015
1044
|
break;
|
|
1016
1045
|
case "keyFilter": {
|
|
1017
|
-
|
|
1046
|
+
let memberAccess;
|
|
1047
|
+
if (seg.literalKey) {
|
|
1048
|
+
memberAccess = `['${seg.property.replace(/'/g, "''")}']`;
|
|
1049
|
+
} else if (NESTED_PATH_RE2.test(seg.property)) {
|
|
1050
|
+
memberAccess = `.${seg.property}`;
|
|
1051
|
+
} else {
|
|
1052
|
+
memberAccess = `['${seg.property.replace(/'/g, "''")}']`;
|
|
1053
|
+
}
|
|
1018
1054
|
result += `[?(@${memberAccess}==${formatFilterLiteral(seg.value)})]`;
|
|
1019
1055
|
break;
|
|
1020
1056
|
}
|
|
@@ -1031,7 +1067,7 @@ function canonicalizeFilterForAtom(inner) {
|
|
|
1031
1067
|
if (eqIdx === -1) return `[?(${inner})]`;
|
|
1032
1068
|
const key = inner.slice(2, eqIdx);
|
|
1033
1069
|
const valuePart = inner.slice(eqIdx);
|
|
1034
|
-
if (
|
|
1070
|
+
if (NESTED_PATH_RE2.test(key)) {
|
|
1035
1071
|
return `[?(@.${key}${valuePart})]`;
|
|
1036
1072
|
}
|
|
1037
1073
|
return `[?(@['${key.replace(/'/g, "''")}']${valuePart})]`;
|
|
@@ -1211,7 +1247,7 @@ function validateAtom(atom) {
|
|
|
1211
1247
|
errors.push(`operations[${i}]: must be an object`);
|
|
1212
1248
|
continue;
|
|
1213
1249
|
}
|
|
1214
|
-
if (!["add", "remove", "replace"].includes(op.op)) {
|
|
1250
|
+
if (!["add", "remove", "replace", "move", "copy"].includes(op.op)) {
|
|
1215
1251
|
errors.push(`operations[${i}]: invalid op '${op.op}'`);
|
|
1216
1252
|
}
|
|
1217
1253
|
if (typeof op.path !== "string") {
|
|
@@ -1231,6 +1267,33 @@ function validateAtom(atom) {
|
|
|
1231
1267
|
if (op.op === "replace" && !("value" in op)) {
|
|
1232
1268
|
errors.push(`operations[${i}]: replace operation must have value`);
|
|
1233
1269
|
}
|
|
1270
|
+
if (op.op === "move") {
|
|
1271
|
+
if (typeof op.from !== "string") {
|
|
1272
|
+
errors.push(`operations[${i}]: move operation must have from (string)`);
|
|
1273
|
+
}
|
|
1274
|
+
if ("value" in op) {
|
|
1275
|
+
errors.push(`operations[${i}]: move operation must not have value`);
|
|
1276
|
+
}
|
|
1277
|
+
if ("oldValue" in op) {
|
|
1278
|
+
errors.push(`operations[${i}]: move operation must not have oldValue`);
|
|
1279
|
+
}
|
|
1280
|
+
if (typeof op.from === "string" && typeof op.path === "string") {
|
|
1281
|
+
if (op.from === op.path) {
|
|
1282
|
+
errors.push(`operations[${i}]: move operation from must not equal path (self-move)`);
|
|
1283
|
+
}
|
|
1284
|
+
if (op.from !== "$" && (op.path.startsWith(op.from + ".") || op.path.startsWith(op.from + "["))) {
|
|
1285
|
+
errors.push(`operations[${i}]: move operation path must not be a subtree of from`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (op.op === "copy") {
|
|
1290
|
+
if (typeof op.from !== "string") {
|
|
1291
|
+
errors.push(`operations[${i}]: copy operation must have from (string)`);
|
|
1292
|
+
}
|
|
1293
|
+
if ("oldValue" in op) {
|
|
1294
|
+
errors.push(`operations[${i}]: copy operation must not have oldValue`);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1234
1297
|
}
|
|
1235
1298
|
}
|
|
1236
1299
|
return { valid: errors.length === 0, errors };
|
|
@@ -1489,6 +1552,9 @@ function fromAtom(atom) {
|
|
|
1489
1552
|
throw new Error(`Invalid atom: ${validation.errors.join(", ")}`);
|
|
1490
1553
|
}
|
|
1491
1554
|
return atom.operations.map((op) => {
|
|
1555
|
+
if (op.op === "move" || op.op === "copy") {
|
|
1556
|
+
throw new Error(`${op.op} operations cannot be converted to v4 atomic changes`);
|
|
1557
|
+
}
|
|
1492
1558
|
const atomicPath = atomPathToAtomicPath(op.path);
|
|
1493
1559
|
const key = extractKeyFromAtomicPath(atomicPath);
|
|
1494
1560
|
switch (op.op) {
|
|
@@ -1534,11 +1600,14 @@ function invertAtom(atom) {
|
|
|
1534
1600
|
if (op.op === "remove" && !("oldValue" in op)) {
|
|
1535
1601
|
throw new Error(`operations[${i}]: remove operation missing oldValue \u2014 atom is not reversible`);
|
|
1536
1602
|
}
|
|
1603
|
+
if (op.op === "copy" && !("value" in op)) {
|
|
1604
|
+
throw new Error(`operations[${i}]: copy operation missing value \u2014 atom is not reversible`);
|
|
1605
|
+
}
|
|
1537
1606
|
}
|
|
1538
1607
|
const invertedOps = [...atom.operations].reverse().map((op) => {
|
|
1539
1608
|
const extensions = {};
|
|
1540
1609
|
for (const key of Object.keys(op)) {
|
|
1541
|
-
if (!["op", "path", "value", "oldValue"].includes(key)) {
|
|
1610
|
+
if (!["op", "path", "from", "value", "oldValue"].includes(key)) {
|
|
1542
1611
|
extensions[key] = op[key];
|
|
1543
1612
|
}
|
|
1544
1613
|
}
|
|
@@ -1549,6 +1618,10 @@ function invertAtom(atom) {
|
|
|
1549
1618
|
return { op: "add", path: op.path, value: op.oldValue, ...extensions };
|
|
1550
1619
|
case "replace":
|
|
1551
1620
|
return { op: "replace", path: op.path, value: op.oldValue, oldValue: op.value, ...extensions };
|
|
1621
|
+
case "move":
|
|
1622
|
+
return { op: "move", from: op.path, path: op.from, ...extensions };
|
|
1623
|
+
case "copy":
|
|
1624
|
+
return { op: "remove", path: op.path, oldValue: op.value, ...extensions };
|
|
1552
1625
|
/* istanbul ignore next -- exhaustive switch */
|
|
1553
1626
|
default:
|
|
1554
1627
|
throw new Error(`Unknown operation: ${op.op}`);
|
|
@@ -1562,6 +1635,49 @@ function invertAtom(atom) {
|
|
|
1562
1635
|
}
|
|
1563
1636
|
return envelope;
|
|
1564
1637
|
}
|
|
1638
|
+
var NESTED_PATH_RE3 = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
1639
|
+
function resolveValueAtPath(obj, atomPath) {
|
|
1640
|
+
const segments = parseAtomPath(atomPath);
|
|
1641
|
+
let current = obj;
|
|
1642
|
+
for (const seg of segments) {
|
|
1643
|
+
switch (seg.type) {
|
|
1644
|
+
case "root":
|
|
1645
|
+
break;
|
|
1646
|
+
case "property":
|
|
1647
|
+
if (current == null || typeof current !== "object") {
|
|
1648
|
+
throw new Error(`Cannot access property '${seg.name}' on ${current === null ? "null" : typeof current} at path: ${atomPath}`);
|
|
1649
|
+
}
|
|
1650
|
+
current = current[seg.name];
|
|
1651
|
+
break;
|
|
1652
|
+
case "index":
|
|
1653
|
+
if (!Array.isArray(current)) {
|
|
1654
|
+
throw new Error(`Cannot access index ${seg.index} on non-array at path: ${atomPath}`);
|
|
1655
|
+
}
|
|
1656
|
+
current = current[seg.index];
|
|
1657
|
+
break;
|
|
1658
|
+
case "keyFilter": {
|
|
1659
|
+
if (!Array.isArray(current)) {
|
|
1660
|
+
throw new Error(`Cannot apply key filter on non-array at path: ${atomPath}`);
|
|
1661
|
+
}
|
|
1662
|
+
const prop = seg.property;
|
|
1663
|
+
const isPath = !seg.literalKey && prop.includes(".") && NESTED_PATH_RE3.test(prop);
|
|
1664
|
+
current = current.find((el) => {
|
|
1665
|
+
const resolved = isPath ? prop.split(".").reduce((c, s) => c?.[s], el) : el[prop];
|
|
1666
|
+
return JSON.stringify(resolved) === JSON.stringify(seg.value);
|
|
1667
|
+
});
|
|
1668
|
+
break;
|
|
1669
|
+
}
|
|
1670
|
+
case "valueFilter": {
|
|
1671
|
+
if (!Array.isArray(current)) {
|
|
1672
|
+
throw new Error(`Cannot apply value filter on non-array at path: ${atomPath}`);
|
|
1673
|
+
}
|
|
1674
|
+
current = current.find((el) => JSON.stringify(el) === JSON.stringify(seg.value));
|
|
1675
|
+
break;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
return current;
|
|
1680
|
+
}
|
|
1565
1681
|
function applyAtom(obj, atom) {
|
|
1566
1682
|
const validation = validateAtom(atom);
|
|
1567
1683
|
if (!validation.valid) {
|
|
@@ -1569,7 +1685,33 @@ function applyAtom(obj, atom) {
|
|
|
1569
1685
|
}
|
|
1570
1686
|
let result = obj;
|
|
1571
1687
|
for (const op of atom.operations) {
|
|
1572
|
-
if (op.
|
|
1688
|
+
if (op.op === "move") {
|
|
1689
|
+
const value = resolveValueAtPath(result, op.from);
|
|
1690
|
+
const removeOp = { op: "remove", path: op.from, oldValue: value };
|
|
1691
|
+
if (removeOp.path === "$") {
|
|
1692
|
+
result = applyRootOp(result, removeOp);
|
|
1693
|
+
} else {
|
|
1694
|
+
const removeChange = atomOpToAtomicChange(removeOp);
|
|
1695
|
+
applyChangeset(result, unatomizeChangeset([removeChange]));
|
|
1696
|
+
}
|
|
1697
|
+
const addOp = { op: "add", path: op.path, value };
|
|
1698
|
+
if (addOp.path === "$") {
|
|
1699
|
+
result = applyRootOp(result, addOp);
|
|
1700
|
+
} else {
|
|
1701
|
+
const addChange = atomOpToAtomicChange(addOp);
|
|
1702
|
+
applyChangeset(result, unatomizeChangeset([addChange]));
|
|
1703
|
+
}
|
|
1704
|
+
} else if (op.op === "copy") {
|
|
1705
|
+
const source = resolveValueAtPath(result, op.from);
|
|
1706
|
+
const value = source === void 0 ? void 0 : JSON.parse(JSON.stringify(source));
|
|
1707
|
+
const addOp = { op: "add", path: op.path, value };
|
|
1708
|
+
if (addOp.path === "$") {
|
|
1709
|
+
result = applyRootOp(result, addOp);
|
|
1710
|
+
} else {
|
|
1711
|
+
const addChange = atomOpToAtomicChange(addOp);
|
|
1712
|
+
applyChangeset(result, unatomizeChangeset([addChange]));
|
|
1713
|
+
}
|
|
1714
|
+
} else if (op.path === "$") {
|
|
1573
1715
|
result = applyRootOp(result, op);
|
|
1574
1716
|
} else {
|
|
1575
1717
|
const atomicChange = atomOpToAtomicChange(op);
|