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 +46 -0
- package/build/index.d.mts +33 -0
- package/build/index.d.ts +33 -0
- package/build/index.js +92 -29
- package/build/index.js.map +1 -1
- package/build/index.mjs +92 -29
- package/build/index.mjs.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# tinybase-mergeable-history
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/tinybase-mergeable-history)
|
|
4
|
+
[](https://www.npmjs.com/package/tinybase-mergeable-history)
|
|
5
|
+
[](https://bundlephobia.com/package/tinybase-mergeable-history)
|
|
6
|
+
[](https://www.npmjs.com/package/tinybase-mergeable-history)
|
|
7
|
+
[](./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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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,
|
|
180
|
+
this.store.setCell(this.tableName, actionId, "undone", 1);
|
|
157
181
|
});
|
|
158
182
|
});
|
|
159
|
-
return this;
|
|
160
183
|
}
|
|
161
|
-
|
|
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 = {
|
package/build/index.js.map
CHANGED
|
@@ -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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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,
|
|
178
|
+
this.store.setCell(this.tableName, actionId, "undone", 1);
|
|
155
179
|
});
|
|
156
180
|
});
|
|
157
|
-
return this;
|
|
158
181
|
}
|
|
159
|
-
|
|
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 = {
|
package/build/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/delta.ts","../src/cleanup.ts","../src/history.ts","../src/create.ts","../src/schema.ts"],"names":[],"mappings":";;;AAGO,SAAS,iBAAiB,IAAA,EAA2B;AAC1D,EAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AACxB;AAEO,SAAS,uBAAuB,IAAA,EAAiC;AACtE,EAAA,OAAO,IAAA,CAAK,MAAM,IAAI,CAAA;AACxB;AAEA,SAAS,UAAA,CACP,KAAA,EACA,KAAA,EACA,EAAA,EACA,KAAA,EACM;AACN,EAAA,KAAA,MAAW,OAAA,IAAW,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,EAAG;AACxC,IAAA,KAAA,MAAW,SAAS,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,OAAO,CAAC,CAAA,EAAG;AAC/C,MAAA,KAAA,MAAW,MAAA,IAAU,OAAO,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,CAAE,KAAK,CAAC,CAAA,EAAG;AACvD,QAAA,MAAM,KAAA,GAAQ,MAAM,OAAO,CAAA,CAAE,KAAK,CAAA,CAAE,MAAM,EAAE,KAAK,CAAA;AACjD,QAAA,IAAI,SAAS,IAAA,EAAM;AACjB,UAAA,KAAA,CAAM,OAAA,CAAQ,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,IAAI,CAAA;AAAA,QAC5C,CAAA,MAAO;AACL,UAAA,KAAA,CAAM,OAAA,CAAQ,OAAA,EAAS,KAAA,EAAO,MAAA,EAAQ,KAAK,CAAA;AAAA,QAC7C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,EAAA,KAAA,MAAW,OAAA,IAAW,MAAA,CAAO,IAAA,CAAK,EAAE,CAAA,EAAG;AACrC,IAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,OAAO,CAAA,CAAE,KAAK,CAAA;AAC/B,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA,KAAA,CAAM,SAAS,OAAO,CAAA;AAAA,IACxB,CAAA,MAAO;AACL,MAAA,KAAA,CAAM,QAAA,CAAS,SAAS,KAAK,CAAA;AAAA,IAC/B;AAAA,EACF;AACF;AAEO,SAAS,YAAA,CACd,KAAA,EACA,KAAA,EACA,EAAA,EACM;AACN,EAAA,UAAA,CAAW,KAAA,EAAO,KAAA,EAAO,EAAA,EAAI,CAAC,CAAA;AAChC;AAEO,SAAS,YAAA,CACd,KAAA,EACA,KAAA,EACA,EAAA,EACM;AACN,EAAA,UAAA,CAAW,KAAA,EAAO,KAAA,EAAO,EAAA,EAAI,CAAC,CAAA;AAChC;;;AClDO,SAAS,WACd,KAAA,EACA,SAAA,EACA,YACA,MAAA,EACA,OAAA,EACA,WACA,UAAA,EACM;AACN,EAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAI;AACrB,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,SAAA,CAAU,SAAS,CAAA;AAExC,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,GAAA,CAAI,CAAC,EAAA,MAAQ;AAAA,IAClC,EAAA;AAAA,IACA,SAAA,EAAW,KAAA,CAAM,OAAA,CAAQ,SAAA,EAAW,IAAI,WAAW,CAAA;AAAA,IACnD,MAAA,EAAQ,KAAA,CAAM,OAAA,CAAQ,SAAA,EAAW,IAAI,QAAQ,CAAA;AAAA,IAC7C,KAAA,EAAO,KAAA,CAAM,OAAA,CAAQ,SAAA,EAAW,IAAI,OAAO,CAAA;AAAA,IAC3C,EAAA,EAAI,KAAA,CAAM,OAAA,CAAQ,SAAA,EAAW,IAAI,IAAI;AAAA,GACvC,CAAE,CAAA;AAEF,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA,EAAG,MAAM,CAAA,CAAE,SAAA,GAAY,EAAE,SAAS,CAAA;AAEhD,EAAA,MAAM,WAAA,uBAAkB,GAAA,EAAY;AAEpC,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,GAAA,GAAM,MAAA,CAAO,SAAA,GAAY,MAAA,EAAQ;AACnC,MAAA,WAAA,CAAY,GAAA,CAAI,OAAO,EAAE,CAAA;AAAA,IAC3B;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,WAAA,CAAY,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,CAAA;AAC9D,EAAA,IAAI,SAAA,CAAU,SAAS,UAAA,EAAY;AACjC,IAAA,MAAM,MAAA,GAAS,UAAU,MAAA,GAAS,UAAA;AAClC,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,EAAQ,CAAA,EAAA,EAAK;AAC/B,MAAA,WAAA,CAAY,GAAA,CAAI,SAAA,CAAU,CAAC,CAAA,CAAE,EAAE,CAAA;AAAA,IACjC;AAAA,EACF;AAEA,EAAA,IAAI,WAAA,CAAY,SAAS,CAAA,EAAG;AAE5B,EAAA,MAAM,QAAA,GAAW,QAAQ,MAAA,CAAO,CAAC,MAAM,WAAA,CAAY,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,CAAA;AAE5D,EAAA,MAAM,OAAA,GAA2B,QAAA,CAAS,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,IACpD,IAAI,CAAA,CAAE,EAAA;AAAA,IACN,WAAW,CAAA,CAAE,SAAA;AAAA,IACb,MAAA,EAAQ,EAAE,MAAA,KAAW,CAAA;AAAA,IACrB,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,aAAa,CAAA,CAAE;AAAA,GACjB,CAAE,CAAA;AAEF,EAAA,KAAA,MAAW,YAAY,KAAA,CAAM,IAAA,CAAK,SAAA,CAAU,MAAA,EAAQ,CAAA,EAAG;AACrD,IAAA,QAAA,CAAS,SAAS,OAAO,CAAA;AAAA,EAC3B;AAEA,EAAA,UAAA,CAAW,MAAM;AACf,IAAA,KAAA,CAAM,YAAY,MAAM;AACtB,MAAA,KAAA,MAAW,UAAU,QAAA,EAAU;AAC7B,QAAA,KAAA,CAAM,MAAA,CAAO,SAAA,EAAW,MAAA,CAAO,EAAE,CAAA;AAAA,MACnC;AAAA,IACF,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;;;ACtDO,IAAM,UAAN,MAAkC;AAAA,EAOvC,YACmB,KAAA,EACA,SAAA,EACT,YACA,MAAA,EACS,UAAA,EACA,WACA,QAAA,EACjB;AAPiB,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AACT,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AACS,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AACA,IAAA,IAAA,CAAA,SAAA,GAAA,SAAA;AACA,IAAA,IAAA,CAAA,QAAA,GAAA,QAAA;AAZnB,IAAA,IAAA,CAAQ,qBAAA,GAAwB,KAAA;AAChC,IAAA,IAAA,CAAiB,gBAAA,uBAAuB,GAAA,EAA6B;AACrE,IAAA,IAAA,CAAQ,cAAA,GAAiB,CAAA;AAYvB,IAAA,IAAA,CAAK,YAAA,GAAe,KAAA,CAAM,gCAAA,CAAiC,MAAM;AAC/D,MAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AAEzB,MAAA,MAAM,KAAK,YAAA,IAAgB,aAAa,CAAA,GAAI,MAAM,iBAAA,EAAkB;AAEpE,MAAA,MAAM,aAAA,GAAgB,EAAE,GAAG,YAAA,EAAa;AACxC,MAAA,OAAO,aAAA,CAAc,KAAK,SAAS,CAAA;AAEnC,MAAA,MAAM,cAAA,GAAiB,MAAA,CAAO,IAAA,CAAK,aAAa,EAAE,MAAA,GAAS,CAAA;AAC3D,MAAA,MAAM,eAAA,GAAkB,MAAA,CAAO,IAAA,CAAK,aAAa,EAAE,MAAA,GAAS,CAAA;AAE5D,MAAA,IAAI,CAAC,cAAA,IAAkB,CAAC,eAAA,EAAiB;AAEzC,MAAA,MAAM,KAAK,IAAA,CAAK,aAAA;AAEhB,MAAA,IAAA,CAAK,SAAS,MAAM;AAClB,QAAA,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,SAAA,EAAW,EAAA,EAAI;AAAA,UAC/B,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB,KAAA,EAAO,IAAA,CAAK,SAAA,CAAU,aAAa,CAAA;AAAA,UACnC,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,aAAa,CAAA;AAAA,UAChC,MAAA,EAAQ,CAAA;AAAA,UACR,MAAA,EAAQ,KAAK,SAAA;AAAU,SACxB,CAAA;AAAA,MACH,CAAC,CAAA;AAED,MAAA,IAAA,CAAK,qBAAA,GAAwB,IAAA;AAAA,IAC/B,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,OAAO,MAAA,EAAoC;AACzC,IAAA,MAAM,EAAA,GAAK,KAAK,UAAA,EAAW;AAE3B,IAAA,MAAM,YAAY,IAAA,CAAK,KAAA,CACpB,SAAA,CAAU,IAAA,CAAK,SAAS,CAAA,CACxB,MAAA;AAAA,MACC,CAAC,UAAU,IAAA,CAAK,KAAA,CAAM,QAAQ,IAAA,CAAK,SAAA,EAAW,KAAA,EAAO,QAAQ,CAAA,KAAM;AAAA,KACrE;AACF,IAAA,IAAI,SAAA,CAAU,SAAS,CAAA,EAAG;AACxB,MAAA,IAAA,CAAK,SAAS,MAAM;AAClB,QAAA,IAAA,CAAK,KAAA,CAAM,YAAY,MAAM;AAC3B,UAAA,KAAA,MAAW,SAAS,SAAA,EAAW;AAC7B,YAAA,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,SAAA,EAAW,KAAK,CAAA;AAAA,UACzC;AAAA,QACF,CAAC,CAAA;AAAA,MACH,CAAC,CAAA;AAAA,IACH;AAEA,IAAA,IAAA,CAAK,aAAA,GAAgB,EAAA;AACrB,IAAA,IAAA,CAAK,qBAAA,GAAwB,KAAA;AAC7B,IAAA,IAAI;AACF,MAAA,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,MAAM,MAAA,EAAQ,CAAA;AAAA,IACvC,SAAS,GAAA,EAAK;AAGZ,MAAA,IAAA,CAAK,MAAM,iBAAA,EAAkB;AAC7B,MAAA,IAAA,CAAK,aAAA,GAAgB,MAAA;AACrB,MAAA,MAAM,GAAA;AAAA,IACR;AACA,IAAA,IAAA,CAAK,aAAA,GAAgB,MAAA;AAErB,IAAA,OAAO,IAAA,CAAK,wBAAwB,EAAA,GAAK,MAAA;AAAA,EAC3C;AAAA,EAEA,UAAA,GAAmB;AACjB,IAAA,MAAM,GAAG,OAAO,CAAA,GAAI,KAAK,YAAA,EAAa;AACtC,IAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,IAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,WAAW,OAAO,CAAA;AACrD,IAAA,MAAM,KAAA,GAAQ,gBAAA,CAAiB,GAAA,CAAI,KAAe,CAAA;AAClD,IAAA,MAAM,EAAA,GAAK,sBAAA,CAAuB,GAAA,CAAI,EAAY,CAAA;AAElD,IAAA,IAAA,CAAK,KAAA,CAAM,YAAY,MAAM;AAC3B,MAAA,YAAA,CAAa,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,EAAE,CAAA;AAClC,MAAA,IAAA,CAAK,SAAS,MAAM;AAClB,QAAA,IAAA,CAAK,MAAM,OAAA,CAAQ,IAAA,CAAK,SAAA,EAAW,OAAA,EAAS,UAAU,CAAC,CAAA;AAAA,MACzD,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,SAAA,GAAkB;AAChB,IAAA,MAAM,KAAK,OAAO,CAAA,GAAI,KAAK,YAAA,EAAa;AACxC,IAAA,IAAI,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG,OAAO,IAAA;AAEjC,IAAA,MAAM,QAAA,GAAW,QAAQ,CAAC,CAAA;AAC1B,IAAA,MAAM,MAAM,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,IAAA,CAAK,WAAW,QAAQ,CAAA;AACtD,IAAA,MAAM,KAAA,GAAQ,gBAAA,CAAiB,GAAA,CAAI,KAAe,CAAA;AAClD,IAAA,MAAM,EAAA,GAAK,sBAAA,CAAuB,GAAA,CAAI,EAAY,CAAA;AAElD,IAAA,IAAA,CAAK,KAAA,CAAM,YAAY,MAAM;AAC3B,MAAA,YAAA,CAAa,IAAA,CAAK,KAAA,EAAO,KAAA,EAAO,EAAE,CAAA;AAClC,MAAA,IAAA,CAAK,SAAS,MAAM;AAClB,QAAA,IAAA,CAAK,MAAM,OAAA,CAAQ,IAAA,CAAK,SAAA,EAAW,QAAA,EAAU,UAAU,CAAC,CAAA;AAAA,MAC1D,CAAC,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,aAAA,GAAyB;AACvB,IAAA,MAAM,GAAG,OAAO,CAAA,GAAI,KAAK,YAAA,EAAa;AACtC,IAAA,OAAO,OAAA,KAAY,MAAA;AAAA,EACrB;AAAA,EAEA,YAAA,GAAwB;AACtB,IAAA,MAAM,KAAK,OAAO,CAAA,GAAI,KAAK,YAAA,EAAa;AACxC,IAAA,OAAO,QAAQ,MAAA,GAAS,CAAA;AAAA,EAC1B;AAAA,EAEA,YAAA,GAA6C;AAC3C,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,KAAK,SAAS,CAAA;AAClD,IAAA,MAAM,MAAA,GAAS,CAAC,GAAG,MAAM,CAAA,CAAE,IAAA;AAAA,MACzB,CAAC,CAAA,EAAG,CAAA,KACD,IAAA,CAAK,KAAA,CAAM,QAAQ,IAAA,CAAK,SAAA,EAAW,CAAA,EAAG,WAAW,IACjD,IAAA,CAAK,KAAA,CAAM,QAAQ,IAAA,CAAK,SAAA,EAAW,GAAG,WAAW;AAAA,KACtD;AAEA,IAAA,IAAI,aAAA,GAAgB,EAAA;AACpB,IAAA,KAAA,IAAS,IAAI,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG,CAAA,IAAK,GAAG,CAAA,EAAA,EAAK;AAC3C,MAAA,IAAI,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,IAAA,CAAK,SAAA,EAAW,OAAO,CAAC,CAAA,EAAG,QAAQ,CAAA,KAAM,CAAA,EAAG;AACjE,QAAA,aAAA,GAAgB,CAAA;AAChB,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,WAAiB,EAAC;AACxB,IAAA,MAAM,UAAgB,EAAC;AAEvB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,MAAA,CAAO,QAAQ,CAAA,EAAA,EAAK;AACtC,MAAA,MAAM,EAAA,GAAK,OAAO,CAAC,CAAA;AACnB,MAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAM,OAAA,CAAQ,KAAK,SAAA,EAAW,EAAA,EAAI,QAAQ,CAAA,KAAM,CAAA;AACpE,MAAA,IAAI,MAAA,IAAU,IAAI,aAAA,EAAe;AAC/B,QAAA,OAAA,CAAQ,KAAK,EAAE,CAAA;AAAA,MACjB,CAAA,MAAA,IAAW,CAAC,MAAA,EAAQ;AAClB,QAAA,QAAA,CAAS,KAAK,EAAE,CAAA;AAAA,MAClB;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAU,SAAS,GAAA,EAAI;AAC7B,IAAA,OAAO,CAAC,QAAA,EAAU,OAAA,EAAS,OAAO,CAAA;AAAA,EACpC;AAAA,EAEA,cAAc,GAAA,EAAmB;AAC/B,IAAA,IAAA,CAAK,UAAA,GAAa,GAAA;AAClB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,UAAU,EAAA,EAAkB;AAC1B,IAAA,IAAA,CAAK,MAAA,GAAS,EAAA;AACd,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,UAAA;AAAA,MACE,IAAA,CAAK,KAAA;AAAA,MACL,IAAA,CAAK,SAAA;AAAA,MACL,IAAA,CAAK,UAAA;AAAA,MACL,IAAA,CAAK,MAAA;AAAA,MACL,IAAA;AAAA,MACA,IAAA,CAAK,gBAAA;AAAA,MACL,IAAA,CAAK;AAAA,KACP;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,mBAAmB,QAAA,EAA+B;AAChD,IAAA,MAAM,EAAA,GAAK,MAAA,CAAO,IAAA,CAAK,cAAA,EAAgB,CAAA;AACvC,IAAA,IAAA,CAAK,gBAAA,CAAiB,GAAA,CAAI,EAAA,EAAI,QAAQ,CAAA;AACtC,IAAA,OAAO,EAAA;AAAA,EACT;AAAA,EAEA,QAAA,GAAkB;AAChB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEA,YAAY,UAAA,EAAsB;AAChC,IAAA,IAAA,CAAK,gBAAA,CAAiB,OAAO,UAAU,CAAA;AACvC,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,OAAA,GAAgB;AACd,IAAA,IAAA,CAAK,KAAA,CAAM,WAAA,CAAY,IAAA,CAAK,YAAY,CAAA;AACxC,IAAA,IAAA,CAAK,iBAAiB,KAAA,EAAM;AAAA,EAC9B;AACF,CAAA;;;AC/MA,IAAM,WAAA,GAAc,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,EAAA,GAAK,GAAA;AAExC,IAAI,OAAA,GAAU,CAAA;AACd,SAAS,iBAAA,GAA4B;AACnC,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,GAAA,EAAI,CAAE,SAAS,EAAE,CAAA;AACjC,EAAA,MAAM,IAAA,GAAO,KAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA;AAClD,EAAA,OAAO,CAAA,EAAG,EAAE,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,EAAI,MAAA,CAAO,EAAE,OAAO,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC5D;AAEO,SAAS,aAAA,CACd,OACA,OAAA,EACU;AACV,EAAA,OAAO,IAAI,OAAA;AAAA,IACT,KAAA;AAAA,IACA,SAAS,SAAA,IAAa,UAAA;AAAA,IACtB,SAAS,UAAA,IAAc,GAAA;AAAA,IACvB,SAAS,MAAA,IAAU,WAAA;AAAA,IACnB,SAAS,UAAA,IAAc,iBAAA;AAAA,IACvB,OAAA,EAAS,cAAc,MAAM,EAAA,CAAA;AAAA,IAC7B,OAAA,EAAS,UAAA,KAAe,CAAC,EAAA,KAAmB,EAAA,EAAG;AAAA,GACjD;AACF;ACjBO,IAAM,mBAAA,GAAsB;AAAA,EACjC,QAAA,EAAU,EAAE,MAAA,CAAO;AAAA,IACjB,SAAA,EAAW,EAAE,MAAA,EAAO;AAAA,IACpB,KAAA,EAAO,EAAE,MAAA,EAAO;AAAA,IAChB,EAAA,EAAI,EAAE,MAAA,EAAO;AAAA,IACb,MAAA,EAAQ,EAAE,MAAA,EAAO;AAAA,IACjB,MAAA,EAAQ,CAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,EAAE;AAAA,GAC9B;AACH","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.
|
|
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",
|