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.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.path === "$") {
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);