tinybase-mergeable-history 1.0.0 → 1.1.0

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
@@ -1,5 +1,11 @@
1
1
  # tinybase-mergeable-history
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/tinybase-mergeable-history)](https://www.npmjs.com/package/tinybase-mergeable-history)
4
+ [![npm downloads](https://img.shields.io/npm/dm/tinybase-mergeable-history)](https://www.npmjs.com/package/tinybase-mergeable-history)
5
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/tinybase-mergeable-history)](https://bundlephobia.com/package/tinybase-mergeable-history)
6
+ [![types](https://img.shields.io/npm/types/tinybase-mergeable-history)](https://www.npmjs.com/package/tinybase-mergeable-history)
7
+ [![license](https://img.shields.io/npm/l/tinybase-mergeable-history)](./LICENSE)
8
+
3
9
  Persistent, syncable, **CRDT-aware** undo/redo history for [TinyBase](https://tinybase.org).
4
10
 
5
11
  TinyBase's built-in [`Checkpoints`](https://tinybase.org/api/checkpoints/) keep an undo stack in memory: it is lost on reload, scoped to a single client, and unaware of `MergeableStore` merges. `tinybase-mergeable-history` takes the opposite approach — **history is stored as data inside the store itself** (in a `_history` table). Because the history lives in the store, it travels with:
@@ -19,6 +25,7 @@ TinyBase's built-in [`Checkpoints`](https://tinybase.org/api/checkpoints/) keep
19
25
  | MergeableStore / CRDT-aware | ❌ | ✅ |
20
26
  | Per-action author attribution | ❌ | ✅ (`getAuthor`) |
21
27
  | Linear undo/redo | ✅ | ✅ |
28
+ | Undo/redo any action by id | ❌ | ✅ (`undo(id)`, `redo(id)`) |
22
29
  | Count- and time-based pruning | ❌ | ✅ (`maxActions`, `maxAge`) |
23
30
 
24
31
  ## Install
@@ -94,6 +101,7 @@ const author = history.getStore().getCell('_history', actionId!, 'author');
94
101
  | `getAuthor` | `() => ''` | returns an identifier for who is making the action; stored on the action's `author` cell |
95
102
  | `generateId` | built-in | action id generator |
96
103
  | `withBypass` | `fn => fn()` | wrap internal writes (e.g. to bypass your listeners) |
104
+ | `clearRedoOnAction` | `true` | whether `action()` drops every undone action first (linear redo-stack clearing); set `false` for an id-addressed ledger |
97
105
 
98
106
  ### `History`
99
107
 
@@ -101,6 +109,9 @@ const author = history.getStore().getCell('_history', actionId!, 'author');
101
109
  - `goBackward()` / `goForward()` — undo / redo
102
110
  - `canGoBackward()` / `canGoForward(): boolean`
103
111
  - `getActionIds(): [backward[], current, forward[]]`
112
+ - `undo(id)` / `redo(id): boolean` — undo / redo one action by id, guarded (see below)
113
+ - `canUndo(id)` / `canRedo(id): boolean`
114
+ - `forget(id): boolean` — delete an undone action's row so it can no longer be redone
104
115
  - `setMaxActions(max)` / `setMaxAge(ms)` — tune retention
105
116
  - `cleanup()` — prune now
106
117
  - `addCleanupListener(listener)` / `delListener(id)` — observe pruning (cascade GC)
@@ -122,6 +133,41 @@ const tablesSchema = {
122
133
 
123
134
  The history table schema includes the `author` cell used for attribution.
124
135
 
136
+ ## Undo any action by id
137
+
138
+ `goBackward()` / `goForward()` walk a linear stack: only the newest live action can be undone. In a shared ledger — an activity feed where any entry has its own Undo — you need to reverse an arbitrary action while newer ones stay applied. `undo(id)` and `redo(id)` do that.
139
+
140
+ ```ts
141
+ const added = history.action(() => store.setRow('files', 'f1', { by: 'maya' }));
142
+ history.action(() => store.setRow('files', 'f2', { by: 'alex' }));
143
+
144
+ history.undo(added!); // f1 is gone, f2 stays — the row is flagged `undone`
145
+ history.redo(added!); // f1 is back
146
+ ```
147
+
148
+ ### The overwrite guard
149
+
150
+ Reversing an action out of order is only sound when nothing newer touched the same cells. `undo(id)` therefore checks that the store still holds the action's **after** side — every cell and value the action wrote still has the value it wrote — and refuses (returns `false`) otherwise. `redo(id)` checks the **before** side the same way. `canUndo(id)` / `canRedo(id)` expose the check so a UI can hide the button.
151
+
152
+ ```ts
153
+ const a = history.action(() => store.setCell('photo', 'p1', 'brightness', 10));
154
+ const b = history.action(() => store.setCell('photo', 'p1', 'brightness', 20));
155
+
156
+ history.undo(a!); // false — b overwrote the cell; undoing a would clobber 20
157
+ history.undo(b!); // true — brightness is 10 again
158
+ history.undo(a!); // true — now a is the live writer of that cell
159
+ ```
160
+
161
+ The linear `goBackward()` / `goForward()` are deliberately **not** guarded: they reverse the newest action whatever the store holds now.
162
+
163
+ ### Redo stack vs. ledger
164
+
165
+ By default `action()` deletes every undone action before recording a new one — the familiar "a new action clears redo". For an id-addressed ledger that is wrong: an undone entry must stay redoable until you decide otherwise. Pass `clearRedoOnAction: false`, and remove undone entries explicitly with `forget(id)` (refuses a live action — undo and forget are two separate steps), or let `cleanup()` age them out.
166
+
167
+ ```ts
168
+ const history = createHistory(store, { clearRedoOnAction: false });
169
+ ```
170
+
125
171
  ## How it works
126
172
 
127
173
  Every `action()` runs inside a TinyBase transaction. A `willFinishTransaction` listener captures the transaction's cell and value changes and writes a single row to the history table holding the forward and reverse deltas, an `undone` flag, and the action's `author`. Undo and redo are themselves ordinary store mutations that flip `undone` and re-apply the relevant delta — which is exactly why they persist, sync, and merge like any other data.
package/build/index.d.mts CHANGED
@@ -8,6 +8,15 @@ interface HistoryOptions {
8
8
  generateId?: () => string;
9
9
  getAuthor?: () => string;
10
10
  withBypass?: (fn: () => void) => void;
11
+ /**
12
+ * Whether `action()` first drops every undone action — the linear
13
+ * "a new action clears the redo stack" rule. Defaults to `true`.
14
+ *
15
+ * Set `false` for a ledger addressed by id (`undo(id)` / `redo(id)`), where
16
+ * an undone action must stay redoable until it is explicitly `forget`-ed
17
+ * or pruned by `cleanup()`.
18
+ */
19
+ clearRedoOnAction?: boolean;
11
20
  }
12
21
  interface History {
13
22
  action(mutate: () => void): Id | undefined;
@@ -16,6 +25,30 @@ interface History {
16
25
  canGoBackward(): boolean;
17
26
  canGoForward(): boolean;
18
27
  getActionIds(): [Id[], Id | undefined, Id[]];
28
+ /**
29
+ * Undo one action by id — any action, not just the newest. Guarded: the
30
+ * store must still hold the action's "after" side of its delta (every cell
31
+ * and value it wrote still has the value it wrote), otherwise a later
32
+ * action overwrote it and reversing it would clobber that newer state.
33
+ * Returns whether the undo was applied.
34
+ */
35
+ undo(actionId: Id): boolean;
36
+ /**
37
+ * Redo one undone action by id. Guarded the same way on the action's
38
+ * "before" side. Returns whether the redo was applied.
39
+ */
40
+ redo(actionId: Id): boolean;
41
+ /** Whether `undo(actionId)` would apply right now. */
42
+ canUndo(actionId: Id): boolean;
43
+ /** Whether `redo(actionId)` would apply right now. */
44
+ canRedo(actionId: Id): boolean;
45
+ /**
46
+ * Delete an UNDONE action's row from the history table, so it can no
47
+ * longer be redone. Undo itself never deletes; this is the separate,
48
+ * explicit step. Returns whether a row was removed (a live or unknown
49
+ * action is left alone).
50
+ */
51
+ forget(actionId: Id): boolean;
19
52
  setMaxActions(max: number): this;
20
53
  setMaxAge(ms: number): this;
21
54
  cleanup(): this;
package/build/index.d.ts CHANGED
@@ -8,6 +8,15 @@ interface HistoryOptions {
8
8
  generateId?: () => string;
9
9
  getAuthor?: () => string;
10
10
  withBypass?: (fn: () => void) => void;
11
+ /**
12
+ * Whether `action()` first drops every undone action — the linear
13
+ * "a new action clears the redo stack" rule. Defaults to `true`.
14
+ *
15
+ * Set `false` for a ledger addressed by id (`undo(id)` / `redo(id)`), where
16
+ * an undone action must stay redoable until it is explicitly `forget`-ed
17
+ * or pruned by `cleanup()`.
18
+ */
19
+ clearRedoOnAction?: boolean;
11
20
  }
12
21
  interface History {
13
22
  action(mutate: () => void): Id | undefined;
@@ -16,6 +25,30 @@ interface History {
16
25
  canGoBackward(): boolean;
17
26
  canGoForward(): boolean;
18
27
  getActionIds(): [Id[], Id | undefined, Id[]];
28
+ /**
29
+ * Undo one action by id — any action, not just the newest. Guarded: the
30
+ * store must still hold the action's "after" side of its delta (every cell
31
+ * and value it wrote still has the value it wrote), otherwise a later
32
+ * action overwrote it and reversing it would clobber that newer state.
33
+ * Returns whether the undo was applied.
34
+ */
35
+ undo(actionId: Id): boolean;
36
+ /**
37
+ * Redo one undone action by id. Guarded the same way on the action's
38
+ * "before" side. Returns whether the redo was applied.
39
+ */
40
+ redo(actionId: Id): boolean;
41
+ /** Whether `undo(actionId)` would apply right now. */
42
+ canUndo(actionId: Id): boolean;
43
+ /** Whether `redo(actionId)` would apply right now. */
44
+ canRedo(actionId: Id): boolean;
45
+ /**
46
+ * Delete an UNDONE action's row from the history table, so it can no
47
+ * longer be redone. Undo itself never deletes; this is the separate,
48
+ * explicit step. Returns whether a row was removed (a live or unknown
49
+ * action is left alone).
50
+ */
51
+ forget(actionId: Id): boolean;
19
52
  setMaxActions(max: number): this;
20
53
  setMaxAge(ms: number): this;
21
54
  cleanup(): this;
package/build/index.js CHANGED
@@ -37,6 +37,23 @@ function applyReverse(store, delta, dv) {
37
37
  function applyForward(store, delta, dv) {
38
38
  applyDelta(store, delta, dv, 1);
39
39
  }
40
+ function deltaHolds(store, delta, dv, index) {
41
+ for (const tableId of Object.keys(delta)) {
42
+ for (const rowId of Object.keys(delta[tableId])) {
43
+ for (const cellId of Object.keys(delta[tableId][rowId])) {
44
+ const expected = delta[tableId][rowId][cellId][index];
45
+ const actual = store.getCell(tableId, rowId, cellId) ?? null;
46
+ if (actual !== expected) return false;
47
+ }
48
+ }
49
+ }
50
+ for (const valueId of Object.keys(dv)) {
51
+ const expected = dv[valueId][index];
52
+ const actual = store.getValue(valueId) ?? null;
53
+ if (actual !== expected) return false;
54
+ }
55
+ return true;
56
+ }
40
57
 
41
58
  // src/cleanup.ts
42
59
  function runCleanup(store, tableName, maxActions, maxAge, history, listeners, withBypass) {
@@ -86,7 +103,7 @@ function runCleanup(store, tableName, maxActions, maxAge, history, listeners, wi
86
103
 
87
104
  // src/history.ts
88
105
  var History = class {
89
- constructor(store, tableName, maxActions, maxAge, generateId, getAuthor, bypassFn) {
106
+ constructor(store, tableName, maxActions, maxAge, generateId, getAuthor, bypassFn, clearRedoOnAction) {
90
107
  this.store = store;
91
108
  this.tableName = tableName;
92
109
  this.maxActions = maxActions;
@@ -94,6 +111,7 @@ var History = class {
94
111
  this.generateId = generateId;
95
112
  this.getAuthor = getAuthor;
96
113
  this.bypassFn = bypassFn;
114
+ this.clearRedoOnAction = clearRedoOnAction;
97
115
  this.pendingActionRecorded = false;
98
116
  this.cleanupListeners = /* @__PURE__ */ new Map();
99
117
  this.nextListenerId = 0;
@@ -120,17 +138,19 @@ var History = class {
120
138
  }
121
139
  action(mutate) {
122
140
  const id = this.generateId();
123
- const undoneIds = this.store.getRowIds(this.tableName).filter(
124
- (rowId) => this.store.getCell(this.tableName, rowId, "undone") === 1
125
- );
126
- if (undoneIds.length > 0) {
127
- this.bypassFn(() => {
128
- this.store.transaction(() => {
129
- for (const rowId of undoneIds) {
130
- this.store.delRow(this.tableName, rowId);
131
- }
141
+ if (this.clearRedoOnAction) {
142
+ const undoneIds = this.store.getRowIds(this.tableName).filter(
143
+ (rowId) => this.store.getCell(this.tableName, rowId, "undone") === 1
144
+ );
145
+ if (undoneIds.length > 0) {
146
+ this.bypassFn(() => {
147
+ this.store.transaction(() => {
148
+ for (const rowId of undoneIds) {
149
+ this.store.delRow(this.tableName, rowId);
150
+ }
151
+ });
132
152
  });
133
- });
153
+ }
134
154
  }
135
155
  this.pendingAction = id;
136
156
  this.pendingActionRecorded = false;
@@ -144,35 +164,77 @@ var History = class {
144
164
  this.pendingAction = void 0;
145
165
  return this.pendingActionRecorded ? id : void 0;
146
166
  }
147
- goBackward() {
148
- const [, current] = this.getActionIds();
149
- if (!current) return this;
150
- const row = this.store.getRow(this.tableName, current);
151
- const delta = deserializeDelta(row.delta);
152
- const dv = deserializeDeltaValues(row.dv);
167
+ load(actionId) {
168
+ if (!this.store.hasRow(this.tableName, actionId)) return void 0;
169
+ const row = this.store.getRow(this.tableName, actionId);
170
+ return {
171
+ undone: row.undone === 1,
172
+ delta: deserializeDelta(row.delta),
173
+ dv: deserializeDeltaValues(row.dv)
174
+ };
175
+ }
176
+ reverse(actionId, action) {
153
177
  this.store.transaction(() => {
154
- applyReverse(this.store, delta, dv);
178
+ applyReverse(this.store, action.delta, action.dv);
155
179
  this.bypassFn(() => {
156
- this.store.setCell(this.tableName, current, "undone", 1);
180
+ this.store.setCell(this.tableName, actionId, "undone", 1);
157
181
  });
158
182
  });
159
- return this;
160
183
  }
161
- goForward() {
162
- const [, , forward] = this.getActionIds();
163
- if (forward.length === 0) return this;
164
- const actionId = forward[0];
165
- const row = this.store.getRow(this.tableName, actionId);
166
- const delta = deserializeDelta(row.delta);
167
- const dv = deserializeDeltaValues(row.dv);
184
+ forward(actionId, action) {
168
185
  this.store.transaction(() => {
169
- applyForward(this.store, delta, dv);
186
+ applyForward(this.store, action.delta, action.dv);
170
187
  this.bypassFn(() => {
171
188
  this.store.setCell(this.tableName, actionId, "undone", 0);
172
189
  });
173
190
  });
191
+ }
192
+ goBackward() {
193
+ const [, current] = this.getActionIds();
194
+ if (!current) return this;
195
+ this.reverse(current, this.load(current));
174
196
  return this;
175
197
  }
198
+ goForward() {
199
+ const [, , forward] = this.getActionIds();
200
+ if (forward.length === 0) return this;
201
+ const actionId = forward[0];
202
+ this.forward(actionId, this.load(actionId));
203
+ return this;
204
+ }
205
+ undoable(action) {
206
+ return action !== void 0 && !action.undone && deltaHolds(this.store, action.delta, action.dv, 1);
207
+ }
208
+ redoable(action) {
209
+ return action !== void 0 && action.undone && deltaHolds(this.store, action.delta, action.dv, 0);
210
+ }
211
+ canUndo(actionId) {
212
+ return this.undoable(this.load(actionId));
213
+ }
214
+ canRedo(actionId) {
215
+ return this.redoable(this.load(actionId));
216
+ }
217
+ undo(actionId) {
218
+ const action = this.load(actionId);
219
+ if (!this.undoable(action)) return false;
220
+ this.reverse(actionId, action);
221
+ return true;
222
+ }
223
+ redo(actionId) {
224
+ const action = this.load(actionId);
225
+ if (!this.redoable(action)) return false;
226
+ this.forward(actionId, action);
227
+ return true;
228
+ }
229
+ forget(actionId) {
230
+ if (this.store.getCell(this.tableName, actionId, "undone") !== 1) {
231
+ return false;
232
+ }
233
+ this.bypassFn(() => {
234
+ this.store.delRow(this.tableName, actionId);
235
+ });
236
+ return true;
237
+ }
176
238
  canGoBackward() {
177
239
  const [, current] = this.getActionIds();
178
240
  return current !== void 0;
@@ -261,7 +323,8 @@ function createHistory(store, options) {
261
323
  options?.maxAge ?? THIRTY_DAYS,
262
324
  options?.generateId ?? defaultGenerateId,
263
325
  options?.getAuthor ?? (() => ""),
264
- options?.withBypass ?? ((fn) => fn())
326
+ options?.withBypass ?? ((fn) => fn()),
327
+ options?.clearRedoOnAction ?? true
265
328
  );
266
329
  }
267
330
  var historyTablesSchema = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/delta.ts","../src/cleanup.ts","../src/history.ts","../src/create.ts","../src/schema.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["import type { Store } from 'tinybase';\nimport type { ParsedDelta, ParsedDeltaValues } from './types';\n\nexport function deserializeDelta(json: string): ParsedDelta {\n return JSON.parse(json);\n}\n\nexport function deserializeDeltaValues(json: string): ParsedDeltaValues {\n return JSON.parse(json);\n}\n\nfunction applyDelta(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues,\n index: 0 | 1\n): void {\n for (const tableId of Object.keys(delta)) {\n for (const rowId of Object.keys(delta[tableId])) {\n for (const cellId of Object.keys(delta[tableId][rowId])) {\n const value = delta[tableId][rowId][cellId][index];\n if (value == null) {\n store.delCell(tableId, rowId, cellId, true);\n } else {\n store.setCell(tableId, rowId, cellId, value);\n }\n }\n }\n }\n for (const valueId of Object.keys(dv)) {\n const value = dv[valueId][index];\n if (value == null) {\n store.delValue(valueId);\n } else {\n store.setValue(valueId, value);\n }\n }\n}\n\nexport function applyReverse(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues\n): void {\n applyDelta(store, delta, dv, 0);\n}\n\nexport function applyForward(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues\n): void {\n applyDelta(store, delta, dv, 1);\n}\n","import type { Store } from 'tinybase';\nimport type { History, CleanedAction, CleanupListener } from './types';\n\nexport function runCleanup(\n store: Store,\n tableName: string,\n maxActions: number,\n maxAge: number,\n history: History,\n listeners: Map<string, CleanupListener>,\n withBypass: (fn: () => void) => void\n): void {\n const now = Date.now();\n const rowIds = store.getRowIds(tableName);\n\n const actions = rowIds.map((id) => ({\n id,\n createdAt: store.getCell(tableName, id, 'createdAt') as number,\n undone: store.getCell(tableName, id, 'undone') as number,\n delta: store.getCell(tableName, id, 'delta') as string,\n dv: store.getCell(tableName, id, 'dv') as string,\n }));\n\n actions.sort((a, b) => a.createdAt - b.createdAt);\n\n const toRemoveSet = new Set<string>();\n\n for (const action of actions) {\n if (now - action.createdAt > maxAge) {\n toRemoveSet.add(action.id);\n }\n }\n\n const remaining = actions.filter((a) => !toRemoveSet.has(a.id));\n if (remaining.length > maxActions) {\n const excess = remaining.length - maxActions;\n for (let i = 0; i < excess; i++) {\n toRemoveSet.add(remaining[i].id);\n }\n }\n\n if (toRemoveSet.size === 0) return;\n\n const toRemove = actions.filter((a) => toRemoveSet.has(a.id));\n\n const cleaned: CleanedAction[] = toRemove.map((a) => ({\n id: a.id,\n createdAt: a.createdAt,\n undone: a.undone === 1,\n delta: a.delta,\n deltaValues: a.dv,\n }));\n\n for (const listener of Array.from(listeners.values())) {\n listener(history, cleaned);\n }\n\n withBypass(() => {\n store.transaction(() => {\n for (const action of toRemove) {\n store.delRow(tableName, action.id);\n }\n });\n });\n}\n","import type { Store, Id } from 'tinybase';\nimport type { History as IHistory, CleanupListener } from './types';\nimport {\n deserializeDelta,\n deserializeDeltaValues,\n applyReverse,\n applyForward,\n} from './delta';\nimport { runCleanup } from './cleanup';\n\nexport class History implements IHistory {\n private pendingAction: string | undefined;\n private pendingActionRecorded = false;\n private readonly cleanupListeners = new Map<string, CleanupListener>();\n private nextListenerId = 0;\n private txListenerId: Id;\n\n constructor(\n private readonly store: Store,\n private readonly tableName: string,\n private maxActions: number,\n private maxAge: number,\n private readonly generateId: () => string,\n private readonly getAuthor: () => string,\n private readonly bypassFn: (fn: () => void) => void\n ) {\n this.txListenerId = store.addWillFinishTransactionListener(() => {\n if (!this.pendingAction) return;\n\n const [, , changedCells, , changedValues] = store.getTransactionLog();\n\n const filteredCells = { ...changedCells };\n delete filteredCells[this.tableName];\n\n const hasCellChanges = Object.keys(filteredCells).length > 0;\n const hasValueChanges = Object.keys(changedValues).length > 0;\n\n if (!hasCellChanges && !hasValueChanges) return;\n\n const id = this.pendingAction;\n\n this.bypassFn(() => {\n store.setRow(this.tableName, id, {\n createdAt: Date.now(),\n delta: JSON.stringify(filteredCells),\n dv: JSON.stringify(changedValues),\n undone: 0,\n author: this.getAuthor(),\n });\n });\n\n this.pendingActionRecorded = true;\n });\n }\n\n action(mutate: () => void): Id | undefined {\n const id = this.generateId();\n\n const undoneIds = this.store\n .getRowIds(this.tableName)\n .filter(\n (rowId) => this.store.getCell(this.tableName, rowId, 'undone') === 1\n );\n if (undoneIds.length > 0) {\n this.bypassFn(() => {\n this.store.transaction(() => {\n for (const rowId of undoneIds) {\n this.store.delRow(this.tableName, rowId);\n }\n });\n });\n }\n\n this.pendingAction = id;\n this.pendingActionRecorded = false;\n try {\n this.store.transaction(() => mutate());\n } catch (err) {\n // TinyBase skips finishTransaction on throw, leaving the store\n // stuck. Manually finish so listeners fire and the store recovers.\n this.store.finishTransaction();\n this.pendingAction = undefined;\n throw err;\n }\n this.pendingAction = undefined;\n\n return this.pendingActionRecorded ? id : undefined;\n }\n\n goBackward(): this {\n const [, current] = this.getActionIds();\n if (!current) return this;\n\n const row = this.store.getRow(this.tableName, current);\n const delta = deserializeDelta(row.delta as string);\n const dv = deserializeDeltaValues(row.dv as string);\n\n this.store.transaction(() => {\n applyReverse(this.store, delta, dv);\n this.bypassFn(() => {\n this.store.setCell(this.tableName, current, 'undone', 1);\n });\n });\n\n return this;\n }\n\n goForward(): this {\n const [, , forward] = this.getActionIds();\n if (forward.length === 0) return this;\n\n const actionId = forward[0];\n const row = this.store.getRow(this.tableName, actionId);\n const delta = deserializeDelta(row.delta as string);\n const dv = deserializeDeltaValues(row.dv as string);\n\n this.store.transaction(() => {\n applyForward(this.store, delta, dv);\n this.bypassFn(() => {\n this.store.setCell(this.tableName, actionId, 'undone', 0);\n });\n });\n\n return this;\n }\n\n canGoBackward(): boolean {\n const [, current] = this.getActionIds();\n return current !== undefined;\n }\n\n canGoForward(): boolean {\n const [, , forward] = this.getActionIds();\n return forward.length > 0;\n }\n\n getActionIds(): [Id[], Id | undefined, Id[]] {\n const rowIds = this.store.getRowIds(this.tableName);\n const sorted = [...rowIds].sort(\n (a, b) =>\n (this.store.getCell(this.tableName, a, 'createdAt') as number) -\n (this.store.getCell(this.tableName, b, 'createdAt') as number)\n );\n\n let lastActiveIdx = -1;\n for (let i = sorted.length - 1; i >= 0; i--) {\n if (this.store.getCell(this.tableName, sorted[i], 'undone') === 0) {\n lastActiveIdx = i;\n break;\n }\n }\n\n const backward: Id[] = [];\n const forward: Id[] = [];\n\n for (let i = 0; i < sorted.length; i++) {\n const id = sorted[i];\n const undone = this.store.getCell(this.tableName, id, 'undone') === 1;\n if (undone && i > lastActiveIdx) {\n forward.push(id);\n } else if (!undone) {\n backward.push(id);\n }\n }\n\n const current = backward.pop();\n return [backward, current, forward];\n }\n\n setMaxActions(max: number): this {\n this.maxActions = max;\n return this;\n }\n\n setMaxAge(ms: number): this {\n this.maxAge = ms;\n return this;\n }\n\n cleanup(): this {\n runCleanup(\n this.store,\n this.tableName,\n this.maxActions,\n this.maxAge,\n this,\n this.cleanupListeners,\n this.bypassFn\n );\n return this;\n }\n\n addCleanupListener(listener: CleanupListener): Id {\n const id = String(this.nextListenerId++);\n this.cleanupListeners.set(id, listener);\n return id;\n }\n\n getStore(): Store {\n return this.store;\n }\n\n delListener(listenerId: Id): this {\n this.cleanupListeners.delete(listenerId);\n return this;\n }\n\n destroy(): void {\n this.store.delListener(this.txListenerId);\n this.cleanupListeners.clear();\n }\n}\n","import type { Store } from 'tinybase';\nimport type { History as IHistory, HistoryOptions } from './types';\nimport { History } from './history';\n\nconst THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;\n\nlet counter = 0;\nfunction defaultGenerateId(): string {\n const ts = Date.now().toString(36);\n const rand = Math.random().toString(36).slice(2, 8);\n return `${ts}-${rand}-${String(++counter).padStart(4, '0')}`;\n}\n\nexport function createHistory(\n store: Store,\n options?: HistoryOptions\n): IHistory {\n return new History(\n store,\n options?.tableName ?? '_history',\n options?.maxActions ?? 200,\n options?.maxAge ?? THIRTY_DAYS,\n options?.generateId ?? defaultGenerateId,\n options?.getAuthor ?? (() => ''),\n options?.withBypass ?? ((fn: () => void) => fn())\n );\n}\n","import { z } from 'zod';\n\n/**\n * Zod schema for the history table.\n * Spread into your store's Zod tables schema to allow history writes.\n *\n * Usage:\n * const myTablesSchema = { ...historyTablesSchema, yourTable: z.object({...}) };\n */\nexport const historyTablesSchema = {\n _history: z.object({\n createdAt: z.number(),\n delta: z.string(),\n dv: z.string(),\n undone: z.number(),\n author: z.string().default(''),\n }),\n};\n"]}
1
+ {"version":3,"sources":["../src/delta.ts","../src/cleanup.ts","../src/history.ts","../src/create.ts","../src/schema.ts"],"names":["z"],"mappings":"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","file":"index.js","sourcesContent":["import type { Store } from 'tinybase';\nimport type { ParsedDelta, ParsedDeltaValues } from './types';\n\nexport function deserializeDelta(json: string): ParsedDelta {\n return JSON.parse(json);\n}\n\nexport function deserializeDeltaValues(json: string): ParsedDeltaValues {\n return JSON.parse(json);\n}\n\nfunction applyDelta(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues,\n index: 0 | 1\n): void {\n for (const tableId of Object.keys(delta)) {\n for (const rowId of Object.keys(delta[tableId])) {\n for (const cellId of Object.keys(delta[tableId][rowId])) {\n const value = delta[tableId][rowId][cellId][index];\n if (value == null) {\n store.delCell(tableId, rowId, cellId, true);\n } else {\n store.setCell(tableId, rowId, cellId, value);\n }\n }\n }\n }\n for (const valueId of Object.keys(dv)) {\n const value = dv[valueId][index];\n if (value == null) {\n store.delValue(valueId);\n } else {\n store.setValue(valueId, value);\n }\n }\n}\n\nexport function applyReverse(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues\n): void {\n applyDelta(store, delta, dv, 0);\n}\n\nexport function applyForward(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues\n): void {\n applyDelta(store, delta, dv, 1);\n}\n\n/**\n * Whether the store currently holds one side of a delta exactly: every cell\n * and value the delta names has that side's value, where `null` means the\n * cell or value is absent. This is the precondition for applying the OTHER\n * side without clobbering anything a later action wrote.\n */\nexport function deltaHolds(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues,\n index: 0 | 1\n): boolean {\n for (const tableId of Object.keys(delta)) {\n for (const rowId of Object.keys(delta[tableId])) {\n for (const cellId of Object.keys(delta[tableId][rowId])) {\n const expected = delta[tableId][rowId][cellId][index];\n const actual = store.getCell(tableId, rowId, cellId) ?? null;\n if (actual !== expected) return false;\n }\n }\n }\n for (const valueId of Object.keys(dv)) {\n const expected = dv[valueId][index];\n const actual = store.getValue(valueId) ?? null;\n if (actual !== expected) return false;\n }\n return true;\n}\n","import type { Store } from 'tinybase';\nimport type { History, CleanedAction, CleanupListener } from './types';\n\nexport function runCleanup(\n store: Store,\n tableName: string,\n maxActions: number,\n maxAge: number,\n history: History,\n listeners: Map<string, CleanupListener>,\n withBypass: (fn: () => void) => void\n): void {\n const now = Date.now();\n const rowIds = store.getRowIds(tableName);\n\n const actions = rowIds.map((id) => ({\n id,\n createdAt: store.getCell(tableName, id, 'createdAt') as number,\n undone: store.getCell(tableName, id, 'undone') as number,\n delta: store.getCell(tableName, id, 'delta') as string,\n dv: store.getCell(tableName, id, 'dv') as string,\n }));\n\n actions.sort((a, b) => a.createdAt - b.createdAt);\n\n const toRemoveSet = new Set<string>();\n\n for (const action of actions) {\n if (now - action.createdAt > maxAge) {\n toRemoveSet.add(action.id);\n }\n }\n\n const remaining = actions.filter((a) => !toRemoveSet.has(a.id));\n if (remaining.length > maxActions) {\n const excess = remaining.length - maxActions;\n for (let i = 0; i < excess; i++) {\n toRemoveSet.add(remaining[i].id);\n }\n }\n\n if (toRemoveSet.size === 0) return;\n\n const toRemove = actions.filter((a) => toRemoveSet.has(a.id));\n\n const cleaned: CleanedAction[] = toRemove.map((a) => ({\n id: a.id,\n createdAt: a.createdAt,\n undone: a.undone === 1,\n delta: a.delta,\n deltaValues: a.dv,\n }));\n\n for (const listener of Array.from(listeners.values())) {\n listener(history, cleaned);\n }\n\n withBypass(() => {\n store.transaction(() => {\n for (const action of toRemove) {\n store.delRow(tableName, action.id);\n }\n });\n });\n}\n","import type { Store, Id } from 'tinybase';\nimport type {\n History as IHistory,\n CleanupListener,\n ParsedDelta,\n ParsedDeltaValues,\n} from './types';\nimport {\n deserializeDelta,\n deserializeDeltaValues,\n applyReverse,\n applyForward,\n deltaHolds,\n} from './delta';\nimport { runCleanup } from './cleanup';\n\ninterface LoadedAction {\n undone: boolean;\n delta: ParsedDelta;\n dv: ParsedDeltaValues;\n}\n\nexport class History implements IHistory {\n private pendingAction: string | undefined;\n private pendingActionRecorded = false;\n private readonly cleanupListeners = new Map<string, CleanupListener>();\n private nextListenerId = 0;\n private txListenerId: Id;\n\n constructor(\n private readonly store: Store,\n private readonly tableName: string,\n private maxActions: number,\n private maxAge: number,\n private readonly generateId: () => string,\n private readonly getAuthor: () => string,\n private readonly bypassFn: (fn: () => void) => void,\n private readonly clearRedoOnAction: boolean\n ) {\n this.txListenerId = store.addWillFinishTransactionListener(() => {\n if (!this.pendingAction) return;\n\n const [, , changedCells, , changedValues] = store.getTransactionLog();\n\n const filteredCells = { ...changedCells };\n delete filteredCells[this.tableName];\n\n const hasCellChanges = Object.keys(filteredCells).length > 0;\n const hasValueChanges = Object.keys(changedValues).length > 0;\n\n if (!hasCellChanges && !hasValueChanges) return;\n\n const id = this.pendingAction;\n\n this.bypassFn(() => {\n store.setRow(this.tableName, id, {\n createdAt: Date.now(),\n delta: JSON.stringify(filteredCells),\n dv: JSON.stringify(changedValues),\n undone: 0,\n author: this.getAuthor(),\n });\n });\n\n this.pendingActionRecorded = true;\n });\n }\n\n action(mutate: () => void): Id | undefined {\n const id = this.generateId();\n\n if (this.clearRedoOnAction) {\n const undoneIds = this.store\n .getRowIds(this.tableName)\n .filter(\n (rowId) => this.store.getCell(this.tableName, rowId, 'undone') === 1\n );\n if (undoneIds.length > 0) {\n this.bypassFn(() => {\n this.store.transaction(() => {\n for (const rowId of undoneIds) {\n this.store.delRow(this.tableName, rowId);\n }\n });\n });\n }\n }\n\n this.pendingAction = id;\n this.pendingActionRecorded = false;\n try {\n this.store.transaction(() => mutate());\n } catch (err) {\n // TinyBase skips finishTransaction on throw, leaving the store\n // stuck. Manually finish so listeners fire and the store recovers.\n this.store.finishTransaction();\n this.pendingAction = undefined;\n throw err;\n }\n this.pendingAction = undefined;\n\n return this.pendingActionRecorded ? id : undefined;\n }\n\n private load(actionId: Id): LoadedAction | undefined {\n if (!this.store.hasRow(this.tableName, actionId)) return undefined;\n const row = this.store.getRow(this.tableName, actionId);\n return {\n undone: row.undone === 1,\n delta: deserializeDelta(row.delta as string),\n dv: deserializeDeltaValues(row.dv as string),\n };\n }\n\n private reverse(actionId: Id, action: LoadedAction): void {\n this.store.transaction(() => {\n applyReverse(this.store, action.delta, action.dv);\n this.bypassFn(() => {\n this.store.setCell(this.tableName, actionId, 'undone', 1);\n });\n });\n }\n\n private forward(actionId: Id, action: LoadedAction): void {\n this.store.transaction(() => {\n applyForward(this.store, action.delta, action.dv);\n this.bypassFn(() => {\n this.store.setCell(this.tableName, actionId, 'undone', 0);\n });\n });\n }\n\n goBackward(): this {\n const [, current] = this.getActionIds();\n if (!current) return this;\n // The linear walk is unguarded on purpose: it reverses the newest live\n // action whatever the store holds now (see the stale-delta tests).\n this.reverse(current, this.load(current)!);\n return this;\n }\n\n goForward(): this {\n const [, , forward] = this.getActionIds();\n if (forward.length === 0) return this;\n const actionId = forward[0];\n this.forward(actionId, this.load(actionId)!);\n return this;\n }\n\n private undoable(action: LoadedAction | undefined): action is LoadedAction {\n return (\n action !== undefined &&\n !action.undone &&\n deltaHolds(this.store, action.delta, action.dv, 1)\n );\n }\n\n private redoable(action: LoadedAction | undefined): action is LoadedAction {\n return (\n action !== undefined &&\n action.undone &&\n deltaHolds(this.store, action.delta, action.dv, 0)\n );\n }\n\n canUndo(actionId: Id): boolean {\n return this.undoable(this.load(actionId));\n }\n\n canRedo(actionId: Id): boolean {\n return this.redoable(this.load(actionId));\n }\n\n undo(actionId: Id): boolean {\n const action = this.load(actionId);\n if (!this.undoable(action)) return false;\n this.reverse(actionId, action);\n return true;\n }\n\n redo(actionId: Id): boolean {\n const action = this.load(actionId);\n if (!this.redoable(action)) return false;\n this.forward(actionId, action);\n return true;\n }\n\n forget(actionId: Id): boolean {\n if (this.store.getCell(this.tableName, actionId, 'undone') !== 1) {\n return false;\n }\n this.bypassFn(() => {\n this.store.delRow(this.tableName, actionId);\n });\n return true;\n }\n\n canGoBackward(): boolean {\n const [, current] = this.getActionIds();\n return current !== undefined;\n }\n\n canGoForward(): boolean {\n const [, , forward] = this.getActionIds();\n return forward.length > 0;\n }\n\n getActionIds(): [Id[], Id | undefined, Id[]] {\n const rowIds = this.store.getRowIds(this.tableName);\n const sorted = [...rowIds].sort(\n (a, b) =>\n (this.store.getCell(this.tableName, a, 'createdAt') as number) -\n (this.store.getCell(this.tableName, b, 'createdAt') as number)\n );\n\n let lastActiveIdx = -1;\n for (let i = sorted.length - 1; i >= 0; i--) {\n if (this.store.getCell(this.tableName, sorted[i], 'undone') === 0) {\n lastActiveIdx = i;\n break;\n }\n }\n\n const backward: Id[] = [];\n const forward: Id[] = [];\n\n for (let i = 0; i < sorted.length; i++) {\n const id = sorted[i];\n const undone = this.store.getCell(this.tableName, id, 'undone') === 1;\n if (undone && i > lastActiveIdx) {\n forward.push(id);\n } else if (!undone) {\n backward.push(id);\n }\n }\n\n const current = backward.pop();\n return [backward, current, forward];\n }\n\n setMaxActions(max: number): this {\n this.maxActions = max;\n return this;\n }\n\n setMaxAge(ms: number): this {\n this.maxAge = ms;\n return this;\n }\n\n cleanup(): this {\n runCleanup(\n this.store,\n this.tableName,\n this.maxActions,\n this.maxAge,\n this,\n this.cleanupListeners,\n this.bypassFn\n );\n return this;\n }\n\n addCleanupListener(listener: CleanupListener): Id {\n const id = String(this.nextListenerId++);\n this.cleanupListeners.set(id, listener);\n return id;\n }\n\n getStore(): Store {\n return this.store;\n }\n\n delListener(listenerId: Id): this {\n this.cleanupListeners.delete(listenerId);\n return this;\n }\n\n destroy(): void {\n this.store.delListener(this.txListenerId);\n this.cleanupListeners.clear();\n }\n}\n","import type { Store } from 'tinybase';\nimport type { History as IHistory, HistoryOptions } from './types';\nimport { History } from './history';\n\nconst THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;\n\nlet counter = 0;\nfunction defaultGenerateId(): string {\n const ts = Date.now().toString(36);\n const rand = Math.random().toString(36).slice(2, 8);\n return `${ts}-${rand}-${String(++counter).padStart(4, '0')}`;\n}\n\nexport function createHistory(\n store: Store,\n options?: HistoryOptions\n): IHistory {\n return new History(\n store,\n options?.tableName ?? '_history',\n options?.maxActions ?? 200,\n options?.maxAge ?? THIRTY_DAYS,\n options?.generateId ?? defaultGenerateId,\n options?.getAuthor ?? (() => ''),\n options?.withBypass ?? ((fn: () => void) => fn()),\n options?.clearRedoOnAction ?? true\n );\n}\n","import { z } from 'zod';\n\n/**\n * Zod schema for the history table.\n * Spread into your store's Zod tables schema to allow history writes.\n *\n * Usage:\n * const myTablesSchema = { ...historyTablesSchema, yourTable: z.object({...}) };\n */\nexport const historyTablesSchema = {\n _history: z.object({\n createdAt: z.number(),\n delta: z.string(),\n dv: z.string(),\n undone: z.number(),\n author: z.string().default(''),\n }),\n};\n"]}
package/build/index.mjs CHANGED
@@ -35,6 +35,23 @@ function applyReverse(store, delta, dv) {
35
35
  function applyForward(store, delta, dv) {
36
36
  applyDelta(store, delta, dv, 1);
37
37
  }
38
+ function deltaHolds(store, delta, dv, index) {
39
+ for (const tableId of Object.keys(delta)) {
40
+ for (const rowId of Object.keys(delta[tableId])) {
41
+ for (const cellId of Object.keys(delta[tableId][rowId])) {
42
+ const expected = delta[tableId][rowId][cellId][index];
43
+ const actual = store.getCell(tableId, rowId, cellId) ?? null;
44
+ if (actual !== expected) return false;
45
+ }
46
+ }
47
+ }
48
+ for (const valueId of Object.keys(dv)) {
49
+ const expected = dv[valueId][index];
50
+ const actual = store.getValue(valueId) ?? null;
51
+ if (actual !== expected) return false;
52
+ }
53
+ return true;
54
+ }
38
55
 
39
56
  // src/cleanup.ts
40
57
  function runCleanup(store, tableName, maxActions, maxAge, history, listeners, withBypass) {
@@ -84,7 +101,7 @@ function runCleanup(store, tableName, maxActions, maxAge, history, listeners, wi
84
101
 
85
102
  // src/history.ts
86
103
  var History = class {
87
- constructor(store, tableName, maxActions, maxAge, generateId, getAuthor, bypassFn) {
104
+ constructor(store, tableName, maxActions, maxAge, generateId, getAuthor, bypassFn, clearRedoOnAction) {
88
105
  this.store = store;
89
106
  this.tableName = tableName;
90
107
  this.maxActions = maxActions;
@@ -92,6 +109,7 @@ var History = class {
92
109
  this.generateId = generateId;
93
110
  this.getAuthor = getAuthor;
94
111
  this.bypassFn = bypassFn;
112
+ this.clearRedoOnAction = clearRedoOnAction;
95
113
  this.pendingActionRecorded = false;
96
114
  this.cleanupListeners = /* @__PURE__ */ new Map();
97
115
  this.nextListenerId = 0;
@@ -118,17 +136,19 @@ var History = class {
118
136
  }
119
137
  action(mutate) {
120
138
  const id = this.generateId();
121
- const undoneIds = this.store.getRowIds(this.tableName).filter(
122
- (rowId) => this.store.getCell(this.tableName, rowId, "undone") === 1
123
- );
124
- if (undoneIds.length > 0) {
125
- this.bypassFn(() => {
126
- this.store.transaction(() => {
127
- for (const rowId of undoneIds) {
128
- this.store.delRow(this.tableName, rowId);
129
- }
139
+ if (this.clearRedoOnAction) {
140
+ const undoneIds = this.store.getRowIds(this.tableName).filter(
141
+ (rowId) => this.store.getCell(this.tableName, rowId, "undone") === 1
142
+ );
143
+ if (undoneIds.length > 0) {
144
+ this.bypassFn(() => {
145
+ this.store.transaction(() => {
146
+ for (const rowId of undoneIds) {
147
+ this.store.delRow(this.tableName, rowId);
148
+ }
149
+ });
130
150
  });
131
- });
151
+ }
132
152
  }
133
153
  this.pendingAction = id;
134
154
  this.pendingActionRecorded = false;
@@ -142,35 +162,77 @@ var History = class {
142
162
  this.pendingAction = void 0;
143
163
  return this.pendingActionRecorded ? id : void 0;
144
164
  }
145
- goBackward() {
146
- const [, current] = this.getActionIds();
147
- if (!current) return this;
148
- const row = this.store.getRow(this.tableName, current);
149
- const delta = deserializeDelta(row.delta);
150
- const dv = deserializeDeltaValues(row.dv);
165
+ load(actionId) {
166
+ if (!this.store.hasRow(this.tableName, actionId)) return void 0;
167
+ const row = this.store.getRow(this.tableName, actionId);
168
+ return {
169
+ undone: row.undone === 1,
170
+ delta: deserializeDelta(row.delta),
171
+ dv: deserializeDeltaValues(row.dv)
172
+ };
173
+ }
174
+ reverse(actionId, action) {
151
175
  this.store.transaction(() => {
152
- applyReverse(this.store, delta, dv);
176
+ applyReverse(this.store, action.delta, action.dv);
153
177
  this.bypassFn(() => {
154
- this.store.setCell(this.tableName, current, "undone", 1);
178
+ this.store.setCell(this.tableName, actionId, "undone", 1);
155
179
  });
156
180
  });
157
- return this;
158
181
  }
159
- goForward() {
160
- const [, , forward] = this.getActionIds();
161
- if (forward.length === 0) return this;
162
- const actionId = forward[0];
163
- const row = this.store.getRow(this.tableName, actionId);
164
- const delta = deserializeDelta(row.delta);
165
- const dv = deserializeDeltaValues(row.dv);
182
+ forward(actionId, action) {
166
183
  this.store.transaction(() => {
167
- applyForward(this.store, delta, dv);
184
+ applyForward(this.store, action.delta, action.dv);
168
185
  this.bypassFn(() => {
169
186
  this.store.setCell(this.tableName, actionId, "undone", 0);
170
187
  });
171
188
  });
189
+ }
190
+ goBackward() {
191
+ const [, current] = this.getActionIds();
192
+ if (!current) return this;
193
+ this.reverse(current, this.load(current));
172
194
  return this;
173
195
  }
196
+ goForward() {
197
+ const [, , forward] = this.getActionIds();
198
+ if (forward.length === 0) return this;
199
+ const actionId = forward[0];
200
+ this.forward(actionId, this.load(actionId));
201
+ return this;
202
+ }
203
+ undoable(action) {
204
+ return action !== void 0 && !action.undone && deltaHolds(this.store, action.delta, action.dv, 1);
205
+ }
206
+ redoable(action) {
207
+ return action !== void 0 && action.undone && deltaHolds(this.store, action.delta, action.dv, 0);
208
+ }
209
+ canUndo(actionId) {
210
+ return this.undoable(this.load(actionId));
211
+ }
212
+ canRedo(actionId) {
213
+ return this.redoable(this.load(actionId));
214
+ }
215
+ undo(actionId) {
216
+ const action = this.load(actionId);
217
+ if (!this.undoable(action)) return false;
218
+ this.reverse(actionId, action);
219
+ return true;
220
+ }
221
+ redo(actionId) {
222
+ const action = this.load(actionId);
223
+ if (!this.redoable(action)) return false;
224
+ this.forward(actionId, action);
225
+ return true;
226
+ }
227
+ forget(actionId) {
228
+ if (this.store.getCell(this.tableName, actionId, "undone") !== 1) {
229
+ return false;
230
+ }
231
+ this.bypassFn(() => {
232
+ this.store.delRow(this.tableName, actionId);
233
+ });
234
+ return true;
235
+ }
174
236
  canGoBackward() {
175
237
  const [, current] = this.getActionIds();
176
238
  return current !== void 0;
@@ -259,7 +321,8 @@ function createHistory(store, options) {
259
321
  options?.maxAge ?? THIRTY_DAYS,
260
322
  options?.generateId ?? defaultGenerateId,
261
323
  options?.getAuthor ?? (() => ""),
262
- options?.withBypass ?? ((fn) => fn())
324
+ options?.withBypass ?? ((fn) => fn()),
325
+ options?.clearRedoOnAction ?? true
263
326
  );
264
327
  }
265
328
  var historyTablesSchema = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/delta.ts","../src/cleanup.ts","../src/history.ts","../src/create.ts","../src/schema.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { Store } from 'tinybase';\nimport type { ParsedDelta, ParsedDeltaValues } from './types';\n\nexport function deserializeDelta(json: string): ParsedDelta {\n return JSON.parse(json);\n}\n\nexport function deserializeDeltaValues(json: string): ParsedDeltaValues {\n return JSON.parse(json);\n}\n\nfunction applyDelta(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues,\n index: 0 | 1\n): void {\n for (const tableId of Object.keys(delta)) {\n for (const rowId of Object.keys(delta[tableId])) {\n for (const cellId of Object.keys(delta[tableId][rowId])) {\n const value = delta[tableId][rowId][cellId][index];\n if (value == null) {\n store.delCell(tableId, rowId, cellId, true);\n } else {\n store.setCell(tableId, rowId, cellId, value);\n }\n }\n }\n }\n for (const valueId of Object.keys(dv)) {\n const value = dv[valueId][index];\n if (value == null) {\n store.delValue(valueId);\n } else {\n store.setValue(valueId, value);\n }\n }\n}\n\nexport function applyReverse(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues\n): void {\n applyDelta(store, delta, dv, 0);\n}\n\nexport function applyForward(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues\n): void {\n applyDelta(store, delta, dv, 1);\n}\n","import type { Store } from 'tinybase';\nimport type { History, CleanedAction, CleanupListener } from './types';\n\nexport function runCleanup(\n store: Store,\n tableName: string,\n maxActions: number,\n maxAge: number,\n history: History,\n listeners: Map<string, CleanupListener>,\n withBypass: (fn: () => void) => void\n): void {\n const now = Date.now();\n const rowIds = store.getRowIds(tableName);\n\n const actions = rowIds.map((id) => ({\n id,\n createdAt: store.getCell(tableName, id, 'createdAt') as number,\n undone: store.getCell(tableName, id, 'undone') as number,\n delta: store.getCell(tableName, id, 'delta') as string,\n dv: store.getCell(tableName, id, 'dv') as string,\n }));\n\n actions.sort((a, b) => a.createdAt - b.createdAt);\n\n const toRemoveSet = new Set<string>();\n\n for (const action of actions) {\n if (now - action.createdAt > maxAge) {\n toRemoveSet.add(action.id);\n }\n }\n\n const remaining = actions.filter((a) => !toRemoveSet.has(a.id));\n if (remaining.length > maxActions) {\n const excess = remaining.length - maxActions;\n for (let i = 0; i < excess; i++) {\n toRemoveSet.add(remaining[i].id);\n }\n }\n\n if (toRemoveSet.size === 0) return;\n\n const toRemove = actions.filter((a) => toRemoveSet.has(a.id));\n\n const cleaned: CleanedAction[] = toRemove.map((a) => ({\n id: a.id,\n createdAt: a.createdAt,\n undone: a.undone === 1,\n delta: a.delta,\n deltaValues: a.dv,\n }));\n\n for (const listener of Array.from(listeners.values())) {\n listener(history, cleaned);\n }\n\n withBypass(() => {\n store.transaction(() => {\n for (const action of toRemove) {\n store.delRow(tableName, action.id);\n }\n });\n });\n}\n","import type { Store, Id } from 'tinybase';\nimport type { History as IHistory, CleanupListener } from './types';\nimport {\n deserializeDelta,\n deserializeDeltaValues,\n applyReverse,\n applyForward,\n} from './delta';\nimport { runCleanup } from './cleanup';\n\nexport class History implements IHistory {\n private pendingAction: string | undefined;\n private pendingActionRecorded = false;\n private readonly cleanupListeners = new Map<string, CleanupListener>();\n private nextListenerId = 0;\n private txListenerId: Id;\n\n constructor(\n private readonly store: Store,\n private readonly tableName: string,\n private maxActions: number,\n private maxAge: number,\n private readonly generateId: () => string,\n private readonly getAuthor: () => string,\n private readonly bypassFn: (fn: () => void) => void\n ) {\n this.txListenerId = store.addWillFinishTransactionListener(() => {\n if (!this.pendingAction) return;\n\n const [, , changedCells, , changedValues] = store.getTransactionLog();\n\n const filteredCells = { ...changedCells };\n delete filteredCells[this.tableName];\n\n const hasCellChanges = Object.keys(filteredCells).length > 0;\n const hasValueChanges = Object.keys(changedValues).length > 0;\n\n if (!hasCellChanges && !hasValueChanges) return;\n\n const id = this.pendingAction;\n\n this.bypassFn(() => {\n store.setRow(this.tableName, id, {\n createdAt: Date.now(),\n delta: JSON.stringify(filteredCells),\n dv: JSON.stringify(changedValues),\n undone: 0,\n author: this.getAuthor(),\n });\n });\n\n this.pendingActionRecorded = true;\n });\n }\n\n action(mutate: () => void): Id | undefined {\n const id = this.generateId();\n\n const undoneIds = this.store\n .getRowIds(this.tableName)\n .filter(\n (rowId) => this.store.getCell(this.tableName, rowId, 'undone') === 1\n );\n if (undoneIds.length > 0) {\n this.bypassFn(() => {\n this.store.transaction(() => {\n for (const rowId of undoneIds) {\n this.store.delRow(this.tableName, rowId);\n }\n });\n });\n }\n\n this.pendingAction = id;\n this.pendingActionRecorded = false;\n try {\n this.store.transaction(() => mutate());\n } catch (err) {\n // TinyBase skips finishTransaction on throw, leaving the store\n // stuck. Manually finish so listeners fire and the store recovers.\n this.store.finishTransaction();\n this.pendingAction = undefined;\n throw err;\n }\n this.pendingAction = undefined;\n\n return this.pendingActionRecorded ? id : undefined;\n }\n\n goBackward(): this {\n const [, current] = this.getActionIds();\n if (!current) return this;\n\n const row = this.store.getRow(this.tableName, current);\n const delta = deserializeDelta(row.delta as string);\n const dv = deserializeDeltaValues(row.dv as string);\n\n this.store.transaction(() => {\n applyReverse(this.store, delta, dv);\n this.bypassFn(() => {\n this.store.setCell(this.tableName, current, 'undone', 1);\n });\n });\n\n return this;\n }\n\n goForward(): this {\n const [, , forward] = this.getActionIds();\n if (forward.length === 0) return this;\n\n const actionId = forward[0];\n const row = this.store.getRow(this.tableName, actionId);\n const delta = deserializeDelta(row.delta as string);\n const dv = deserializeDeltaValues(row.dv as string);\n\n this.store.transaction(() => {\n applyForward(this.store, delta, dv);\n this.bypassFn(() => {\n this.store.setCell(this.tableName, actionId, 'undone', 0);\n });\n });\n\n return this;\n }\n\n canGoBackward(): boolean {\n const [, current] = this.getActionIds();\n return current !== undefined;\n }\n\n canGoForward(): boolean {\n const [, , forward] = this.getActionIds();\n return forward.length > 0;\n }\n\n getActionIds(): [Id[], Id | undefined, Id[]] {\n const rowIds = this.store.getRowIds(this.tableName);\n const sorted = [...rowIds].sort(\n (a, b) =>\n (this.store.getCell(this.tableName, a, 'createdAt') as number) -\n (this.store.getCell(this.tableName, b, 'createdAt') as number)\n );\n\n let lastActiveIdx = -1;\n for (let i = sorted.length - 1; i >= 0; i--) {\n if (this.store.getCell(this.tableName, sorted[i], 'undone') === 0) {\n lastActiveIdx = i;\n break;\n }\n }\n\n const backward: Id[] = [];\n const forward: Id[] = [];\n\n for (let i = 0; i < sorted.length; i++) {\n const id = sorted[i];\n const undone = this.store.getCell(this.tableName, id, 'undone') === 1;\n if (undone && i > lastActiveIdx) {\n forward.push(id);\n } else if (!undone) {\n backward.push(id);\n }\n }\n\n const current = backward.pop();\n return [backward, current, forward];\n }\n\n setMaxActions(max: number): this {\n this.maxActions = max;\n return this;\n }\n\n setMaxAge(ms: number): this {\n this.maxAge = ms;\n return this;\n }\n\n cleanup(): this {\n runCleanup(\n this.store,\n this.tableName,\n this.maxActions,\n this.maxAge,\n this,\n this.cleanupListeners,\n this.bypassFn\n );\n return this;\n }\n\n addCleanupListener(listener: CleanupListener): Id {\n const id = String(this.nextListenerId++);\n this.cleanupListeners.set(id, listener);\n return id;\n }\n\n getStore(): Store {\n return this.store;\n }\n\n delListener(listenerId: Id): this {\n this.cleanupListeners.delete(listenerId);\n return this;\n }\n\n destroy(): void {\n this.store.delListener(this.txListenerId);\n this.cleanupListeners.clear();\n }\n}\n","import type { Store } from 'tinybase';\nimport type { History as IHistory, HistoryOptions } from './types';\nimport { History } from './history';\n\nconst THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;\n\nlet counter = 0;\nfunction defaultGenerateId(): string {\n const ts = Date.now().toString(36);\n const rand = Math.random().toString(36).slice(2, 8);\n return `${ts}-${rand}-${String(++counter).padStart(4, '0')}`;\n}\n\nexport function createHistory(\n store: Store,\n options?: HistoryOptions\n): IHistory {\n return new History(\n store,\n options?.tableName ?? '_history',\n options?.maxActions ?? 200,\n options?.maxAge ?? THIRTY_DAYS,\n options?.generateId ?? defaultGenerateId,\n options?.getAuthor ?? (() => ''),\n options?.withBypass ?? ((fn: () => void) => fn())\n );\n}\n","import { z } from 'zod';\n\n/**\n * Zod schema for the history table.\n * Spread into your store's Zod tables schema to allow history writes.\n *\n * Usage:\n * const myTablesSchema = { ...historyTablesSchema, yourTable: z.object({...}) };\n */\nexport const historyTablesSchema = {\n _history: z.object({\n createdAt: z.number(),\n delta: z.string(),\n dv: z.string(),\n undone: z.number(),\n author: z.string().default(''),\n }),\n};\n"]}
1
+ {"version":3,"sources":["../src/delta.ts","../src/cleanup.ts","../src/history.ts","../src/create.ts","../src/schema.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { Store } from 'tinybase';\nimport type { ParsedDelta, ParsedDeltaValues } from './types';\n\nexport function deserializeDelta(json: string): ParsedDelta {\n return JSON.parse(json);\n}\n\nexport function deserializeDeltaValues(json: string): ParsedDeltaValues {\n return JSON.parse(json);\n}\n\nfunction applyDelta(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues,\n index: 0 | 1\n): void {\n for (const tableId of Object.keys(delta)) {\n for (const rowId of Object.keys(delta[tableId])) {\n for (const cellId of Object.keys(delta[tableId][rowId])) {\n const value = delta[tableId][rowId][cellId][index];\n if (value == null) {\n store.delCell(tableId, rowId, cellId, true);\n } else {\n store.setCell(tableId, rowId, cellId, value);\n }\n }\n }\n }\n for (const valueId of Object.keys(dv)) {\n const value = dv[valueId][index];\n if (value == null) {\n store.delValue(valueId);\n } else {\n store.setValue(valueId, value);\n }\n }\n}\n\nexport function applyReverse(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues\n): void {\n applyDelta(store, delta, dv, 0);\n}\n\nexport function applyForward(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues\n): void {\n applyDelta(store, delta, dv, 1);\n}\n\n/**\n * Whether the store currently holds one side of a delta exactly: every cell\n * and value the delta names has that side's value, where `null` means the\n * cell or value is absent. This is the precondition for applying the OTHER\n * side without clobbering anything a later action wrote.\n */\nexport function deltaHolds(\n store: Store,\n delta: ParsedDelta,\n dv: ParsedDeltaValues,\n index: 0 | 1\n): boolean {\n for (const tableId of Object.keys(delta)) {\n for (const rowId of Object.keys(delta[tableId])) {\n for (const cellId of Object.keys(delta[tableId][rowId])) {\n const expected = delta[tableId][rowId][cellId][index];\n const actual = store.getCell(tableId, rowId, cellId) ?? null;\n if (actual !== expected) return false;\n }\n }\n }\n for (const valueId of Object.keys(dv)) {\n const expected = dv[valueId][index];\n const actual = store.getValue(valueId) ?? null;\n if (actual !== expected) return false;\n }\n return true;\n}\n","import type { Store } from 'tinybase';\nimport type { History, CleanedAction, CleanupListener } from './types';\n\nexport function runCleanup(\n store: Store,\n tableName: string,\n maxActions: number,\n maxAge: number,\n history: History,\n listeners: Map<string, CleanupListener>,\n withBypass: (fn: () => void) => void\n): void {\n const now = Date.now();\n const rowIds = store.getRowIds(tableName);\n\n const actions = rowIds.map((id) => ({\n id,\n createdAt: store.getCell(tableName, id, 'createdAt') as number,\n undone: store.getCell(tableName, id, 'undone') as number,\n delta: store.getCell(tableName, id, 'delta') as string,\n dv: store.getCell(tableName, id, 'dv') as string,\n }));\n\n actions.sort((a, b) => a.createdAt - b.createdAt);\n\n const toRemoveSet = new Set<string>();\n\n for (const action of actions) {\n if (now - action.createdAt > maxAge) {\n toRemoveSet.add(action.id);\n }\n }\n\n const remaining = actions.filter((a) => !toRemoveSet.has(a.id));\n if (remaining.length > maxActions) {\n const excess = remaining.length - maxActions;\n for (let i = 0; i < excess; i++) {\n toRemoveSet.add(remaining[i].id);\n }\n }\n\n if (toRemoveSet.size === 0) return;\n\n const toRemove = actions.filter((a) => toRemoveSet.has(a.id));\n\n const cleaned: CleanedAction[] = toRemove.map((a) => ({\n id: a.id,\n createdAt: a.createdAt,\n undone: a.undone === 1,\n delta: a.delta,\n deltaValues: a.dv,\n }));\n\n for (const listener of Array.from(listeners.values())) {\n listener(history, cleaned);\n }\n\n withBypass(() => {\n store.transaction(() => {\n for (const action of toRemove) {\n store.delRow(tableName, action.id);\n }\n });\n });\n}\n","import type { Store, Id } from 'tinybase';\nimport type {\n History as IHistory,\n CleanupListener,\n ParsedDelta,\n ParsedDeltaValues,\n} from './types';\nimport {\n deserializeDelta,\n deserializeDeltaValues,\n applyReverse,\n applyForward,\n deltaHolds,\n} from './delta';\nimport { runCleanup } from './cleanup';\n\ninterface LoadedAction {\n undone: boolean;\n delta: ParsedDelta;\n dv: ParsedDeltaValues;\n}\n\nexport class History implements IHistory {\n private pendingAction: string | undefined;\n private pendingActionRecorded = false;\n private readonly cleanupListeners = new Map<string, CleanupListener>();\n private nextListenerId = 0;\n private txListenerId: Id;\n\n constructor(\n private readonly store: Store,\n private readonly tableName: string,\n private maxActions: number,\n private maxAge: number,\n private readonly generateId: () => string,\n private readonly getAuthor: () => string,\n private readonly bypassFn: (fn: () => void) => void,\n private readonly clearRedoOnAction: boolean\n ) {\n this.txListenerId = store.addWillFinishTransactionListener(() => {\n if (!this.pendingAction) return;\n\n const [, , changedCells, , changedValues] = store.getTransactionLog();\n\n const filteredCells = { ...changedCells };\n delete filteredCells[this.tableName];\n\n const hasCellChanges = Object.keys(filteredCells).length > 0;\n const hasValueChanges = Object.keys(changedValues).length > 0;\n\n if (!hasCellChanges && !hasValueChanges) return;\n\n const id = this.pendingAction;\n\n this.bypassFn(() => {\n store.setRow(this.tableName, id, {\n createdAt: Date.now(),\n delta: JSON.stringify(filteredCells),\n dv: JSON.stringify(changedValues),\n undone: 0,\n author: this.getAuthor(),\n });\n });\n\n this.pendingActionRecorded = true;\n });\n }\n\n action(mutate: () => void): Id | undefined {\n const id = this.generateId();\n\n if (this.clearRedoOnAction) {\n const undoneIds = this.store\n .getRowIds(this.tableName)\n .filter(\n (rowId) => this.store.getCell(this.tableName, rowId, 'undone') === 1\n );\n if (undoneIds.length > 0) {\n this.bypassFn(() => {\n this.store.transaction(() => {\n for (const rowId of undoneIds) {\n this.store.delRow(this.tableName, rowId);\n }\n });\n });\n }\n }\n\n this.pendingAction = id;\n this.pendingActionRecorded = false;\n try {\n this.store.transaction(() => mutate());\n } catch (err) {\n // TinyBase skips finishTransaction on throw, leaving the store\n // stuck. Manually finish so listeners fire and the store recovers.\n this.store.finishTransaction();\n this.pendingAction = undefined;\n throw err;\n }\n this.pendingAction = undefined;\n\n return this.pendingActionRecorded ? id : undefined;\n }\n\n private load(actionId: Id): LoadedAction | undefined {\n if (!this.store.hasRow(this.tableName, actionId)) return undefined;\n const row = this.store.getRow(this.tableName, actionId);\n return {\n undone: row.undone === 1,\n delta: deserializeDelta(row.delta as string),\n dv: deserializeDeltaValues(row.dv as string),\n };\n }\n\n private reverse(actionId: Id, action: LoadedAction): void {\n this.store.transaction(() => {\n applyReverse(this.store, action.delta, action.dv);\n this.bypassFn(() => {\n this.store.setCell(this.tableName, actionId, 'undone', 1);\n });\n });\n }\n\n private forward(actionId: Id, action: LoadedAction): void {\n this.store.transaction(() => {\n applyForward(this.store, action.delta, action.dv);\n this.bypassFn(() => {\n this.store.setCell(this.tableName, actionId, 'undone', 0);\n });\n });\n }\n\n goBackward(): this {\n const [, current] = this.getActionIds();\n if (!current) return this;\n // The linear walk is unguarded on purpose: it reverses the newest live\n // action whatever the store holds now (see the stale-delta tests).\n this.reverse(current, this.load(current)!);\n return this;\n }\n\n goForward(): this {\n const [, , forward] = this.getActionIds();\n if (forward.length === 0) return this;\n const actionId = forward[0];\n this.forward(actionId, this.load(actionId)!);\n return this;\n }\n\n private undoable(action: LoadedAction | undefined): action is LoadedAction {\n return (\n action !== undefined &&\n !action.undone &&\n deltaHolds(this.store, action.delta, action.dv, 1)\n );\n }\n\n private redoable(action: LoadedAction | undefined): action is LoadedAction {\n return (\n action !== undefined &&\n action.undone &&\n deltaHolds(this.store, action.delta, action.dv, 0)\n );\n }\n\n canUndo(actionId: Id): boolean {\n return this.undoable(this.load(actionId));\n }\n\n canRedo(actionId: Id): boolean {\n return this.redoable(this.load(actionId));\n }\n\n undo(actionId: Id): boolean {\n const action = this.load(actionId);\n if (!this.undoable(action)) return false;\n this.reverse(actionId, action);\n return true;\n }\n\n redo(actionId: Id): boolean {\n const action = this.load(actionId);\n if (!this.redoable(action)) return false;\n this.forward(actionId, action);\n return true;\n }\n\n forget(actionId: Id): boolean {\n if (this.store.getCell(this.tableName, actionId, 'undone') !== 1) {\n return false;\n }\n this.bypassFn(() => {\n this.store.delRow(this.tableName, actionId);\n });\n return true;\n }\n\n canGoBackward(): boolean {\n const [, current] = this.getActionIds();\n return current !== undefined;\n }\n\n canGoForward(): boolean {\n const [, , forward] = this.getActionIds();\n return forward.length > 0;\n }\n\n getActionIds(): [Id[], Id | undefined, Id[]] {\n const rowIds = this.store.getRowIds(this.tableName);\n const sorted = [...rowIds].sort(\n (a, b) =>\n (this.store.getCell(this.tableName, a, 'createdAt') as number) -\n (this.store.getCell(this.tableName, b, 'createdAt') as number)\n );\n\n let lastActiveIdx = -1;\n for (let i = sorted.length - 1; i >= 0; i--) {\n if (this.store.getCell(this.tableName, sorted[i], 'undone') === 0) {\n lastActiveIdx = i;\n break;\n }\n }\n\n const backward: Id[] = [];\n const forward: Id[] = [];\n\n for (let i = 0; i < sorted.length; i++) {\n const id = sorted[i];\n const undone = this.store.getCell(this.tableName, id, 'undone') === 1;\n if (undone && i > lastActiveIdx) {\n forward.push(id);\n } else if (!undone) {\n backward.push(id);\n }\n }\n\n const current = backward.pop();\n return [backward, current, forward];\n }\n\n setMaxActions(max: number): this {\n this.maxActions = max;\n return this;\n }\n\n setMaxAge(ms: number): this {\n this.maxAge = ms;\n return this;\n }\n\n cleanup(): this {\n runCleanup(\n this.store,\n this.tableName,\n this.maxActions,\n this.maxAge,\n this,\n this.cleanupListeners,\n this.bypassFn\n );\n return this;\n }\n\n addCleanupListener(listener: CleanupListener): Id {\n const id = String(this.nextListenerId++);\n this.cleanupListeners.set(id, listener);\n return id;\n }\n\n getStore(): Store {\n return this.store;\n }\n\n delListener(listenerId: Id): this {\n this.cleanupListeners.delete(listenerId);\n return this;\n }\n\n destroy(): void {\n this.store.delListener(this.txListenerId);\n this.cleanupListeners.clear();\n }\n}\n","import type { Store } from 'tinybase';\nimport type { History as IHistory, HistoryOptions } from './types';\nimport { History } from './history';\n\nconst THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;\n\nlet counter = 0;\nfunction defaultGenerateId(): string {\n const ts = Date.now().toString(36);\n const rand = Math.random().toString(36).slice(2, 8);\n return `${ts}-${rand}-${String(++counter).padStart(4, '0')}`;\n}\n\nexport function createHistory(\n store: Store,\n options?: HistoryOptions\n): IHistory {\n return new History(\n store,\n options?.tableName ?? '_history',\n options?.maxActions ?? 200,\n options?.maxAge ?? THIRTY_DAYS,\n options?.generateId ?? defaultGenerateId,\n options?.getAuthor ?? (() => ''),\n options?.withBypass ?? ((fn: () => void) => fn()),\n options?.clearRedoOnAction ?? true\n );\n}\n","import { z } from 'zod';\n\n/**\n * Zod schema for the history table.\n * Spread into your store's Zod tables schema to allow history writes.\n *\n * Usage:\n * const myTablesSchema = { ...historyTablesSchema, yourTable: z.object({...}) };\n */\nexport const historyTablesSchema = {\n _history: z.object({\n createdAt: z.number(),\n delta: z.string(),\n dv: z.string(),\n undone: z.number(),\n author: z.string().default(''),\n }),\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tinybase-mergeable-history",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Persistent, syncable, CRDT-aware undo/redo history for TinyBase MergeableStore. Unlike built-in Checkpoints, history is stored as data inside the store, so it survives reloads and merges across clients.",
5
5
  "main": "build/index.js",
6
6
  "module": "build/index.mjs",
@@ -33,6 +33,7 @@
33
33
  "peerDependencies": { "tinybase": "^8.0.0" },
34
34
  "dependencies": { "zod": "^4.3.6" },
35
35
  "devDependencies": {
36
+ "@vitest/coverage-v8": "4.1.8",
36
37
  "tinybase": "^8.0.2",
37
38
  "tsup": "^8.0.0",
38
39
  "typescript": "^5.9.3",