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.d.cts
CHANGED
|
@@ -191,10 +191,11 @@ declare function parseAtomPath(path: string): AtomPathSegment[];
|
|
|
191
191
|
*/
|
|
192
192
|
declare function buildAtomPath(segments: AtomPathSegment[]): string;
|
|
193
193
|
|
|
194
|
-
type AtomOp = 'add' | 'remove' | 'replace';
|
|
194
|
+
type AtomOp = 'add' | 'remove' | 'replace' | 'move' | 'copy';
|
|
195
195
|
interface IAtomOperation {
|
|
196
196
|
op: AtomOp;
|
|
197
197
|
path: string;
|
|
198
|
+
from?: string;
|
|
198
199
|
value?: any;
|
|
199
200
|
oldValue?: any;
|
|
200
201
|
[key: string]: any;
|
package/dist/index.d.ts
CHANGED
|
@@ -191,10 +191,11 @@ declare function parseAtomPath(path: string): AtomPathSegment[];
|
|
|
191
191
|
*/
|
|
192
192
|
declare function buildAtomPath(segments: AtomPathSegment[]): string;
|
|
193
193
|
|
|
194
|
-
type AtomOp = 'add' | 'remove' | 'replace';
|
|
194
|
+
type AtomOp = 'add' | 'remove' | 'replace' | 'move' | 'copy';
|
|
195
195
|
interface IAtomOperation {
|
|
196
196
|
op: AtomOp;
|
|
197
197
|
path: string;
|
|
198
|
+
from?: string;
|
|
198
199
|
value?: any;
|
|
199
200
|
oldValue?: any;
|
|
200
201
|
[key: string]: any;
|
package/dist/index.js
CHANGED
|
@@ -1187,7 +1187,7 @@ function validateAtom(atom) {
|
|
|
1187
1187
|
errors.push(`operations[${i}]: must be an object`);
|
|
1188
1188
|
continue;
|
|
1189
1189
|
}
|
|
1190
|
-
if (!["add", "remove", "replace"].includes(op.op)) {
|
|
1190
|
+
if (!["add", "remove", "replace", "move", "copy"].includes(op.op)) {
|
|
1191
1191
|
errors.push(`operations[${i}]: invalid op '${op.op}'`);
|
|
1192
1192
|
}
|
|
1193
1193
|
if (typeof op.path !== "string") {
|
|
@@ -1207,6 +1207,33 @@ function validateAtom(atom) {
|
|
|
1207
1207
|
if (op.op === "replace" && !("value" in op)) {
|
|
1208
1208
|
errors.push(`operations[${i}]: replace operation must have value`);
|
|
1209
1209
|
}
|
|
1210
|
+
if (op.op === "move") {
|
|
1211
|
+
if (typeof op.from !== "string") {
|
|
1212
|
+
errors.push(`operations[${i}]: move operation must have from (string)`);
|
|
1213
|
+
}
|
|
1214
|
+
if ("value" in op) {
|
|
1215
|
+
errors.push(`operations[${i}]: move operation must not have value`);
|
|
1216
|
+
}
|
|
1217
|
+
if ("oldValue" in op) {
|
|
1218
|
+
errors.push(`operations[${i}]: move operation must not have oldValue`);
|
|
1219
|
+
}
|
|
1220
|
+
if (typeof op.from === "string" && typeof op.path === "string") {
|
|
1221
|
+
if (op.from === op.path) {
|
|
1222
|
+
errors.push(`operations[${i}]: move operation from must not equal path (self-move)`);
|
|
1223
|
+
}
|
|
1224
|
+
if (op.from !== "$" && (op.path.startsWith(op.from + ".") || op.path.startsWith(op.from + "["))) {
|
|
1225
|
+
errors.push(`operations[${i}]: move operation path must not be a subtree of from`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (op.op === "copy") {
|
|
1230
|
+
if (typeof op.from !== "string") {
|
|
1231
|
+
errors.push(`operations[${i}]: copy operation must have from (string)`);
|
|
1232
|
+
}
|
|
1233
|
+
if ("oldValue" in op) {
|
|
1234
|
+
errors.push(`operations[${i}]: copy operation must not have oldValue`);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1210
1237
|
}
|
|
1211
1238
|
}
|
|
1212
1239
|
return { valid: errors.length === 0, errors };
|
|
@@ -1465,6 +1492,9 @@ function fromAtom(atom) {
|
|
|
1465
1492
|
throw new Error(`Invalid atom: ${validation.errors.join(", ")}`);
|
|
1466
1493
|
}
|
|
1467
1494
|
return atom.operations.map((op) => {
|
|
1495
|
+
if (op.op === "move" || op.op === "copy") {
|
|
1496
|
+
throw new Error(`${op.op} operations cannot be converted to v4 atomic changes`);
|
|
1497
|
+
}
|
|
1468
1498
|
const atomicPath = atomPathToAtomicPath(op.path);
|
|
1469
1499
|
const key = extractKeyFromAtomicPath(atomicPath);
|
|
1470
1500
|
switch (op.op) {
|
|
@@ -1510,11 +1540,14 @@ function invertAtom(atom) {
|
|
|
1510
1540
|
if (op.op === "remove" && !("oldValue" in op)) {
|
|
1511
1541
|
throw new Error(`operations[${i}]: remove operation missing oldValue \u2014 atom is not reversible`);
|
|
1512
1542
|
}
|
|
1543
|
+
if (op.op === "copy" && !("value" in op)) {
|
|
1544
|
+
throw new Error(`operations[${i}]: copy operation missing value \u2014 atom is not reversible`);
|
|
1545
|
+
}
|
|
1513
1546
|
}
|
|
1514
1547
|
const invertedOps = [...atom.operations].reverse().map((op) => {
|
|
1515
1548
|
const extensions = {};
|
|
1516
1549
|
for (const key of Object.keys(op)) {
|
|
1517
|
-
if (!["op", "path", "value", "oldValue"].includes(key)) {
|
|
1550
|
+
if (!["op", "path", "from", "value", "oldValue"].includes(key)) {
|
|
1518
1551
|
extensions[key] = op[key];
|
|
1519
1552
|
}
|
|
1520
1553
|
}
|
|
@@ -1525,6 +1558,10 @@ function invertAtom(atom) {
|
|
|
1525
1558
|
return { op: "add", path: op.path, value: op.oldValue, ...extensions };
|
|
1526
1559
|
case "replace":
|
|
1527
1560
|
return { op: "replace", path: op.path, value: op.oldValue, oldValue: op.value, ...extensions };
|
|
1561
|
+
case "move":
|
|
1562
|
+
return { op: "move", from: op.path, path: op.from, ...extensions };
|
|
1563
|
+
case "copy":
|
|
1564
|
+
return { op: "remove", path: op.path, oldValue: op.value, ...extensions };
|
|
1528
1565
|
/* istanbul ignore next -- exhaustive switch */
|
|
1529
1566
|
default:
|
|
1530
1567
|
throw new Error(`Unknown operation: ${op.op}`);
|
|
@@ -1538,6 +1575,49 @@ function invertAtom(atom) {
|
|
|
1538
1575
|
}
|
|
1539
1576
|
return envelope;
|
|
1540
1577
|
}
|
|
1578
|
+
var NESTED_PATH_RE3 = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
1579
|
+
function resolveValueAtPath(obj, atomPath) {
|
|
1580
|
+
const segments = parseAtomPath(atomPath);
|
|
1581
|
+
let current = obj;
|
|
1582
|
+
for (const seg of segments) {
|
|
1583
|
+
switch (seg.type) {
|
|
1584
|
+
case "root":
|
|
1585
|
+
break;
|
|
1586
|
+
case "property":
|
|
1587
|
+
if (current == null || typeof current !== "object") {
|
|
1588
|
+
throw new Error(`Cannot access property '${seg.name}' on ${current === null ? "null" : typeof current} at path: ${atomPath}`);
|
|
1589
|
+
}
|
|
1590
|
+
current = current[seg.name];
|
|
1591
|
+
break;
|
|
1592
|
+
case "index":
|
|
1593
|
+
if (!Array.isArray(current)) {
|
|
1594
|
+
throw new Error(`Cannot access index ${seg.index} on non-array at path: ${atomPath}`);
|
|
1595
|
+
}
|
|
1596
|
+
current = current[seg.index];
|
|
1597
|
+
break;
|
|
1598
|
+
case "keyFilter": {
|
|
1599
|
+
if (!Array.isArray(current)) {
|
|
1600
|
+
throw new Error(`Cannot apply key filter on non-array at path: ${atomPath}`);
|
|
1601
|
+
}
|
|
1602
|
+
const prop = seg.property;
|
|
1603
|
+
const isPath = !seg.literalKey && prop.includes(".") && NESTED_PATH_RE3.test(prop);
|
|
1604
|
+
current = current.find((el) => {
|
|
1605
|
+
const resolved = isPath ? prop.split(".").reduce((c, s) => c?.[s], el) : el[prop];
|
|
1606
|
+
return JSON.stringify(resolved) === JSON.stringify(seg.value);
|
|
1607
|
+
});
|
|
1608
|
+
break;
|
|
1609
|
+
}
|
|
1610
|
+
case "valueFilter": {
|
|
1611
|
+
if (!Array.isArray(current)) {
|
|
1612
|
+
throw new Error(`Cannot apply value filter on non-array at path: ${atomPath}`);
|
|
1613
|
+
}
|
|
1614
|
+
current = current.find((el) => JSON.stringify(el) === JSON.stringify(seg.value));
|
|
1615
|
+
break;
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
return current;
|
|
1620
|
+
}
|
|
1541
1621
|
function applyAtom(obj, atom) {
|
|
1542
1622
|
const validation = validateAtom(atom);
|
|
1543
1623
|
if (!validation.valid) {
|
|
@@ -1545,7 +1625,33 @@ function applyAtom(obj, atom) {
|
|
|
1545
1625
|
}
|
|
1546
1626
|
let result = obj;
|
|
1547
1627
|
for (const op of atom.operations) {
|
|
1548
|
-
if (op.
|
|
1628
|
+
if (op.op === "move") {
|
|
1629
|
+
const value = resolveValueAtPath(result, op.from);
|
|
1630
|
+
const removeOp = { op: "remove", path: op.from, oldValue: value };
|
|
1631
|
+
if (removeOp.path === "$") {
|
|
1632
|
+
result = applyRootOp(result, removeOp);
|
|
1633
|
+
} else {
|
|
1634
|
+
const removeChange = atomOpToAtomicChange(removeOp);
|
|
1635
|
+
applyChangeset(result, unatomizeChangeset([removeChange]));
|
|
1636
|
+
}
|
|
1637
|
+
const addOp = { op: "add", path: op.path, value };
|
|
1638
|
+
if (addOp.path === "$") {
|
|
1639
|
+
result = applyRootOp(result, addOp);
|
|
1640
|
+
} else {
|
|
1641
|
+
const addChange = atomOpToAtomicChange(addOp);
|
|
1642
|
+
applyChangeset(result, unatomizeChangeset([addChange]));
|
|
1643
|
+
}
|
|
1644
|
+
} else if (op.op === "copy") {
|
|
1645
|
+
const source = resolveValueAtPath(result, op.from);
|
|
1646
|
+
const value = source === void 0 ? void 0 : JSON.parse(JSON.stringify(source));
|
|
1647
|
+
const addOp = { op: "add", path: op.path, value };
|
|
1648
|
+
if (addOp.path === "$") {
|
|
1649
|
+
result = applyRootOp(result, addOp);
|
|
1650
|
+
} else {
|
|
1651
|
+
const addChange = atomOpToAtomicChange(addOp);
|
|
1652
|
+
applyChangeset(result, unatomizeChangeset([addChange]));
|
|
1653
|
+
}
|
|
1654
|
+
} else if (op.path === "$") {
|
|
1549
1655
|
result = applyRootOp(result, op);
|
|
1550
1656
|
} else {
|
|
1551
1657
|
const atomicChange = atomOpToAtomicChange(op);
|