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

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/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
13
  [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-yellow.svg?logo=buy-me-a-coffee)](https://buymeacoffee.com/leitwolf)
14
14
 
15
- **Deterministic JSON state transitions with key-based array identity.** A TypeScript JSON diff library that computes, applies, and reverts atomic changes using the [JSON Delta](https://github.com/ltwlf/json-delta-format) wire format -- a JSON Patch alternative with stable array paths, built-in undo/redo for JSON, and language-agnostic state synchronization.
15
+ **Deterministic JSON state transitions with key-based array identity.** A TypeScript JSON diff library that computes, applies, and reverts atomic changes using the [JSON Atom](https://github.com/ltwlf/json-atom-format) wire format -- a JSON Patch alternative with stable array paths, built-in undo/redo for JSON, and language-agnostic state synchronization.
16
16
 
17
17
  Zero dependencies. TypeScript-first. ESM + CommonJS. Trusted by thousands of developers ([500K+ weekly npm downloads](https://www.npmjs.com/package/json-diff-ts)).
18
18
 
@@ -32,7 +32,7 @@ This makes diffs fragile -- you can't store them, replay them reliably, or build
32
32
  **json-diff-ts solves this with key-based identity.** Array elements are matched by a stable key (`id`, `sku`, or any field), and paths use JSONPath filter expressions that survive insertions, deletions, and reordering:
33
33
 
34
34
  ```typescript
35
- import { diffDelta, applyDelta, revertDelta } from 'json-diff-ts';
35
+ import { diffAtom, applyAtom, revertAtom } from 'json-diff-ts';
36
36
 
37
37
  const before = {
38
38
  items: [
@@ -49,14 +49,14 @@ const after = {
49
49
  ],
50
50
  };
51
51
 
52
- const delta = diffDelta(before, after, { arrayIdentityKeys: { items: 'id' } });
52
+ const atom = diffAtom(before, after, { arrayIdentityKeys: { items: 'id' } });
53
53
  ```
54
54
 
55
- The delta tracks _what_ changed, not _where_ it moved:
55
+ The atom tracks _what_ changed, not _where_ it moved:
56
56
 
57
57
  ```json
58
58
  {
59
- "format": "json-delta",
59
+ "format": "json-atom",
60
60
  "version": 1,
61
61
  "operations": [
62
62
  { "op": "replace", "path": "$.items[?(@.id==1)].name", "value": "Widget Pro", "oldValue": "Widget" },
@@ -69,15 +69,15 @@ The delta tracks _what_ changed, not _where_ it moved:
69
69
  Apply forward to get the new state, or revert to restore the original:
70
70
 
71
71
  ```typescript
72
- // Clone before applying — applyDelta mutates the input object
73
- const updated = applyDelta(structuredClone(before), delta); // updated === after
74
- const restored = revertDelta(structuredClone(updated), delta); // restored === before
72
+ // Clone before applying — applyAtom mutates the input object
73
+ const updated = applyAtom(structuredClone(before), atom); // updated === after
74
+ const restored = revertAtom(structuredClone(updated), atom); // restored === before
75
75
  ```
76
76
 
77
77
  ## Quick Start
78
78
 
79
79
  ```typescript
80
- import { diffDelta, applyDelta, revertDelta } from 'json-diff-ts';
80
+ import { diffAtom, applyAtom, revertAtom } from 'json-diff-ts';
81
81
 
82
82
  const oldObj = {
83
83
  items: [
@@ -94,24 +94,24 @@ const newObj = {
94
94
  ],
95
95
  };
96
96
 
97
- // 1. Compute a delta between two JSON objects
98
- const delta = diffDelta(oldObj, newObj, {
97
+ // 1. Compute an atom between two JSON objects
98
+ const atom = diffAtom(oldObj, newObj, {
99
99
  arrayIdentityKeys: { items: 'id' }, // match array elements by 'id' field
100
100
  });
101
- // delta.operations =>
101
+ // atom.operations =>
102
102
  // [
103
103
  // { op: 'replace', path: '$.items[?(@.id==1)].name', value: 'Widget Pro', oldValue: 'Widget' },
104
104
  // { op: 'add', path: '$.items[?(@.id==3)]', value: { id: 3, name: 'Doohickey', price: 4.99 } }
105
105
  // ]
106
106
 
107
- // 2. Apply the delta to produce the new state
108
- const updated = applyDelta(structuredClone(oldObj), delta);
107
+ // 2. Apply the atom to produce the new state
108
+ const updated = applyAtom(structuredClone(oldObj), atom);
109
109
 
110
- // 3. Revert the delta to restore the original state
111
- const reverted = revertDelta(structuredClone(updated), delta);
110
+ // 3. Revert the atom to restore the original state
111
+ const reverted = revertAtom(structuredClone(updated), atom);
112
112
  ```
113
113
 
114
- That's it. `delta` is a plain JSON object you can store in a database, send over HTTP, or consume in any language.
114
+ That's it. `atom` is a plain JSON object you can store in a database, send over HTTP, or consume in any language.
115
115
 
116
116
  ## Installation
117
117
 
@@ -121,58 +121,58 @@ npm install json-diff-ts
121
121
 
122
122
  ```typescript
123
123
  // ESM / TypeScript
124
- import { diffDelta, applyDelta, revertDelta } from 'json-diff-ts';
124
+ import { diffAtom, applyAtom, revertAtom } from 'json-diff-ts';
125
125
 
126
126
  // CommonJS
127
- const { diffDelta, applyDelta, revertDelta } = require('json-diff-ts');
127
+ const { diffAtom, applyAtom, revertAtom } = require('json-diff-ts');
128
128
  ```
129
129
 
130
- ## What is JSON Delta?
130
+ ## What is JSON Atom?
131
131
 
132
- [JSON Delta](https://github.com/ltwlf/json-delta-format) is a specification for representing atomic changes to JSON documents. json-diff-ts is the originating implementation from which the spec was derived.
132
+ [JSON Atom](https://github.com/ltwlf/json-atom-format) is a specification for representing atomic changes to JSON documents. json-diff-ts is the originating implementation from which the spec was derived.
133
133
 
134
134
  ```text
135
- json-delta-format (specification)
135
+ json-atom-format (specification)
136
136
  ├── json-diff-ts (TypeScript implementation) ← this package
137
- └── json-delta-py (Python implementation)
137
+ └── json-atom-py (Python implementation)
138
138
  ```
139
139
 
140
- The specification defines the wire format. Each language implementation produces and consumes compatible deltas.
140
+ The specification defines the wire format. Each language implementation produces and consumes compatible atoms.
141
141
 
142
- A delta is a self-describing JSON document you can store, transmit, and consume in any language:
142
+ An atom is a self-describing JSON document you can store, transmit, and consume in any language:
143
143
 
144
144
  - **Three operations** -- `add`, `remove`, `replace`. Nothing else to learn.
145
145
  - **JSONPath-based paths** -- `$.items[?(@.id==1)].name` identifies elements by key, not index.
146
146
  - **Reversible by default** -- every `replace` and `remove` includes `oldValue` for undo.
147
- - **Self-identifying** -- the `format` field makes deltas discoverable without external context.
147
+ - **Self-identifying** -- the `format` field makes atoms discoverable without external context.
148
148
  - **Extension-friendly** -- unknown properties are preserved; `x_`-prefixed properties are future-safe.
149
149
 
150
- ### JSON Delta vs JSON Patch (RFC 6902)
150
+ ### JSON Atom vs JSON Patch (RFC 6902)
151
151
 
152
152
  JSON Patch uses JSON Pointer paths like `/items/0` that reference array elements by index. When an element is inserted at position 0, every subsequent path shifts -- `/items/1` now points to what was `/items/0`. This makes stored patches unreliable for JSON change tracking, audit logs, or undo/redo across time.
153
153
 
154
- JSON Delta uses JSONPath filter expressions like `$.items[?(@.id==1)]` that identify elements by a stable key. The path stays valid regardless of insertions, deletions, or reordering.
154
+ JSON Atom uses JSONPath filter expressions like `$.items[?(@.id==1)]` that identify elements by a stable key. The path stays valid regardless of insertions, deletions, or reordering.
155
155
 
156
- | | JSON Delta | JSON Patch (RFC 6902) |
156
+ | | JSON Atom | JSON Patch (RFC 6902) |
157
157
  | --- | --- | --- |
158
158
  | Path syntax | JSONPath (`$.items[?(@.id==1)]`) | JSON Pointer (`/items/0`) |
159
159
  | Array identity | Key-based -- survives reorder | Index-based -- breaks on insert/delete |
160
160
  | Reversibility | Built-in `oldValue` | Not supported |
161
161
  | Self-describing | `format` field in envelope | No envelope |
162
- | Specification | [json-delta-format](https://github.com/ltwlf/json-delta-format) | [RFC 6902](https://tools.ietf.org/html/rfc6902) |
162
+ | Specification | [json-atom-format](https://github.com/ltwlf/json-atom-format) | [RFC 6902](https://tools.ietf.org/html/rfc6902) |
163
163
 
164
164
  ---
165
165
 
166
- ## JSON Delta API
166
+ ## JSON Atom API
167
167
 
168
- ### `diffDelta` -- Compute a Delta
168
+ ### `diffAtom` -- Compute an Atom
169
169
 
170
170
  ```typescript
171
- const delta = diffDelta(
171
+ const atom = diffAtom(
172
172
  { user: { name: 'Alice', role: 'viewer' } },
173
173
  { user: { name: 'Alice', role: 'admin' } }
174
174
  );
175
- // delta.operations → [{ op: 'replace', path: '$.user.role', value: 'admin', oldValue: 'viewer' }]
175
+ // atom.operations → [{ op: 'replace', path: '$.user.role', value: 'admin', oldValue: 'viewer' }]
176
176
  ```
177
177
 
178
178
  #### Keyed Arrays
@@ -180,12 +180,12 @@ const delta = diffDelta(
180
180
  Match array elements by identity key. Filter paths use canonical typed literals per the spec:
181
181
 
182
182
  ```typescript
183
- const delta = diffDelta(
183
+ const atom = diffAtom(
184
184
  { users: [{ id: 1, role: 'viewer' }, { id: 2, role: 'editor' }] },
185
185
  { users: [{ id: 1, role: 'admin' }, { id: 2, role: 'editor' }] },
186
186
  { arrayIdentityKeys: { users: 'id' } }
187
187
  );
188
- // delta.operations → [{ op: 'replace', path: '$.users[?(@.id==1)].role', value: 'admin', oldValue: 'viewer' }]
188
+ // atom.operations → [{ op: 'replace', path: '$.users[?(@.id==1)].role', value: 'admin', oldValue: 'viewer' }]
189
189
  ```
190
190
 
191
191
  #### Non-reversible Mode
@@ -193,140 +193,140 @@ const delta = diffDelta(
193
193
  Omit `oldValue` fields when you don't need undo:
194
194
 
195
195
  ```typescript
196
- const delta = diffDelta(source, target, { reversible: false });
196
+ const atom = diffAtom(source, target, { reversible: false });
197
197
  ```
198
198
 
199
- ### `applyDelta` -- Apply a Delta
199
+ ### `applyAtom` -- Apply an Atom
200
200
 
201
201
  Applies operations sequentially. Always use the return value (required for root-level replacements):
202
202
 
203
203
  ```typescript
204
- const result = applyDelta(structuredClone(source), delta);
204
+ const result = applyAtom(structuredClone(source), atom);
205
205
  ```
206
206
 
207
- ### `revertDelta` -- Revert a Delta
207
+ ### `revertAtom` -- Revert an Atom
208
208
 
209
209
  Computes the inverse and applies it. Requires `oldValue` on all `replace` and `remove` operations:
210
210
 
211
211
  ```typescript
212
- const original = revertDelta(structuredClone(target), delta);
212
+ const original = revertAtom(structuredClone(target), atom);
213
213
  ```
214
214
 
215
- ### `invertDelta` -- Compute the Inverse
215
+ ### `invertAtom` -- Compute the Inverse
216
216
 
217
- Returns a new delta that undoes the original (spec Section 9.2):
217
+ Returns a new atom that undoes the original (spec Section 9.2):
218
218
 
219
219
  ```typescript
220
- const inverse = invertDelta(delta);
220
+ const inverse = invertAtom(atom);
221
221
  // add ↔ remove, replace swaps value/oldValue, order reversed
222
222
  ```
223
223
 
224
- ### `validateDelta` -- Validate Structure
224
+ ### `validateAtom` -- Validate Structure
225
225
 
226
226
  ```typescript
227
- const { valid, errors } = validateDelta(maybeDelta);
227
+ const { valid, errors } = validateAtom(maybeAtom);
228
228
  ```
229
229
 
230
230
  ### API Reference
231
231
 
232
232
  | Function | Signature | Description |
233
233
  | --- | --- | --- |
234
- | `diffDelta` | `(oldObj, newObj, options?) => IJsonDelta` | Compute a canonical JSON Delta |
235
- | `applyDelta` | `(obj, delta) => any` | Apply a delta sequentially. Returns the result |
236
- | `revertDelta` | `(obj, delta) => any` | Revert a reversible delta |
237
- | `invertDelta` | `(delta) => IJsonDelta` | Compute the inverse delta |
238
- | `validateDelta` | `(delta) => { valid, errors }` | Structural validation |
239
- | `toDelta` | `(changeset, options?) => IJsonDelta` | Bridge: v4 changeset to JSON Delta |
240
- | `fromDelta` | `(delta) => IAtomicChange[]` | Bridge: JSON Delta to v4 atomic changes |
241
- | `squashDeltas` | `(source, deltas, options?) => IJsonDelta` | Compact multiple deltas into one net-effect delta |
242
- | `deltaMap` | `(delta, fn) => IJsonDelta` | Transform each operation in a delta |
243
- | `deltaStamp` | `(delta, extensions) => IJsonDelta` | Set extension properties on all operations |
244
- | `deltaGroupBy` | `(delta, keyFn) => Record<string, IJsonDelta>` | Group operations into sub-deltas |
245
- | `operationSpecDict` | `(op) => IDeltaOperation` | Strip extension properties from operation |
234
+ | `diffAtom` | `(oldObj, newObj, options?) => IJsonAtom` | Compute a canonical JSON Atom |
235
+ | `applyAtom` | `(obj, atom) => any` | Apply an atom sequentially. Returns the result |
236
+ | `revertAtom` | `(obj, atom) => any` | Revert a reversible atom |
237
+ | `invertAtom` | `(atom) => IJsonAtom` | Compute the inverse atom |
238
+ | `validateAtom` | `(atom) => { valid, errors }` | Structural validation |
239
+ | `toAtom` | `(changeset, options?) => IJsonAtom` | Bridge: v4 changeset to JSON Atom |
240
+ | `fromAtom` | `(atom) => IAtomicChange[]` | Bridge: JSON Atom to v4 atomic changes |
241
+ | `squashAtoms` | `(source, atoms, options?) => IJsonAtom` | Compact multiple atoms into one net-effect atom |
242
+ | `atomMap` | `(atom, fn) => IJsonAtom` | Transform each operation in an atom |
243
+ | `atomStamp` | `(atom, extensions) => IJsonAtom` | Set extension properties on all operations |
244
+ | `atomGroupBy` | `(atom, keyFn) => Record<string, IJsonAtom>` | Group operations into sub-atoms |
245
+ | `operationSpecDict` | `(op) => IAtomOperation` | Strip extension properties from operation |
246
246
  | `operationExtensions` | `(op) => Record<string, any>` | Get extension properties from operation |
247
- | `deltaSpecDict` | `(delta) => IJsonDelta` | Strip all extensions from delta |
248
- | `deltaExtensions` | `(delta) => Record<string, any>` | Get envelope extensions from delta |
247
+ | `atomSpecDict` | `(atom) => IJsonAtom` | Strip all extensions from atom |
248
+ | `atomExtensions` | `(atom) => Record<string, any>` | Get envelope extensions from atom |
249
249
  | `leafProperty` | `(op) => string \| null` | Terminal property name from operation path |
250
250
 
251
- ### DeltaOptions
251
+ ### AtomOptions
252
252
 
253
253
  Extends the base `Options` interface:
254
254
 
255
255
  ```typescript
256
- interface DeltaOptions extends Options {
256
+ interface AtomOptions extends Options {
257
257
  reversible?: boolean; // Include oldValue for undo. Default: true
258
258
  arrayIdentityKeys?: Record<string, string | FunctionKey>;
259
259
  keysToSkip?: readonly string[];
260
260
  }
261
261
  ```
262
262
 
263
- ### Delta Workflow Helpers
263
+ ### Atom Workflow Helpers
264
264
 
265
- Transform, inspect, and compact deltas for workflow automation.
265
+ Transform, inspect, and compact atoms for workflow automation.
266
266
 
267
- #### `squashDeltas` -- Compact Multiple Deltas
267
+ #### `squashAtoms` -- Compact Multiple Atoms
268
268
 
269
- Combine a sequence of deltas into a single net-effect delta. Useful for compacting audit logs or collapsing undo history:
269
+ Combine a sequence of atoms into a single net-effect atom. Useful for compacting audit logs or collapsing undo history:
270
270
 
271
271
  ```typescript
272
- import { diffDelta, applyDelta, squashDeltas } from 'json-diff-ts';
272
+ import { diffAtom, applyAtom, squashAtoms } from 'json-diff-ts';
273
273
 
274
274
  const source = { name: 'Alice', role: 'viewer' };
275
- const d1 = diffDelta(source, { name: 'Bob', role: 'viewer' });
276
- const d2 = diffDelta({ name: 'Bob', role: 'viewer' }, { name: 'Bob', role: 'admin' });
275
+ const d1 = diffAtom(source, { name: 'Bob', role: 'viewer' });
276
+ const d2 = diffAtom({ name: 'Bob', role: 'viewer' }, { name: 'Bob', role: 'admin' });
277
277
 
278
- const squashed = squashDeltas(source, [d1, d2]);
278
+ const squashed = squashAtoms(source, [d1, d2]);
279
279
  // squashed.operations => [
280
280
  // { op: 'replace', path: '$.name', value: 'Bob', oldValue: 'Alice' },
281
281
  // { op: 'replace', path: '$.role', value: 'admin', oldValue: 'viewer' }
282
282
  // ]
283
283
 
284
- // Verify: applying the squashed delta equals applying both sequentially
285
- const result = applyDelta(structuredClone(source), squashed);
284
+ // Verify: applying the squashed atom equals applying both sequentially
285
+ const result = applyAtom(structuredClone(source), squashed);
286
286
  // result => { name: 'Bob', role: 'admin' }
287
287
  ```
288
288
 
289
289
  Options: `reversible`, `arrayIdentityKeys`, `target` (pre-computed final state), `verifyTarget` (default: true).
290
290
 
291
- #### `deltaMap` / `deltaStamp` / `deltaGroupBy` -- Delta Transformations
291
+ #### `atomMap` / `atomStamp` / `atomGroupBy` -- Atom Transformations
292
292
 
293
- All transforms are immutable — they return new deltas without modifying the original:
293
+ All transforms are immutable — they return new atoms without modifying the original:
294
294
 
295
295
  ```typescript
296
- import { diffDelta, deltaMap, deltaStamp, deltaGroupBy } from 'json-diff-ts';
296
+ import { diffAtom, atomMap, atomStamp, atomGroupBy } from 'json-diff-ts';
297
297
 
298
- const delta = diffDelta(
298
+ const atom = diffAtom(
299
299
  { name: 'Alice', age: 30, role: 'viewer' },
300
300
  { name: 'Bob', age: 31, status: 'active' }
301
301
  );
302
302
 
303
303
  // Stamp metadata onto every operation
304
- const stamped = deltaStamp(delta, { x_author: 'system', x_ts: Date.now() });
304
+ const stamped = atomStamp(atom, { x_author: 'system', x_ts: Date.now() });
305
305
 
306
306
  // Transform operations
307
- const prefixed = deltaMap(delta, (op) => ({
307
+ const prefixed = atomMap(atom, (op) => ({
308
308
  ...op,
309
309
  path: op.path.replace('$', '$.data'),
310
310
  }));
311
311
 
312
312
  // Group by operation type
313
- const groups = deltaGroupBy(delta, (op) => op.op);
314
- // groups => { replace: IJsonDelta, add: IJsonDelta, remove: IJsonDelta }
313
+ const groups = atomGroupBy(atom, (op) => op.op);
314
+ // groups => { replace: IJsonAtom, add: IJsonAtom, remove: IJsonAtom }
315
315
  ```
316
316
 
317
- #### `operationSpecDict` / `deltaSpecDict` -- Spec Introspection
317
+ #### `operationSpecDict` / `atomSpecDict` -- Spec Introspection
318
318
 
319
319
  Separate spec-defined fields from extension properties:
320
320
 
321
321
  ```typescript
322
- import { operationSpecDict, operationExtensions, deltaSpecDict } from 'json-diff-ts';
322
+ import { operationSpecDict, operationExtensions, atomSpecDict } from 'json-diff-ts';
323
323
 
324
324
  const op = { op: 'replace', path: '$.name', value: 'Bob', x_author: 'system' };
325
325
  operationSpecDict(op); // { op: 'replace', path: '$.name', value: 'Bob' }
326
326
  operationExtensions(op); // { x_author: 'system' }
327
327
 
328
- // Strip all extensions from a delta
329
- const clean = deltaSpecDict(delta);
328
+ // Strip all extensions from an atom
329
+ const clean = atomSpecDict(atom);
330
330
  ```
331
331
 
332
332
  #### `leafProperty` -- Path Introspection
@@ -385,15 +385,15 @@ const all = comparisonToFlatList(result, { includeUnchanged: true });
385
385
 
386
386
  ### Audit Log
387
387
 
388
- Store every change to a document as a reversible delta. Each entry records who changed what, when, and can be replayed or reverted independently -- a complete JSON change tracking system:
388
+ Store every change to a document as a reversible atom. Each entry records who changed what, when, and can be replayed or reverted independently -- a complete JSON change tracking system:
389
389
 
390
390
  ```typescript
391
- import { diffDelta, applyDelta, revertDelta, IJsonDelta } from 'json-diff-ts';
391
+ import { diffAtom, applyAtom, revertAtom, IJsonAtom } from 'json-diff-ts';
392
392
 
393
393
  interface AuditEntry {
394
394
  timestamp: string;
395
395
  userId: string;
396
- delta: IJsonDelta;
396
+ atom: IJsonAtom;
397
397
  }
398
398
 
399
399
  const auditLog: AuditEntry[] = [];
@@ -407,13 +407,13 @@ let doc = {
407
407
  };
408
408
 
409
409
  function updateDocument(newDoc: typeof doc, userId: string) {
410
- const delta = diffDelta(doc, newDoc, {
410
+ const atom = diffAtom(doc, newDoc, {
411
411
  arrayIdentityKeys: { items: 'id' },
412
412
  });
413
413
 
414
- if (delta.operations.length > 0) {
415
- auditLog.push({ timestamp: new Date().toISOString(), userId, delta });
416
- doc = applyDelta(structuredClone(doc), delta);
414
+ if (atom.operations.length > 0) {
415
+ auditLog.push({ timestamp: new Date().toISOString(), userId, atom });
416
+ doc = applyAtom(structuredClone(doc), atom);
417
417
  }
418
418
 
419
419
  return doc;
@@ -423,7 +423,7 @@ function updateDocument(newDoc: typeof doc, userId: string) {
423
423
  function undo(): typeof doc {
424
424
  const last = auditLog.pop();
425
425
  if (!last) return doc;
426
- doc = revertDelta(structuredClone(doc), last.delta);
426
+ doc = revertAtom(structuredClone(doc), last.atom);
427
427
  return doc;
428
428
  }
429
429
 
@@ -432,50 +432,50 @@ updateDocument(
432
432
  { ...doc, status: 'active', items: [{ id: 1, task: 'Design', done: true }, ...doc.items.slice(1)] },
433
433
  'alice'
434
434
  );
435
- // auditLog[0].delta.operations =>
435
+ // auditLog[0].atom.operations =>
436
436
  // [
437
437
  // { op: 'replace', path: '$.status', value: 'active', oldValue: 'draft' },
438
438
  // { op: 'replace', path: '$.items[?(@.id==1)].done', value: true, oldValue: false }
439
439
  // ]
440
440
  ```
441
441
 
442
- Because every delta is self-describing JSON, your audit log is queryable, storable in any database, and readable from any language.
442
+ Because every atom is self-describing JSON, your audit log is queryable, storable in any database, and readable from any language.
443
443
 
444
444
  ### Undo / Redo Stack
445
445
 
446
- Build undo/redo for any JSON state object. Deltas are small (only changed fields), reversible, and serializable:
446
+ Build undo/redo for any JSON state object. Atoms are small (only changed fields), reversible, and serializable:
447
447
 
448
448
  ```typescript
449
- import { diffDelta, applyDelta, revertDelta, IJsonDelta } from 'json-diff-ts';
449
+ import { diffAtom, applyAtom, revertAtom, IJsonAtom } from 'json-diff-ts';
450
450
 
451
451
  class UndoManager<T extends object> {
452
- private undoStack: IJsonDelta[] = [];
453
- private redoStack: IJsonDelta[] = [];
452
+ private undoStack: IJsonAtom[] = [];
453
+ private redoStack: IJsonAtom[] = [];
454
454
 
455
455
  constructor(private state: T) {}
456
456
 
457
457
  apply(newState: T): T {
458
- const delta = diffDelta(this.state, newState);
459
- if (delta.operations.length === 0) return this.state;
460
- this.undoStack.push(delta);
458
+ const atom = diffAtom(this.state, newState);
459
+ if (atom.operations.length === 0) return this.state;
460
+ this.undoStack.push(atom);
461
461
  this.redoStack = [];
462
- this.state = applyDelta(structuredClone(this.state), delta);
462
+ this.state = applyAtom(structuredClone(this.state), atom);
463
463
  return this.state;
464
464
  }
465
465
 
466
466
  undo(): T {
467
- const delta = this.undoStack.pop();
468
- if (!delta) return this.state;
469
- this.redoStack.push(delta);
470
- this.state = revertDelta(structuredClone(this.state), delta);
467
+ const atom = this.undoStack.pop();
468
+ if (!atom) return this.state;
469
+ this.redoStack.push(atom);
470
+ this.state = revertAtom(structuredClone(this.state), atom);
471
471
  return this.state;
472
472
  }
473
473
 
474
474
  redo(): T {
475
- const delta = this.redoStack.pop();
476
- if (!delta) return this.state;
477
- this.undoStack.push(delta);
478
- this.state = applyDelta(structuredClone(this.state), delta);
475
+ const atom = this.redoStack.pop();
476
+ if (!atom) return this.state;
477
+ this.undoStack.push(atom);
478
+ this.state = applyAtom(structuredClone(this.state), atom);
479
479
  return this.state;
480
480
  }
481
481
  }
@@ -483,55 +483,55 @@ class UndoManager<T extends object> {
483
483
 
484
484
  ### Data Synchronization
485
485
 
486
- Send only what changed between client and server. Deltas are compact -- a single field change in a 10KB document produces a few bytes of delta, making state synchronization efficient over the wire:
486
+ Send only what changed between client and server. Atoms are compact -- a single field change in a 10KB document produces a few bytes of atom, making state synchronization efficient over the wire:
487
487
 
488
488
  ```typescript
489
- import { diffDelta, applyDelta, validateDelta } from 'json-diff-ts';
489
+ import { diffAtom, applyAtom, validateAtom } from 'json-diff-ts';
490
490
 
491
- // Client side: compute and send delta
492
- const delta = diffDelta(localState, updatedState, {
491
+ // Client side: compute and send atom
492
+ const atom = diffAtom(localState, updatedState, {
493
493
  arrayIdentityKeys: { records: 'id' },
494
494
  });
495
495
  await fetch('/api/sync', {
496
496
  method: 'POST',
497
- body: JSON.stringify(delta),
497
+ body: JSON.stringify(atom),
498
498
  });
499
499
 
500
500
  // Server side: validate and apply
501
- const result = validateDelta(req.body);
501
+ const result = validateAtom(req.body);
502
502
  if (!result.valid) return res.status(400).json(result.errors);
503
503
  // ⚠️ In production, sanitize paths/values to prevent prototype pollution
504
504
  // (e.g. reject paths containing "__proto__" or "constructor")
505
- currentState = applyDelta(structuredClone(currentState), req.body);
505
+ currentState = applyAtom(structuredClone(currentState), req.body);
506
506
  ```
507
507
 
508
508
  ---
509
509
 
510
- ## Bridge: v4 Changeset <-> JSON Delta
510
+ ## Bridge: v4 Changeset <-> JSON Atom
511
511
 
512
- Convert between the legacy internal format and JSON Delta:
512
+ Convert between the legacy internal format and JSON Atom:
513
513
 
514
514
  ```typescript
515
- import { diff, toDelta, fromDelta, unatomizeChangeset } from 'json-diff-ts';
515
+ import { diff, toAtom, fromAtom, unatomizeChangeset } from 'json-diff-ts';
516
516
 
517
- // v4 changeset → JSON Delta
517
+ // v4 changeset → JSON Atom
518
518
  const changeset = diff(source, target, { arrayIdentityKeys: { items: 'id' } });
519
- const delta = toDelta(changeset);
519
+ const atom = toAtom(changeset);
520
520
 
521
- // JSON Delta → v4 atomic changes
522
- const atoms = fromDelta(delta);
521
+ // JSON Atom → v4 atomic changes
522
+ const atoms = fromAtom(atom);
523
523
 
524
524
  // v4 atomic changes → hierarchical changeset (if needed)
525
525
  const cs = unatomizeChangeset(atoms);
526
526
  ```
527
527
 
528
- **Note:** `toDelta` is a best-effort bridge. Filter literals are always string-quoted (e.g., `[?(@.id=='42')]` instead of canonical `[?(@.id==42)]`). Use `diffDelta()` for fully canonical output.
528
+ **Note:** `toAtom` is a best-effort bridge. Filter literals are always string-quoted (e.g., `[?(@.id=='42')]` instead of canonical `[?(@.id==42)]`). Use `diffAtom()` for fully canonical output.
529
529
 
530
530
  ---
531
531
 
532
532
  ## Legacy Changeset API (v4 Compatibility)
533
533
 
534
- All v4 APIs remain fully supported. Existing code continues to work without changes. For new projects, prefer the JSON Delta API above.
534
+ All v4 APIs remain fully supported. Existing code continues to work without changes. For new projects, prefer the JSON Atom API above.
535
535
 
536
536
  ### `diff`
537
537
 
@@ -659,8 +659,8 @@ interface Options {
659
659
  ## Migration from v4
660
660
 
661
661
  1. **No action required** -- all v4 APIs work identically in v5.
662
- 2. **Adopt JSON Delta** -- use `diffDelta()` / `applyDelta()` for new code.
663
- 3. **Bridge existing data** -- `toDelta()` / `fromDelta()` for interop with stored v4 changesets.
662
+ 2. **Adopt JSON Atom** -- use `diffAtom()` / `applyAtom()` for new code.
663
+ 3. **Bridge existing data** -- `toAtom()` / `fromAtom()` for interop with stored v4 changesets.
664
664
  4. **Rename `embeddedObjKeys` to `arrayIdentityKeys`** -- the old name still works, but `arrayIdentityKeys` is the preferred name going forward.
665
665
  5. Both formats coexist. No forced migration.
666
666
 
@@ -675,13 +675,13 @@ interface Options {
675
675
  | Dependencies | Zero | Few | Many | Varies |
676
676
  | ESM Support | Native | CJS only | CJS only | Varies |
677
677
  | Array Identity | Key-based | Index only | Configurable | Index only |
678
- | Wire Format | JSON Delta (standardized) | Proprietary | Proprietary | JSON Pointer |
678
+ | Wire Format | JSON Atom (standardized) | Proprietary | Proprietary | JSON Pointer |
679
679
  | Reversibility | Built-in (`oldValue`) | Manual | Plugin | Not built-in |
680
680
 
681
681
  ## FAQ
682
682
 
683
- **Q: How does JSON Delta compare to JSON Patch (RFC 6902)?**
684
- JSON Patch uses JSON Pointer (`/items/0`) for paths, which breaks when array elements are inserted, deleted, or reordered. JSON Delta uses JSONPath filter expressions (`$.items[?(@.id==1)]`) for stable, key-based identity. JSON Delta also supports built-in reversibility via `oldValue`.
683
+ **Q: How does JSON Atom compare to JSON Patch (RFC 6902)?**
684
+ JSON Patch uses JSON Pointer (`/items/0`) for paths, which breaks when array elements are inserted, deleted, or reordered. JSON Atom uses JSONPath filter expressions (`$.items[?(@.id==1)]`) for stable, key-based identity. JSON Atom also supports built-in reversibility via `oldValue`.
685
685
 
686
686
  **Q: Can I use this with React / Vue / Angular?**
687
687
  Yes. json-diff-ts works in any JavaScript runtime -- browsers, Node.js, Deno, Bun, edge workers.
@@ -689,8 +689,8 @@ Yes. json-diff-ts works in any JavaScript runtime -- browsers, Node.js, Deno, Bu
689
689
  **Q: Is it suitable for large objects?**
690
690
  Yes. The library handles large, deeply nested JSON structures efficiently with zero dependencies and a ~6KB gzipped footprint.
691
691
 
692
- **Q: Can I use the v4 API alongside JSON Delta?**
693
- Yes. Both APIs coexist. Use `toDelta()` / `fromDelta()` to convert between formats.
692
+ **Q: Can I use the v4 API alongside JSON Atom?**
693
+ Yes. Both APIs coexist. Use `toAtom()` / `fromAtom()` to convert between formats.
694
694
 
695
695
  **Q: What about arrays of primitives?**
696
696
  Use `$value` as the identity key: `{ arrayIdentityKeys: { tags: '$value' } }`. Elements are matched by value identity.
@@ -700,14 +700,14 @@ Use `$value` as the identity key: `{ arrayIdentityKeys: { tags: '$value' } }`. E
700
700
  ## Release Notes
701
701
 
702
702
  - **v5.0.0-alpha.2:**
703
- - Delta workflow helpers: `squashDeltas`, `deltaMap`, `deltaStamp`, `deltaGroupBy`
704
- - Delta/operation introspection: `operationSpecDict`, `operationExtensions`, `deltaSpecDict`, `deltaExtensions`, `leafProperty`
703
+ - Atom workflow helpers: `squashAtoms`, `atomMap`, `atomStamp`, `atomGroupBy`
704
+ - Atom/operation introspection: `operationSpecDict`, `operationExtensions`, `atomSpecDict`, `atomExtensions`, `leafProperty`
705
705
  - Comparison serialization: `comparisonToDict`, `comparisonToFlatList`
706
706
 
707
707
  - **v5.0.0-alpha.0:**
708
- - JSON Delta API: `diffDelta`, `applyDelta`, `revertDelta`, `invertDelta`, `toDelta`, `fromDelta`, `validateDelta`
708
+ - JSON Atom API: `diffAtom`, `applyAtom`, `revertAtom`, `invertAtom`, `toAtom`, `fromAtom`, `validateAtom`
709
709
  - Canonical path production with typed filter literals
710
- - Conformance with the [JSON Delta Specification](https://github.com/ltwlf/json-delta-format) v0
710
+ - Conformance with the [JSON Atom Specification](https://github.com/ltwlf/json-atom-format) v0
711
711
  - Renamed `embeddedObjKeys` to `arrayIdentityKeys` (old name still works as deprecated alias)
712
712
  - All v4 APIs preserved unchanged
713
713