json-diff-ts 5.0.0-alpha.6 → 5.0.0-alpha.8
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 +135 -16
- 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 +135 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -487,12 +487,14 @@ var compareArray = (oldObj, newObj, path, keyPath, options) => {
|
|
|
487
487
|
const isFunctionKey = typeof uniqKey === "function" && uniqKey.length === 2;
|
|
488
488
|
if (diffs.length) {
|
|
489
489
|
const resolvedKey = isFunctionKey ? uniqKey(newObj[0] ?? oldObj[0], true) : uniqKey;
|
|
490
|
+
const sampleEl = newObj[0] ?? oldObj[0];
|
|
491
|
+
const isNestedPath = typeof resolvedKey === "string" && resolvedKey.includes(".") && NESTED_PATH_RE.test(resolvedKey) && (isFunctionKey || !(sampleEl != null && typeof sampleEl === "object" && resolvedKey in sampleEl));
|
|
490
492
|
return [
|
|
491
493
|
{
|
|
492
494
|
type: "UPDATE" /* UPDATE */,
|
|
493
495
|
key: getKey(path),
|
|
494
496
|
embeddedKey: resolvedKey,
|
|
495
|
-
...
|
|
497
|
+
...isNestedPath ? { embeddedKeyIsPath: true } : {},
|
|
496
498
|
changes: diffs
|
|
497
499
|
}
|
|
498
500
|
];
|
|
@@ -533,7 +535,8 @@ var convertArrayToObj = (arr, uniqKey) => {
|
|
|
533
535
|
obj[i] = value;
|
|
534
536
|
}
|
|
535
537
|
} else {
|
|
536
|
-
const
|
|
538
|
+
const maybeNestedPath = typeof uniqKey === "string" && uniqKey.includes(".") && NESTED_PATH_RE.test(uniqKey);
|
|
539
|
+
const keyFunction = typeof uniqKey === "string" ? maybeNestedPath ? (item) => item != null && typeof item === "object" && uniqKey in item ? item[uniqKey] : resolveProperty(item, uniqKey, true) : (item) => item[uniqKey] : uniqKey;
|
|
537
540
|
obj = keyBy(arr, keyFunction);
|
|
538
541
|
}
|
|
539
542
|
return obj;
|
|
@@ -580,7 +583,7 @@ var indexOfItemInArray = (arr, key, value, isPath) => {
|
|
|
580
583
|
const item = arr[i];
|
|
581
584
|
if (item == null) continue;
|
|
582
585
|
const resolved = resolveProperty(item, key, isPath);
|
|
583
|
-
if (resolved
|
|
586
|
+
if (resolved !== void 0 && String(resolved) === String(value)) {
|
|
584
587
|
return i;
|
|
585
588
|
}
|
|
586
589
|
}
|
|
@@ -636,7 +639,7 @@ var applyArrayChange = (arr, change) => {
|
|
|
636
639
|
} else {
|
|
637
640
|
element = arr.find((el) => {
|
|
638
641
|
const resolved = resolveProperty(el, change.embeddedKey, change.embeddedKeyIsPath);
|
|
639
|
-
return resolved
|
|
642
|
+
return resolved !== void 0 && String(resolved) === String(subchange.key);
|
|
640
643
|
});
|
|
641
644
|
}
|
|
642
645
|
if (element) {
|
|
@@ -706,7 +709,7 @@ var revertArrayChange = (arr, change) => {
|
|
|
706
709
|
} else {
|
|
707
710
|
element = arr.find((el) => {
|
|
708
711
|
const resolved = resolveProperty(el, change.embeddedKey, change.embeddedKeyIsPath);
|
|
709
|
-
return resolved
|
|
712
|
+
return resolved !== void 0 && String(resolved) === String(subchange.key);
|
|
710
713
|
});
|
|
711
714
|
}
|
|
712
715
|
if (element) {
|
|
@@ -1247,7 +1250,7 @@ function validateAtom(atom) {
|
|
|
1247
1250
|
errors.push(`operations[${i}]: must be an object`);
|
|
1248
1251
|
continue;
|
|
1249
1252
|
}
|
|
1250
|
-
if (!["add", "remove", "replace"].includes(op.op)) {
|
|
1253
|
+
if (!["add", "remove", "replace", "move", "copy"].includes(op.op)) {
|
|
1251
1254
|
errors.push(`operations[${i}]: invalid op '${op.op}'`);
|
|
1252
1255
|
}
|
|
1253
1256
|
if (typeof op.path !== "string") {
|
|
@@ -1267,6 +1270,33 @@ function validateAtom(atom) {
|
|
|
1267
1270
|
if (op.op === "replace" && !("value" in op)) {
|
|
1268
1271
|
errors.push(`operations[${i}]: replace operation must have value`);
|
|
1269
1272
|
}
|
|
1273
|
+
if (op.op === "move") {
|
|
1274
|
+
if (typeof op.from !== "string") {
|
|
1275
|
+
errors.push(`operations[${i}]: move operation must have from (string)`);
|
|
1276
|
+
}
|
|
1277
|
+
if ("value" in op) {
|
|
1278
|
+
errors.push(`operations[${i}]: move operation must not have value`);
|
|
1279
|
+
}
|
|
1280
|
+
if ("oldValue" in op) {
|
|
1281
|
+
errors.push(`operations[${i}]: move operation must not have oldValue`);
|
|
1282
|
+
}
|
|
1283
|
+
if (typeof op.from === "string" && typeof op.path === "string") {
|
|
1284
|
+
if (op.from === op.path) {
|
|
1285
|
+
errors.push(`operations[${i}]: move operation from must not equal path (self-move)`);
|
|
1286
|
+
}
|
|
1287
|
+
if (op.from !== "$" && (op.path.startsWith(op.from + ".") || op.path.startsWith(op.from + "["))) {
|
|
1288
|
+
errors.push(`operations[${i}]: move operation path must not be a subtree of from`);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (op.op === "copy") {
|
|
1293
|
+
if (typeof op.from !== "string") {
|
|
1294
|
+
errors.push(`operations[${i}]: copy operation must have from (string)`);
|
|
1295
|
+
}
|
|
1296
|
+
if ("oldValue" in op) {
|
|
1297
|
+
errors.push(`operations[${i}]: copy operation must not have oldValue`);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1270
1300
|
}
|
|
1271
1301
|
}
|
|
1272
1302
|
return { valid: errors.length === 0, errors };
|
|
@@ -1334,7 +1364,8 @@ function walkChanges(changes, basePath, oldCtx, newCtx, ops, options) {
|
|
|
1334
1364
|
childChange.key,
|
|
1335
1365
|
childOld,
|
|
1336
1366
|
childNew,
|
|
1337
|
-
childChange
|
|
1367
|
+
childChange,
|
|
1368
|
+
change.embeddedKeyIsPath
|
|
1338
1369
|
);
|
|
1339
1370
|
if (childChange.changes) {
|
|
1340
1371
|
const oldEl = findElement(childOld, change.embeddedKey, childChange.key);
|
|
@@ -1377,7 +1408,7 @@ function emitLeafOp(change, path, ops, options) {
|
|
|
1377
1408
|
}
|
|
1378
1409
|
}
|
|
1379
1410
|
}
|
|
1380
|
-
function buildCanonicalFilterPath(basePath, embeddedKey, changeKey, oldArr, newArr, change) {
|
|
1411
|
+
function buildCanonicalFilterPath(basePath, embeddedKey, changeKey, oldArr, newArr, change, embeddedKeyIsPath) {
|
|
1381
1412
|
if (embeddedKey === "$index") {
|
|
1382
1413
|
return `${basePath}[${changeKey}]`;
|
|
1383
1414
|
}
|
|
@@ -1397,9 +1428,11 @@ function buildCanonicalFilterPath(basePath, embeddedKey, changeKey, oldArr, newA
|
|
|
1397
1428
|
const memberAccess2 = typeof keyName === "string" && SIMPLE_PROPERTY_RE2.test(keyName) ? `.${keyName}` : `.${changeKey}`;
|
|
1398
1429
|
return `${basePath}[?(@${memberAccess2}=='${changeKey}')]`;
|
|
1399
1430
|
}
|
|
1400
|
-
const
|
|
1401
|
-
const
|
|
1402
|
-
const
|
|
1431
|
+
const isNestedPath = embeddedKeyIsPath && NESTED_PATH_RE3.test(embeddedKey);
|
|
1432
|
+
const element = findElementByKey(oldArr, newArr, embeddedKey, changeKey, change.type, isNestedPath);
|
|
1433
|
+
const resolved = element !== void 0 ? resolveNestedKey(element, embeddedKey, !!isNestedPath) : void 0;
|
|
1434
|
+
const typedVal = resolved !== void 0 ? resolved : changeKey;
|
|
1435
|
+
const memberAccess = SIMPLE_PROPERTY_RE2.test(embeddedKey) || isNestedPath ? `.${embeddedKey}` : `['${embeddedKey.replace(/'/g, "''")}']`;
|
|
1403
1436
|
return `${basePath}[?(@${memberAccess}==${formatFilterLiteral(typedVal)})]`;
|
|
1404
1437
|
}
|
|
1405
1438
|
function findActualValue(oldArr, newArr, stringKey, opType) {
|
|
@@ -1438,13 +1471,20 @@ function findElement(arr, embeddedKey, changeKey) {
|
|
|
1438
1471
|
}
|
|
1439
1472
|
return arr.find((item) => item && String(item[embeddedKey]) === changeKey);
|
|
1440
1473
|
}
|
|
1441
|
-
function
|
|
1474
|
+
function resolveNestedKey(item, key, isPath) {
|
|
1475
|
+
if (isPath) {
|
|
1476
|
+
return key.split(".").reduce((c, s) => c?.[s], item);
|
|
1477
|
+
}
|
|
1478
|
+
return item?.[key];
|
|
1479
|
+
}
|
|
1480
|
+
function findElementByKey(oldArr, newArr, embeddedKey, changeKey, opType, isPath) {
|
|
1481
|
+
const match = (item) => item && String(resolveNestedKey(item, embeddedKey, !!isPath)) === changeKey;
|
|
1442
1482
|
if (opType === "REMOVE" /* REMOVE */ || opType === "UPDATE" /* UPDATE */) {
|
|
1443
|
-
const el = oldArr?.find(
|
|
1483
|
+
const el = oldArr?.find(match);
|
|
1444
1484
|
if (el) return el;
|
|
1445
1485
|
}
|
|
1446
1486
|
if (opType === "ADD" /* ADD */ || opType === "UPDATE" /* UPDATE */) {
|
|
1447
|
-
const el = newArr?.find(
|
|
1487
|
+
const el = newArr?.find(match);
|
|
1448
1488
|
if (el) return el;
|
|
1449
1489
|
}
|
|
1450
1490
|
return void 0;
|
|
@@ -1525,6 +1565,9 @@ function fromAtom(atom) {
|
|
|
1525
1565
|
throw new Error(`Invalid atom: ${validation.errors.join(", ")}`);
|
|
1526
1566
|
}
|
|
1527
1567
|
return atom.operations.map((op) => {
|
|
1568
|
+
if (op.op === "move" || op.op === "copy") {
|
|
1569
|
+
throw new Error(`${op.op} operations cannot be converted to v4 atomic changes`);
|
|
1570
|
+
}
|
|
1528
1571
|
const atomicPath = atomPathToAtomicPath(op.path);
|
|
1529
1572
|
const key = extractKeyFromAtomicPath(atomicPath);
|
|
1530
1573
|
switch (op.op) {
|
|
@@ -1570,11 +1613,14 @@ function invertAtom(atom) {
|
|
|
1570
1613
|
if (op.op === "remove" && !("oldValue" in op)) {
|
|
1571
1614
|
throw new Error(`operations[${i}]: remove operation missing oldValue \u2014 atom is not reversible`);
|
|
1572
1615
|
}
|
|
1616
|
+
if (op.op === "copy" && !("value" in op)) {
|
|
1617
|
+
throw new Error(`operations[${i}]: copy operation missing value \u2014 atom is not reversible`);
|
|
1618
|
+
}
|
|
1573
1619
|
}
|
|
1574
1620
|
const invertedOps = [...atom.operations].reverse().map((op) => {
|
|
1575
1621
|
const extensions = {};
|
|
1576
1622
|
for (const key of Object.keys(op)) {
|
|
1577
|
-
if (!["op", "path", "value", "oldValue"].includes(key)) {
|
|
1623
|
+
if (!["op", "path", "from", "value", "oldValue"].includes(key)) {
|
|
1578
1624
|
extensions[key] = op[key];
|
|
1579
1625
|
}
|
|
1580
1626
|
}
|
|
@@ -1585,6 +1631,10 @@ function invertAtom(atom) {
|
|
|
1585
1631
|
return { op: "add", path: op.path, value: op.oldValue, ...extensions };
|
|
1586
1632
|
case "replace":
|
|
1587
1633
|
return { op: "replace", path: op.path, value: op.oldValue, oldValue: op.value, ...extensions };
|
|
1634
|
+
case "move":
|
|
1635
|
+
return { op: "move", from: op.path, path: op.from, ...extensions };
|
|
1636
|
+
case "copy":
|
|
1637
|
+
return { op: "remove", path: op.path, oldValue: op.value, ...extensions };
|
|
1588
1638
|
/* istanbul ignore next -- exhaustive switch */
|
|
1589
1639
|
default:
|
|
1590
1640
|
throw new Error(`Unknown operation: ${op.op}`);
|
|
@@ -1598,6 +1648,49 @@ function invertAtom(atom) {
|
|
|
1598
1648
|
}
|
|
1599
1649
|
return envelope;
|
|
1600
1650
|
}
|
|
1651
|
+
var NESTED_PATH_RE3 = /^[a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*$/;
|
|
1652
|
+
function resolveValueAtPath(obj, atomPath) {
|
|
1653
|
+
const segments = parseAtomPath(atomPath);
|
|
1654
|
+
let current = obj;
|
|
1655
|
+
for (const seg of segments) {
|
|
1656
|
+
switch (seg.type) {
|
|
1657
|
+
case "root":
|
|
1658
|
+
break;
|
|
1659
|
+
case "property":
|
|
1660
|
+
if (current == null || typeof current !== "object") {
|
|
1661
|
+
throw new Error(`Cannot access property '${seg.name}' on ${current === null ? "null" : typeof current} at path: ${atomPath}`);
|
|
1662
|
+
}
|
|
1663
|
+
current = current[seg.name];
|
|
1664
|
+
break;
|
|
1665
|
+
case "index":
|
|
1666
|
+
if (!Array.isArray(current)) {
|
|
1667
|
+
throw new Error(`Cannot access index ${seg.index} on non-array at path: ${atomPath}`);
|
|
1668
|
+
}
|
|
1669
|
+
current = current[seg.index];
|
|
1670
|
+
break;
|
|
1671
|
+
case "keyFilter": {
|
|
1672
|
+
if (!Array.isArray(current)) {
|
|
1673
|
+
throw new Error(`Cannot apply key filter on non-array at path: ${atomPath}`);
|
|
1674
|
+
}
|
|
1675
|
+
const prop = seg.property;
|
|
1676
|
+
const isPath = !seg.literalKey && prop.includes(".") && NESTED_PATH_RE3.test(prop);
|
|
1677
|
+
current = current.find((el) => {
|
|
1678
|
+
const resolved = isPath ? prop.split(".").reduce((c, s) => c?.[s], el) : el[prop];
|
|
1679
|
+
return JSON.stringify(resolved) === JSON.stringify(seg.value);
|
|
1680
|
+
});
|
|
1681
|
+
break;
|
|
1682
|
+
}
|
|
1683
|
+
case "valueFilter": {
|
|
1684
|
+
if (!Array.isArray(current)) {
|
|
1685
|
+
throw new Error(`Cannot apply value filter on non-array at path: ${atomPath}`);
|
|
1686
|
+
}
|
|
1687
|
+
current = current.find((el) => JSON.stringify(el) === JSON.stringify(seg.value));
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return current;
|
|
1693
|
+
}
|
|
1601
1694
|
function applyAtom(obj, atom) {
|
|
1602
1695
|
const validation = validateAtom(atom);
|
|
1603
1696
|
if (!validation.valid) {
|
|
@@ -1605,7 +1698,33 @@ function applyAtom(obj, atom) {
|
|
|
1605
1698
|
}
|
|
1606
1699
|
let result = obj;
|
|
1607
1700
|
for (const op of atom.operations) {
|
|
1608
|
-
if (op.
|
|
1701
|
+
if (op.op === "move") {
|
|
1702
|
+
const value = resolveValueAtPath(result, op.from);
|
|
1703
|
+
const removeOp = { op: "remove", path: op.from, oldValue: value };
|
|
1704
|
+
if (removeOp.path === "$") {
|
|
1705
|
+
result = applyRootOp(result, removeOp);
|
|
1706
|
+
} else {
|
|
1707
|
+
const removeChange = atomOpToAtomicChange(removeOp);
|
|
1708
|
+
applyChangeset(result, unatomizeChangeset([removeChange]));
|
|
1709
|
+
}
|
|
1710
|
+
const addOp = { op: "add", path: op.path, value };
|
|
1711
|
+
if (addOp.path === "$") {
|
|
1712
|
+
result = applyRootOp(result, addOp);
|
|
1713
|
+
} else {
|
|
1714
|
+
const addChange = atomOpToAtomicChange(addOp);
|
|
1715
|
+
applyChangeset(result, unatomizeChangeset([addChange]));
|
|
1716
|
+
}
|
|
1717
|
+
} else if (op.op === "copy") {
|
|
1718
|
+
const source = resolveValueAtPath(result, op.from);
|
|
1719
|
+
const value = source === void 0 ? void 0 : JSON.parse(JSON.stringify(source));
|
|
1720
|
+
const addOp = { op: "add", path: op.path, value };
|
|
1721
|
+
if (addOp.path === "$") {
|
|
1722
|
+
result = applyRootOp(result, addOp);
|
|
1723
|
+
} else {
|
|
1724
|
+
const addChange = atomOpToAtomicChange(addOp);
|
|
1725
|
+
applyChangeset(result, unatomizeChangeset([addChange]));
|
|
1726
|
+
}
|
|
1727
|
+
} else if (op.path === "$") {
|
|
1609
1728
|
result = applyRootOp(result, op);
|
|
1610
1729
|
} else {
|
|
1611
1730
|
const atomicChange = atomOpToAtomicChange(op);
|