json-diff-ts 5.0.0-alpha.0 → 5.0.0-alpha.2

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
@@ -121,6 +121,35 @@ declare const createContainer: (value: object | []) => IComparisonEnrichedNode;
121
121
  declare const enrich: (object: any) => IComparisonEnrichedNode;
122
122
  declare const applyChangelist: (object: IComparisonEnrichedNode, changelist: IAtomicChange[]) => IComparisonEnrichedNode;
123
123
  declare const compare: (oldObject: any, newObject: any) => IComparisonEnrichedNode;
124
+ interface IComparisonDict {
125
+ type: string;
126
+ value?: any;
127
+ oldValue?: any;
128
+ }
129
+ interface IFlatChange {
130
+ path: string;
131
+ type: string;
132
+ value?: any;
133
+ oldValue?: any;
134
+ }
135
+ /**
136
+ * Recursively serializes an enriched comparison tree to a plain JS object.
137
+ * The result is JSON-serializable when contained `value`/`oldValue` are
138
+ * themselves JSON-serializable. Includes `value`/`oldValue` based on change
139
+ * type, not truthiness — `null` is preserved as a valid JSON value.
140
+ */
141
+ declare const comparisonToDict: (node: IComparisonEnrichedNode) => IComparisonDict;
142
+ /**
143
+ * Flattens an enriched comparison tree to a list of leaf changes with paths.
144
+ * Uses dot notation for identifier keys and bracket-quote notation for
145
+ * non-identifier keys (per spec Section 5.5).
146
+ *
147
+ * By default, `UNCHANGED` entries are excluded. Set `includeUnchanged: true`
148
+ * to include them.
149
+ */
150
+ declare const comparisonToFlatList: (node: IComparisonEnrichedNode, options?: {
151
+ includeUnchanged?: boolean;
152
+ }) => IFlatChange[];
124
153
 
125
154
  type DeltaPathSegment = {
126
155
  type: 'root';
@@ -217,4 +246,60 @@ declare function applyDelta(obj: any, delta: IJsonDelta): any;
217
246
  */
218
247
  declare function revertDelta(obj: any, delta: IJsonDelta): any;
219
248
 
220
- export { type Changeset, CompareOperation, type DeltaOp, type DeltaOptions, type DeltaPathSegment, type EmbeddedObjKeysMapType, type EmbeddedObjKeysType, type IAtomicChange, type IChange, type IComparisonEnrichedNode, type IDeltaOperation, type IJsonDelta, Operation, type Options, applyChangelist, applyChangeset, applyDelta, atomizeChangeset, buildDeltaPath, compare, createContainer, createValue, diff, diffDelta, enrich, formatFilterLiteral, fromDelta, getTypeOfObj, invertDelta, parseDeltaPath, parseFilterLiteral, revertChangeset, revertDelta, toDelta, unatomizeChangeset, validateDelta };
249
+ /**
250
+ * Returns a copy of the operation containing only spec-defined keys
251
+ * (`op`, `path`, `value`, `oldValue`). Complement of `operationExtensions`.
252
+ */
253
+ declare function operationSpecDict(op: IDeltaOperation): IDeltaOperation;
254
+ /**
255
+ * Returns a record of non-spec keys from the operation (extension properties).
256
+ * Complement of `operationSpecDict`.
257
+ */
258
+ declare function operationExtensions(op: IDeltaOperation): Record<string, any>;
259
+ /**
260
+ * Returns the terminal property name from the operation's path, or `null`
261
+ * for root, index, and filter segments.
262
+ */
263
+ declare function leafProperty(op: IDeltaOperation): string | null;
264
+ /**
265
+ * Returns a copy of the delta with only spec-defined keys in the envelope
266
+ * and each operation. Strips all extension properties.
267
+ */
268
+ declare function deltaSpecDict(delta: IJsonDelta): IJsonDelta;
269
+ /**
270
+ * Returns a record of non-spec keys from the delta envelope.
271
+ * Complement of `deltaSpecDict`.
272
+ */
273
+ declare function deltaExtensions(delta: IJsonDelta): Record<string, any>;
274
+ /**
275
+ * Transforms each operation in the delta using the provided function.
276
+ * Returns a new delta (immutable). Preserves all envelope properties.
277
+ */
278
+ declare function deltaMap(delta: IJsonDelta, fn: (op: IDeltaOperation, index: number) => IDeltaOperation): IJsonDelta;
279
+ /**
280
+ * Returns a new delta with the given extension properties merged onto every
281
+ * operation. Immutable — the original delta is not modified.
282
+ */
283
+ declare function deltaStamp(delta: IJsonDelta, extensions: Record<string, any>): IJsonDelta;
284
+ /**
285
+ * Groups operations in the delta by the result of `keyFn`. Returns a record
286
+ * mapping each key to a sub-delta containing only the matching operations.
287
+ * Each sub-delta preserves all envelope properties.
288
+ */
289
+ declare function deltaGroupBy(delta: IJsonDelta, keyFn: (op: IDeltaOperation) => string): Record<string, IJsonDelta>;
290
+ interface SquashDeltasOptions extends DeltaOptions {
291
+ /** Pre-computed final state. When provided with deltas, used instead of computing. */
292
+ target?: any;
293
+ /** Verify that `target` matches sequential application of deltas. Default: true. */
294
+ verifyTarget?: boolean;
295
+ }
296
+ /**
297
+ * Compacts multiple deltas into a single net-effect delta. The result is
298
+ * equivalent to applying all deltas sequentially, but expressed as a single
299
+ * `source → final` diff.
300
+ *
301
+ * Envelope extensions from all input deltas are merged (last-wins on conflict).
302
+ */
303
+ declare function squashDeltas(source: any, deltas: IJsonDelta[], options?: SquashDeltasOptions): IJsonDelta;
304
+
305
+ export { type Changeset, CompareOperation, type DeltaOp, type DeltaOptions, type DeltaPathSegment, type EmbeddedObjKeysMapType, type EmbeddedObjKeysType, type IAtomicChange, type IChange, type IComparisonDict, type IComparisonEnrichedNode, type IDeltaOperation, type IFlatChange, type IJsonDelta, Operation, type Options, type SquashDeltasOptions, applyChangelist, applyChangeset, applyDelta, atomizeChangeset, buildDeltaPath, compare, comparisonToDict, comparisonToFlatList, createContainer, createValue, deltaExtensions, deltaGroupBy, deltaMap, deltaSpecDict, deltaStamp, diff, diffDelta, enrich, formatFilterLiteral, fromDelta, getTypeOfObj, invertDelta, leafProperty, operationExtensions, operationSpecDict, parseDeltaPath, parseFilterLiteral, revertChangeset, revertDelta, squashDeltas, toDelta, unatomizeChangeset, validateDelta };
package/dist/index.d.ts CHANGED
@@ -121,6 +121,35 @@ declare const createContainer: (value: object | []) => IComparisonEnrichedNode;
121
121
  declare const enrich: (object: any) => IComparisonEnrichedNode;
122
122
  declare const applyChangelist: (object: IComparisonEnrichedNode, changelist: IAtomicChange[]) => IComparisonEnrichedNode;
123
123
  declare const compare: (oldObject: any, newObject: any) => IComparisonEnrichedNode;
124
+ interface IComparisonDict {
125
+ type: string;
126
+ value?: any;
127
+ oldValue?: any;
128
+ }
129
+ interface IFlatChange {
130
+ path: string;
131
+ type: string;
132
+ value?: any;
133
+ oldValue?: any;
134
+ }
135
+ /**
136
+ * Recursively serializes an enriched comparison tree to a plain JS object.
137
+ * The result is JSON-serializable when contained `value`/`oldValue` are
138
+ * themselves JSON-serializable. Includes `value`/`oldValue` based on change
139
+ * type, not truthiness — `null` is preserved as a valid JSON value.
140
+ */
141
+ declare const comparisonToDict: (node: IComparisonEnrichedNode) => IComparisonDict;
142
+ /**
143
+ * Flattens an enriched comparison tree to a list of leaf changes with paths.
144
+ * Uses dot notation for identifier keys and bracket-quote notation for
145
+ * non-identifier keys (per spec Section 5.5).
146
+ *
147
+ * By default, `UNCHANGED` entries are excluded. Set `includeUnchanged: true`
148
+ * to include them.
149
+ */
150
+ declare const comparisonToFlatList: (node: IComparisonEnrichedNode, options?: {
151
+ includeUnchanged?: boolean;
152
+ }) => IFlatChange[];
124
153
 
125
154
  type DeltaPathSegment = {
126
155
  type: 'root';
@@ -217,4 +246,60 @@ declare function applyDelta(obj: any, delta: IJsonDelta): any;
217
246
  */
218
247
  declare function revertDelta(obj: any, delta: IJsonDelta): any;
219
248
 
220
- export { type Changeset, CompareOperation, type DeltaOp, type DeltaOptions, type DeltaPathSegment, type EmbeddedObjKeysMapType, type EmbeddedObjKeysType, type IAtomicChange, type IChange, type IComparisonEnrichedNode, type IDeltaOperation, type IJsonDelta, Operation, type Options, applyChangelist, applyChangeset, applyDelta, atomizeChangeset, buildDeltaPath, compare, createContainer, createValue, diff, diffDelta, enrich, formatFilterLiteral, fromDelta, getTypeOfObj, invertDelta, parseDeltaPath, parseFilterLiteral, revertChangeset, revertDelta, toDelta, unatomizeChangeset, validateDelta };
249
+ /**
250
+ * Returns a copy of the operation containing only spec-defined keys
251
+ * (`op`, `path`, `value`, `oldValue`). Complement of `operationExtensions`.
252
+ */
253
+ declare function operationSpecDict(op: IDeltaOperation): IDeltaOperation;
254
+ /**
255
+ * Returns a record of non-spec keys from the operation (extension properties).
256
+ * Complement of `operationSpecDict`.
257
+ */
258
+ declare function operationExtensions(op: IDeltaOperation): Record<string, any>;
259
+ /**
260
+ * Returns the terminal property name from the operation's path, or `null`
261
+ * for root, index, and filter segments.
262
+ */
263
+ declare function leafProperty(op: IDeltaOperation): string | null;
264
+ /**
265
+ * Returns a copy of the delta with only spec-defined keys in the envelope
266
+ * and each operation. Strips all extension properties.
267
+ */
268
+ declare function deltaSpecDict(delta: IJsonDelta): IJsonDelta;
269
+ /**
270
+ * Returns a record of non-spec keys from the delta envelope.
271
+ * Complement of `deltaSpecDict`.
272
+ */
273
+ declare function deltaExtensions(delta: IJsonDelta): Record<string, any>;
274
+ /**
275
+ * Transforms each operation in the delta using the provided function.
276
+ * Returns a new delta (immutable). Preserves all envelope properties.
277
+ */
278
+ declare function deltaMap(delta: IJsonDelta, fn: (op: IDeltaOperation, index: number) => IDeltaOperation): IJsonDelta;
279
+ /**
280
+ * Returns a new delta with the given extension properties merged onto every
281
+ * operation. Immutable — the original delta is not modified.
282
+ */
283
+ declare function deltaStamp(delta: IJsonDelta, extensions: Record<string, any>): IJsonDelta;
284
+ /**
285
+ * Groups operations in the delta by the result of `keyFn`. Returns a record
286
+ * mapping each key to a sub-delta containing only the matching operations.
287
+ * Each sub-delta preserves all envelope properties.
288
+ */
289
+ declare function deltaGroupBy(delta: IJsonDelta, keyFn: (op: IDeltaOperation) => string): Record<string, IJsonDelta>;
290
+ interface SquashDeltasOptions extends DeltaOptions {
291
+ /** Pre-computed final state. When provided with deltas, used instead of computing. */
292
+ target?: any;
293
+ /** Verify that `target` matches sequential application of deltas. Default: true. */
294
+ verifyTarget?: boolean;
295
+ }
296
+ /**
297
+ * Compacts multiple deltas into a single net-effect delta. The result is
298
+ * equivalent to applying all deltas sequentially, but expressed as a single
299
+ * `source → final` diff.
300
+ *
301
+ * Envelope extensions from all input deltas are merged (last-wins on conflict).
302
+ */
303
+ declare function squashDeltas(source: any, deltas: IJsonDelta[], options?: SquashDeltasOptions): IJsonDelta;
304
+
305
+ export { type Changeset, CompareOperation, type DeltaOp, type DeltaOptions, type DeltaPathSegment, type EmbeddedObjKeysMapType, type EmbeddedObjKeysType, type IAtomicChange, type IChange, type IComparisonDict, type IComparisonEnrichedNode, type IDeltaOperation, type IFlatChange, type IJsonDelta, Operation, type Options, type SquashDeltasOptions, applyChangelist, applyChangeset, applyDelta, atomizeChangeset, buildDeltaPath, compare, comparisonToDict, comparisonToFlatList, createContainer, createValue, deltaExtensions, deltaGroupBy, deltaMap, deltaSpecDict, deltaStamp, diff, diffDelta, enrich, formatFilterLiteral, fromDelta, getTypeOfObj, invertDelta, leafProperty, operationExtensions, operationSpecDict, parseDeltaPath, parseFilterLiteral, revertChangeset, revertDelta, squashDeltas, toDelta, unatomizeChangeset, validateDelta };
package/dist/index.js CHANGED
@@ -734,6 +734,74 @@ var compare2 = (oldObject, newObject) => {
734
734
  }
735
735
  return applyChangelist(enrich(oldObject), atomizeChangeset(diff(oldObject, newObject)));
736
736
  };
737
+ var comparisonToDict = (node) => {
738
+ const result = { type: node.type };
739
+ if (node.type === "CONTAINER" /* CONTAINER */) {
740
+ if (Array.isArray(node.value)) {
741
+ const children = node.value;
742
+ const serialized = new Array(children.length);
743
+ for (let i = 0; i < children.length; i++) {
744
+ const child = children[i];
745
+ serialized[i] = child != null ? comparisonToDict(child) : null;
746
+ }
747
+ result.value = serialized;
748
+ } else if (node.value && typeof node.value === "object") {
749
+ const obj = /* @__PURE__ */ Object.create(null);
750
+ for (const [key, child] of Object.entries(
751
+ node.value
752
+ )) {
753
+ if (child == null) continue;
754
+ obj[key] = comparisonToDict(child);
755
+ }
756
+ result.value = obj;
757
+ }
758
+ } else {
759
+ if (node.type === "UNCHANGED" /* UNCHANGED */ || node.type === "ADD" /* ADD */ || node.type === "UPDATE" /* UPDATE */) {
760
+ result.value = node.value;
761
+ }
762
+ if (node.type === "REMOVE" /* REMOVE */ || node.type === "UPDATE" /* UPDATE */) {
763
+ result.oldValue = node.oldValue;
764
+ }
765
+ }
766
+ return result;
767
+ };
768
+ var IDENT_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
769
+ var comparisonToFlatList = (node, options = {}) => {
770
+ const results = [];
771
+ flattenNode(node, "$", options.includeUnchanged ?? false, results);
772
+ return results;
773
+ };
774
+ function flattenNode(node, path, includeUnchanged, results) {
775
+ if (node.type === "CONTAINER" /* CONTAINER */) {
776
+ if (Array.isArray(node.value)) {
777
+ for (let i = 0; i < node.value.length; i++) {
778
+ const child = node.value[i];
779
+ if (child == null) continue;
780
+ flattenNode(child, `${path}[${i}]`, includeUnchanged, results);
781
+ }
782
+ } else if (node.value && typeof node.value === "object") {
783
+ for (const [key, child] of Object.entries(
784
+ node.value
785
+ )) {
786
+ if (child == null) continue;
787
+ const childPath = IDENT_RE.test(key) ? `${path}.${key}` : `${path}['${key.replace(/'/g, "''")}']`;
788
+ flattenNode(child, childPath, includeUnchanged, results);
789
+ }
790
+ }
791
+ return;
792
+ }
793
+ if (node.type === "UNCHANGED" /* UNCHANGED */ && !includeUnchanged) {
794
+ return;
795
+ }
796
+ const entry = { path, type: node.type };
797
+ if (node.type === "UNCHANGED" /* UNCHANGED */ || node.type === "ADD" /* ADD */ || node.type === "UPDATE" /* UPDATE */) {
798
+ entry.value = node.value;
799
+ }
800
+ if (node.type === "REMOVE" /* REMOVE */ || node.type === "UPDATE" /* UPDATE */) {
801
+ entry.oldValue = node.oldValue;
802
+ }
803
+ results.push(entry);
804
+ }
737
805
 
738
806
  // src/deltaPath.ts
739
807
  function formatFilterLiteral(value) {
@@ -1462,6 +1530,113 @@ function revertDelta(obj, delta) {
1462
1530
  const inverse = invertDelta(delta);
1463
1531
  return applyDelta(obj, inverse);
1464
1532
  }
1533
+
1534
+ // src/deltaHelpers.ts
1535
+ var OP_SPEC_KEYS = /* @__PURE__ */ new Set(["op", "path", "value", "oldValue"]);
1536
+ var DELTA_SPEC_KEYS = /* @__PURE__ */ new Set(["format", "version", "operations"]);
1537
+ function operationSpecDict(op) {
1538
+ const result = { op: op.op, path: op.path };
1539
+ if ("value" in op) result.value = op.value;
1540
+ if ("oldValue" in op) result.oldValue = op.oldValue;
1541
+ return result;
1542
+ }
1543
+ function operationExtensions(op) {
1544
+ const result = /* @__PURE__ */ Object.create(null);
1545
+ for (const key of Object.keys(op)) {
1546
+ if (!OP_SPEC_KEYS.has(key)) {
1547
+ result[key] = op[key];
1548
+ }
1549
+ }
1550
+ return result;
1551
+ }
1552
+ function leafProperty(op) {
1553
+ const segments = parseDeltaPath(op.path);
1554
+ if (segments.length === 0) return null;
1555
+ const last = segments[segments.length - 1];
1556
+ return last.type === "property" ? last.name : null;
1557
+ }
1558
+ function deltaSpecDict(delta) {
1559
+ return {
1560
+ format: delta.format,
1561
+ version: delta.version,
1562
+ operations: delta.operations.map(operationSpecDict)
1563
+ };
1564
+ }
1565
+ function deltaExtensions(delta) {
1566
+ const result = /* @__PURE__ */ Object.create(null);
1567
+ for (const key of Object.keys(delta)) {
1568
+ if (!DELTA_SPEC_KEYS.has(key)) {
1569
+ result[key] = delta[key];
1570
+ }
1571
+ }
1572
+ return result;
1573
+ }
1574
+ function deltaMap(delta, fn) {
1575
+ return { ...delta, operations: delta.operations.map((op, i) => fn(op, i)) };
1576
+ }
1577
+ function deltaStamp(delta, extensions) {
1578
+ return deltaMap(delta, (op) => ({ ...op, ...extensions }));
1579
+ }
1580
+ function deltaGroupBy(delta, keyFn) {
1581
+ const groups = /* @__PURE__ */ Object.create(null);
1582
+ for (const op of delta.operations) {
1583
+ const k = keyFn(op);
1584
+ if (!groups[k]) groups[k] = [];
1585
+ groups[k].push(op);
1586
+ }
1587
+ const envelope = /* @__PURE__ */ Object.create(null);
1588
+ for (const key of Object.keys(delta)) {
1589
+ if (key !== "operations") {
1590
+ envelope[key] = delta[key];
1591
+ }
1592
+ }
1593
+ const result = /* @__PURE__ */ Object.create(null);
1594
+ for (const [k, ops] of Object.entries(groups)) {
1595
+ result[k] = { ...envelope, operations: ops };
1596
+ }
1597
+ return result;
1598
+ }
1599
+ function deepClone(obj) {
1600
+ return JSON.parse(JSON.stringify(obj));
1601
+ }
1602
+ function squashDeltas(source, deltas, options = {}) {
1603
+ const { target, verifyTarget = true, ...diffOptions } = options;
1604
+ let final;
1605
+ if (target !== void 0 && deltas.length > 0 && verifyTarget) {
1606
+ let computed = deepClone(source);
1607
+ for (const d of deltas) {
1608
+ computed = applyDelta(computed, d);
1609
+ }
1610
+ const verification = diffDelta(computed, target, diffOptions);
1611
+ if (verification.operations.length > 0) {
1612
+ throw new Error(
1613
+ "squashDeltas: provided target does not match sequential application of deltas to source"
1614
+ );
1615
+ }
1616
+ final = target;
1617
+ } else if (target !== void 0) {
1618
+ final = target;
1619
+ } else {
1620
+ final = deepClone(source);
1621
+ for (const d of deltas) {
1622
+ final = applyDelta(final, d);
1623
+ }
1624
+ }
1625
+ const result = diffDelta(source, final, diffOptions);
1626
+ for (const d of deltas) {
1627
+ for (const key of Object.keys(d)) {
1628
+ if (!DELTA_SPEC_KEYS.has(key)) {
1629
+ Object.defineProperty(result, key, {
1630
+ value: d[key],
1631
+ writable: true,
1632
+ enumerable: true,
1633
+ configurable: true
1634
+ });
1635
+ }
1636
+ }
1637
+ }
1638
+ return result;
1639
+ }
1465
1640
  export {
1466
1641
  CompareOperation,
1467
1642
  Operation,
@@ -1471,8 +1646,15 @@ export {
1471
1646
  atomizeChangeset,
1472
1647
  buildDeltaPath,
1473
1648
  compare2 as compare,
1649
+ comparisonToDict,
1650
+ comparisonToFlatList,
1474
1651
  createContainer,
1475
1652
  createValue,
1653
+ deltaExtensions,
1654
+ deltaGroupBy,
1655
+ deltaMap,
1656
+ deltaSpecDict,
1657
+ deltaStamp,
1476
1658
  diff,
1477
1659
  diffDelta,
1478
1660
  enrich,
@@ -1480,10 +1662,14 @@ export {
1480
1662
  fromDelta,
1481
1663
  getTypeOfObj,
1482
1664
  invertDelta,
1665
+ leafProperty,
1666
+ operationExtensions,
1667
+ operationSpecDict,
1483
1668
  parseDeltaPath,
1484
1669
  parseFilterLiteral,
1485
1670
  revertChangeset,
1486
1671
  revertDelta,
1672
+ squashDeltas,
1487
1673
  toDelta,
1488
1674
  unatomizeChangeset,
1489
1675
  validateDelta