json-diff-ts 5.0.0-alpha.6 → 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 +109 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +109 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1247,7 +1247,7 @@ function validateAtom(atom) {
|
|
|
1247
1247
|
errors.push(`operations[${i}]: must be an object`);
|
|
1248
1248
|
continue;
|
|
1249
1249
|
}
|
|
1250
|
-
if (!["add", "remove", "replace"].includes(op.op)) {
|
|
1250
|
+
if (!["add", "remove", "replace", "move", "copy"].includes(op.op)) {
|
|
1251
1251
|
errors.push(`operations[${i}]: invalid op '${op.op}'`);
|
|
1252
1252
|
}
|
|
1253
1253
|
if (typeof op.path !== "string") {
|
|
@@ -1267,6 +1267,33 @@ function validateAtom(atom) {
|
|
|
1267
1267
|
if (op.op === "replace" && !("value" in op)) {
|
|
1268
1268
|
errors.push(`operations[${i}]: replace operation must have value`);
|
|
1269
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
|
+
}
|
|
1270
1297
|
}
|
|
1271
1298
|
}
|
|
1272
1299
|
return { valid: errors.length === 0, errors };
|
|
@@ -1525,6 +1552,9 @@ function fromAtom(atom) {
|
|
|
1525
1552
|
throw new Error(`Invalid atom: ${validation.errors.join(", ")}`);
|
|
1526
1553
|
}
|
|
1527
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
|
+
}
|
|
1528
1558
|
const atomicPath = atomPathToAtomicPath(op.path);
|
|
1529
1559
|
const key = extractKeyFromAtomicPath(atomicPath);
|
|
1530
1560
|
switch (op.op) {
|
|
@@ -1570,11 +1600,14 @@ function invertAtom(atom) {
|
|
|
1570
1600
|
if (op.op === "remove" && !("oldValue" in op)) {
|
|
1571
1601
|
throw new Error(`operations[${i}]: remove operation missing oldValue \u2014 atom is not reversible`);
|
|
1572
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
|
+
}
|
|
1573
1606
|
}
|
|
1574
1607
|
const invertedOps = [...atom.operations].reverse().map((op) => {
|
|
1575
1608
|
const extensions = {};
|
|
1576
1609
|
for (const key of Object.keys(op)) {
|
|
1577
|
-
if (!["op", "path", "value", "oldValue"].includes(key)) {
|
|
1610
|
+
if (!["op", "path", "from", "value", "oldValue"].includes(key)) {
|
|
1578
1611
|
extensions[key] = op[key];
|
|
1579
1612
|
}
|
|
1580
1613
|
}
|
|
@@ -1585,6 +1618,10 @@ function invertAtom(atom) {
|
|
|
1585
1618
|
return { op: "add", path: op.path, value: op.oldValue, ...extensions };
|
|
1586
1619
|
case "replace":
|
|
1587
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 };
|
|
1588
1625
|
/* istanbul ignore next -- exhaustive switch */
|
|
1589
1626
|
default:
|
|
1590
1627
|
throw new Error(`Unknown operation: ${op.op}`);
|
|
@@ -1598,6 +1635,49 @@ function invertAtom(atom) {
|
|
|
1598
1635
|
}
|
|
1599
1636
|
return envelope;
|
|
1600
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
|
+
}
|
|
1601
1681
|
function applyAtom(obj, atom) {
|
|
1602
1682
|
const validation = validateAtom(atom);
|
|
1603
1683
|
if (!validation.valid) {
|
|
@@ -1605,7 +1685,33 @@ function applyAtom(obj, atom) {
|
|
|
1605
1685
|
}
|
|
1606
1686
|
let result = obj;
|
|
1607
1687
|
for (const op of atom.operations) {
|
|
1608
|
-
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 === "$") {
|
|
1609
1715
|
result = applyRootOp(result, op);
|
|
1610
1716
|
} else {
|
|
1611
1717
|
const atomicChange = atomOpToAtomicChange(op);
|