@arcmantle/chronicle 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -12
- package/dist/api-methods.d.ts +10 -2
- package/dist/api-methods.d.ts.map +1 -1
- package/dist/api-methods.js +42 -20
- package/dist/api-methods.js.map +1 -1
- package/dist/batch-transaction.d.ts +6 -10
- package/dist/batch-transaction.d.ts.map +1 -1
- package/dist/batch-transaction.js +36 -42
- package/dist/batch-transaction.js.map +1 -1
- package/dist/chronicle.d.ts +3 -35
- package/dist/chronicle.d.ts.map +1 -1
- package/dist/chronicle.js +6 -8
- package/dist/chronicle.js.map +1 -1
- package/dist/history-recorder.d.ts.map +1 -1
- package/dist/history-recorder.js +5 -4
- package/dist/history-recorder.js.map +1 -1
- package/dist/history.d.ts +64 -0
- package/dist/history.d.ts.map +1 -1
- package/dist/history.js +16 -1
- package/dist/history.js.map +1 -1
- package/dist/nameof.d.ts +1 -2
- package/dist/nameof.d.ts.map +1 -1
- package/dist/nameof.js +38 -12
- package/dist/nameof.js.map +1 -1
- package/dist/path.js.map +1 -1
- package/dist/three-way-merge.d.ts +13 -0
- package/dist/three-way-merge.d.ts.map +1 -0
- package/dist/three-way-merge.js +152 -0
- package/dist/three-way-merge.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api-methods.ts +86 -53
- package/src/batch-transaction.ts +56 -55
- package/src/chronicle.ts +31 -84
- package/src/history-recorder.ts +5 -4
- package/src/history.ts +82 -1
- package/src/nameof.ts +51 -13
- package/src/path.ts +1 -1
- package/src/three-way-merge.ts +196 -0
- package/src/types.ts +25 -0
package/src/api-methods.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { type ConfigureOptions, configureRoot } from './config.ts';
|
|
1
2
|
import { clearLastUngrouped, historyDelete, historyGet } from './history.ts';
|
|
2
3
|
import { addListenerToTrie, cleanupListenerBucket, ensureListenerBucket, removeListenerFromTrie } from './listener-trie.ts';
|
|
3
4
|
import { nameofSegments } from './nameof.ts';
|
|
4
5
|
import { clearProxyCache as pfClearProxyCache } from './proxy-factory.ts';
|
|
5
6
|
import { buildEffectiveListener, flush as scheduleFlush, pause as schedulePause, resume as scheduleResume } from './schedule-queue.ts';
|
|
6
7
|
import { cloneWithOptions, diffValues, originalSnapshotCache } from './snapshot-diff.ts';
|
|
7
|
-
import
|
|
8
|
+
import { threeWayMerge } from './three-way-merge.ts';
|
|
9
|
+
import type { ChangeListener, ChangeRecord, ConflictResolutions, DiffRecord, ListenerOptions, MergeResult, PathMode, PathSelector } from './types.ts';
|
|
8
10
|
import { canRedo as coreCanRedo, canUndo as coreCanUndo, clearRedoCache, redo as coreRedo, redoGroups as coreRedoGroups, resumeWrites, suspendWrites, undo as coreUndo, undoGroups as coreUndoGroups, undoSince as coreUndoSince } from './undo-redo.ts';
|
|
9
11
|
|
|
10
12
|
|
|
@@ -17,13 +19,13 @@ const isObject = (v: unknown): v is Record<string, unknown> => typeof v === 'obj
|
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
export interface ChronicleApiMethods {
|
|
20
|
-
listen
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
listen<T extends object>(object: T, selector: PathSelector<T>, listener: ChangeListener): () => void;
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/unified-signatures
|
|
24
|
+
listen<T extends object>(object: T, selector: PathSelector<T>, listener: ChangeListener, options: ListenerOptions): () => void;
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/unified-signatures
|
|
26
|
+
listen<T extends object>(object: T, selector: PathSelector<T>, listener: ChangeListener, mode: PathMode): () => void;
|
|
27
|
+
// eslint-disable-next-line @stylistic/max-len, @typescript-eslint/unified-signatures
|
|
28
|
+
listen<T extends object>(object: T, selector: PathSelector<T>, listener: ChangeListener, mode: PathMode, options: ListenerOptions): () => void;
|
|
27
29
|
onAny: (obj: object, listener: ChangeListener, options?: ListenerOptions) => () => void;
|
|
28
30
|
pause: (obj: object) => void;
|
|
29
31
|
resume: (obj: object) => void;
|
|
@@ -34,6 +36,9 @@ export interface ChronicleApiMethods {
|
|
|
34
36
|
markPristine: (obj: object) => void;
|
|
35
37
|
diff: (obj: object) => DiffRecord[];
|
|
36
38
|
isPristine: (obj: object) => boolean;
|
|
39
|
+
snapshot: <T extends object>(obj: T) => T;
|
|
40
|
+
unwrap: <T extends object>(obj: T) => T;
|
|
41
|
+
merge: (obj: object, incomingObject: object, resolutions?: ConflictResolutions) => MergeResult;
|
|
37
42
|
mark: (obj: object) => number;
|
|
38
43
|
undo: (obj: object, steps?: number) => void;
|
|
39
44
|
undoSince: (obj: object, historyLengthBefore: number) => void;
|
|
@@ -43,19 +48,20 @@ export interface ChronicleApiMethods {
|
|
|
43
48
|
clearRedo: (obj: object) => void;
|
|
44
49
|
redo: (obj: object, steps?: number) => void;
|
|
45
50
|
redoGroups: (obj: object, groups?: number) => void;
|
|
51
|
+
configure: (obj: object, options: ConfigureOptions) => void;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export const createApiMethods = (deps: ApiDeps): ChronicleApiMethods => {
|
|
49
55
|
// listen/onAny --------------------------------------------------------------
|
|
50
|
-
const listen =
|
|
51
|
-
object
|
|
52
|
-
selector
|
|
53
|
-
listener
|
|
56
|
+
const listen: ChronicleApiMethods['listen'] = (
|
|
57
|
+
object,
|
|
58
|
+
selector,
|
|
59
|
+
listener,
|
|
54
60
|
modeOrOptions?: PathMode | ListenerOptions,
|
|
55
61
|
maybeOptions?: ListenerOptions,
|
|
56
62
|
) => {
|
|
57
63
|
const segs = nameofSegments(selector);
|
|
58
|
-
const root = deps.getRoot(object
|
|
64
|
+
const root = deps.getRoot(object);
|
|
59
65
|
const bucket = ensureListenerBucket(root);
|
|
60
66
|
|
|
61
67
|
let mode: PathMode = 'down';
|
|
@@ -96,34 +102,34 @@ export const createApiMethods = (deps: ApiDeps): ChronicleApiMethods => {
|
|
|
96
102
|
return unsubscribe;
|
|
97
103
|
};
|
|
98
104
|
|
|
99
|
-
const onAny = (obj
|
|
100
|
-
return listen(obj
|
|
105
|
+
const onAny: ChronicleApiMethods['onAny'] = (obj, listener, options) => {
|
|
106
|
+
return listen(obj, s => s, listener, options!);
|
|
101
107
|
};
|
|
102
108
|
|
|
103
109
|
// pause/resume/flush --------------------------------------------------------
|
|
104
|
-
const pause = (obj
|
|
110
|
+
const pause: ChronicleApiMethods['pause'] = (obj) => {
|
|
105
111
|
const root = deps.getRoot(obj);
|
|
106
112
|
schedulePause(root);
|
|
107
113
|
};
|
|
108
114
|
|
|
109
|
-
const resume = (obj
|
|
115
|
+
const resume: ChronicleApiMethods['resume'] = (obj) => {
|
|
110
116
|
const root = deps.getRoot(obj);
|
|
111
117
|
scheduleResume(root);
|
|
112
118
|
};
|
|
113
119
|
|
|
114
|
-
const flush = (obj
|
|
120
|
+
const flush: ChronicleApiMethods['flush'] = (obj) => {
|
|
115
121
|
const root = deps.getRoot(obj);
|
|
116
122
|
scheduleFlush(root);
|
|
117
123
|
};
|
|
118
124
|
|
|
119
125
|
// history ------------------------------------------------------------------
|
|
120
|
-
const getHistory = (obj
|
|
126
|
+
const getHistory: ChronicleApiMethods['getHistory'] = (obj) => {
|
|
121
127
|
const root = deps.getRoot(obj);
|
|
122
128
|
|
|
123
129
|
return (historyGet(root) ?? []).slice();
|
|
124
130
|
};
|
|
125
131
|
|
|
126
|
-
const clearHistory = (obj
|
|
132
|
+
const clearHistory: ChronicleApiMethods['clearHistory'] = (obj) => {
|
|
127
133
|
const root = deps.getRoot(obj);
|
|
128
134
|
historyDelete(root);
|
|
129
135
|
clearLastUngrouped(root);
|
|
@@ -131,7 +137,7 @@ export const createApiMethods = (deps: ApiDeps): ChronicleApiMethods => {
|
|
|
131
137
|
};
|
|
132
138
|
|
|
133
139
|
// reset/markPristine/diff/pristine ----------------------------------------
|
|
134
|
-
const markPristine = (obj
|
|
140
|
+
const markPristine: ChronicleApiMethods['markPristine'] = (obj) => {
|
|
135
141
|
const root = deps.getRoot(obj);
|
|
136
142
|
originalSnapshotCache.set(root, cloneWithOptions(root, root));
|
|
137
143
|
historyDelete(root);
|
|
@@ -140,7 +146,7 @@ export const createApiMethods = (deps: ApiDeps): ChronicleApiMethods => {
|
|
|
140
146
|
pfClearProxyCache(root);
|
|
141
147
|
};
|
|
142
148
|
|
|
143
|
-
const reset = (obj
|
|
149
|
+
const reset: ChronicleApiMethods['reset'] = (obj) => {
|
|
144
150
|
const root = deps.getRoot(obj);
|
|
145
151
|
const snapshot = originalSnapshotCache.get(root);
|
|
146
152
|
if (!snapshot) {
|
|
@@ -198,7 +204,7 @@ export const createApiMethods = (deps: ApiDeps): ChronicleApiMethods => {
|
|
|
198
204
|
clearRedoCache(root);
|
|
199
205
|
};
|
|
200
206
|
|
|
201
|
-
const diff = (obj
|
|
207
|
+
const diff: ChronicleApiMethods['diff'] = (obj) => {
|
|
202
208
|
const root = deps.getRoot(obj);
|
|
203
209
|
const original = originalSnapshotCache.get(root) ?? cloneWithOptions(root, root as any);
|
|
204
210
|
const out: DiffRecord[] = [];
|
|
@@ -207,86 +213,113 @@ export const createApiMethods = (deps: ApiDeps): ChronicleApiMethods => {
|
|
|
207
213
|
return out;
|
|
208
214
|
};
|
|
209
215
|
|
|
210
|
-
const isPristine = (obj
|
|
216
|
+
const isPristine: ChronicleApiMethods['isPristine'] = (obj) => {
|
|
211
217
|
const diffs = diff(obj);
|
|
212
218
|
|
|
213
219
|
return diffs.length === 0;
|
|
214
220
|
};
|
|
215
221
|
|
|
222
|
+
const snapshot: ChronicleApiMethods['snapshot'] = (obj) => {
|
|
223
|
+
const root = deps.getRoot(obj);
|
|
224
|
+
|
|
225
|
+
return cloneWithOptions(root, root) as any;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const unwrap: ChronicleApiMethods['unwrap'] = (obj) => {
|
|
229
|
+
const root = deps.getRoot(obj);
|
|
230
|
+
|
|
231
|
+
return root as any;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const merge: ChronicleApiMethods['merge'] = (obj, incomingObject, resolutions?) => {
|
|
235
|
+
const root = deps.getRoot(obj);
|
|
236
|
+
|
|
237
|
+
return threeWayMerge(root, obj, incomingObject, resolutions);
|
|
238
|
+
};
|
|
239
|
+
|
|
216
240
|
// marks/undo/redo -----------------------------------------------------------
|
|
217
|
-
const mark = (obj
|
|
241
|
+
const mark: ChronicleApiMethods['mark'] = (obj) => {
|
|
218
242
|
const root = deps.getRoot(obj);
|
|
219
243
|
const history = historyGet(root);
|
|
220
244
|
|
|
221
245
|
return history ? history.length : 0;
|
|
222
246
|
};
|
|
223
247
|
|
|
224
|
-
const undo = (obj
|
|
248
|
+
const undo: ChronicleApiMethods['undo'] = (obj, steps = Number.POSITIVE_INFINITY) => {
|
|
225
249
|
const root = deps.getRoot(obj);
|
|
226
250
|
coreUndo(root, steps);
|
|
227
251
|
};
|
|
228
252
|
|
|
229
|
-
const undoSince = (obj
|
|
253
|
+
const undoSince: ChronicleApiMethods['undoSince'] = (obj, historyLengthBefore) => {
|
|
230
254
|
const root = deps.getRoot(obj);
|
|
231
255
|
coreUndoSince(root, historyLengthBefore);
|
|
232
256
|
clearLastUngrouped(root);
|
|
233
257
|
};
|
|
234
258
|
|
|
235
|
-
const undoGroups = (obj
|
|
259
|
+
const undoGroups: ChronicleApiMethods['undoGroups'] = (obj, groups = 1) => {
|
|
236
260
|
const root = deps.getRoot(obj);
|
|
237
261
|
coreUndoGroups(root, groups);
|
|
238
262
|
clearLastUngrouped(root);
|
|
239
263
|
};
|
|
240
264
|
|
|
241
|
-
const canUndo = (obj
|
|
265
|
+
const canUndo: ChronicleApiMethods['canUndo'] = (obj) => {
|
|
242
266
|
const root = deps.getRoot(obj);
|
|
243
267
|
|
|
244
268
|
return coreCanUndo(root);
|
|
245
269
|
};
|
|
246
270
|
|
|
247
|
-
const canRedo = (obj
|
|
271
|
+
const canRedo: ChronicleApiMethods['canRedo'] = (obj) => {
|
|
248
272
|
const root = deps.getRoot(obj);
|
|
249
273
|
|
|
250
274
|
return coreCanRedo(root);
|
|
251
275
|
};
|
|
252
276
|
|
|
253
|
-
const clearRedo = (obj
|
|
277
|
+
const clearRedo: ChronicleApiMethods['clearRedo'] = (obj) => {
|
|
254
278
|
const root = deps.getRoot(obj);
|
|
255
279
|
clearRedoCache(root);
|
|
256
280
|
};
|
|
257
281
|
|
|
258
|
-
const redo = (obj
|
|
282
|
+
const redo: ChronicleApiMethods['redo'] = (obj, steps = Number.POSITIVE_INFINITY) => {
|
|
259
283
|
const root = deps.getRoot(obj);
|
|
260
284
|
coreRedo(root, steps);
|
|
261
285
|
clearLastUngrouped(root);
|
|
262
286
|
};
|
|
263
287
|
|
|
264
|
-
const redoGroups = (obj
|
|
288
|
+
const redoGroups: ChronicleApiMethods['redoGroups'] = (obj, groups = 1) => {
|
|
265
289
|
const root = deps.getRoot(obj);
|
|
266
290
|
coreRedoGroups(root, groups);
|
|
267
291
|
clearLastUngrouped(root);
|
|
268
292
|
};
|
|
269
293
|
|
|
294
|
+
const configure: ChronicleApiMethods['configure'] = (obj, options) => {
|
|
295
|
+
const root = deps.getRoot(obj);
|
|
296
|
+
configureRoot(root, options);
|
|
297
|
+
};
|
|
298
|
+
|
|
270
299
|
return {
|
|
271
|
-
listen
|
|
272
|
-
onAny
|
|
273
|
-
pause
|
|
274
|
-
resume
|
|
275
|
-
flush
|
|
276
|
-
getHistory
|
|
277
|
-
clearHistory
|
|
278
|
-
reset
|
|
279
|
-
markPristine
|
|
280
|
-
diff
|
|
281
|
-
isPristine
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
listen,
|
|
301
|
+
onAny,
|
|
302
|
+
pause,
|
|
303
|
+
resume,
|
|
304
|
+
flush,
|
|
305
|
+
getHistory,
|
|
306
|
+
clearHistory,
|
|
307
|
+
reset,
|
|
308
|
+
markPristine,
|
|
309
|
+
diff,
|
|
310
|
+
isPristine,
|
|
311
|
+
snapshot,
|
|
312
|
+
unwrap,
|
|
313
|
+
merge,
|
|
314
|
+
mark,
|
|
315
|
+
undo,
|
|
316
|
+
undoSince,
|
|
317
|
+
undoGroups,
|
|
318
|
+
canUndo,
|
|
319
|
+
canRedo,
|
|
320
|
+
clearRedo,
|
|
321
|
+
redo,
|
|
322
|
+
redoGroups,
|
|
323
|
+
configure,
|
|
291
324
|
};
|
|
292
325
|
};
|
package/src/batch-transaction.ts
CHANGED
|
@@ -8,6 +8,8 @@ const batchStack: WeakMap<object, BatchFrame[]> = new WeakMap();
|
|
|
8
8
|
|
|
9
9
|
export type BatchDeps = Pick<ChronicleCore, 'chronicle' | 'getRoot'>;
|
|
10
10
|
|
|
11
|
+
export interface TransactionResult<R> { result: R; marker: number; undo: () => void; }
|
|
12
|
+
|
|
11
13
|
export interface BatchFrame { marker: number; id: string; }
|
|
12
14
|
|
|
13
15
|
export interface BatchAPI {
|
|
@@ -16,10 +18,11 @@ export interface BatchAPI {
|
|
|
16
18
|
commitBatch: (obj: object) => void;
|
|
17
19
|
rollbackBatch: (obj: object) => void;
|
|
18
20
|
batch: <T extends object, R>(object: T, action: (observed: T) => R) => R;
|
|
19
|
-
transaction
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
transaction<T extends object, R>(
|
|
22
|
+
object: T, action: (observed: T) => R,
|
|
23
|
+
): R extends Promise<infer U>
|
|
24
|
+
? Promise<TransactionResult<U>>
|
|
25
|
+
: TransactionResult<R>;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
|
|
@@ -80,7 +83,7 @@ export const createBatchTransaction = (deps: BatchDeps): BatchAPI => {
|
|
|
80
83
|
};
|
|
81
84
|
|
|
82
85
|
const transaction: BatchAPI['transaction'] = (object, action) => {
|
|
83
|
-
const root = deps.getRoot(object
|
|
86
|
+
const root = deps.getRoot(object);
|
|
84
87
|
const marker = (historyGet(root) ?? []).length;
|
|
85
88
|
|
|
86
89
|
const framesBefore = (batchStack.get(root) ?? []).length;
|
|
@@ -90,63 +93,62 @@ export const createBatchTransaction = (deps: BatchDeps): BatchAPI => {
|
|
|
90
93
|
|
|
91
94
|
const observed = deps.chronicle(object);
|
|
92
95
|
let groupId: string | undefined;
|
|
93
|
-
try {
|
|
94
|
-
const result = action(observed);
|
|
95
|
-
const frames = (batchStack.get(root) ?? []);
|
|
96
|
-
groupId = frames.length > 0 ? frames[frames.length - 1]!.id : undefined;
|
|
97
|
-
if (isTopLevel)
|
|
98
|
-
commitBatch(root);
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
result,
|
|
102
|
-
marker,
|
|
103
|
-
undo: () => {
|
|
104
|
-
const h = historyGet(root);
|
|
105
|
-
if (groupId && h && h.length > 0) {
|
|
106
|
-
const topGroup = h[h.length - 1]!.groupId ?? `__g#${ h.length - 1 }`;
|
|
107
|
-
if (topGroup === groupId) {
|
|
108
|
-
undoGroups(root, 1);
|
|
109
96
|
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
undoSince(root, marker);
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
if (isTopLevel)
|
|
120
|
-
rollbackBatch(root);
|
|
121
|
-
else
|
|
122
|
-
undoSince(root, marker);
|
|
123
|
-
|
|
124
|
-
throw err;
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const transactionAsync: BatchAPI['transactionAsync'] = async (object, action) => {
|
|
129
|
-
const root = deps.getRoot(object as unknown as object);
|
|
130
|
-
const marker = (historyGet(root) ?? []).length;
|
|
131
|
-
|
|
132
|
-
const framesBefore = (batchStack.get(root) ?? []).length;
|
|
133
|
-
const isTopLevel = framesBefore === 0;
|
|
134
|
-
if (isTopLevel)
|
|
135
|
-
beginBatch(root);
|
|
136
|
-
|
|
137
|
-
const observed = deps.chronicle(object);
|
|
138
|
-
let groupId: string | undefined;
|
|
139
97
|
try {
|
|
140
|
-
const
|
|
98
|
+
const actionResult = action(observed);
|
|
99
|
+
const isObject = typeof actionResult === 'object' && actionResult !== null;
|
|
100
|
+
const isPromise = isObject && 'then' in actionResult && typeof actionResult.then === 'function';
|
|
101
|
+
|
|
102
|
+
// Check if result is a Promise
|
|
103
|
+
if (isPromise) {
|
|
104
|
+
const typedActionResult = actionResult as any as Promise<ReturnType<typeof action>>;
|
|
105
|
+
|
|
106
|
+
return typedActionResult.then(
|
|
107
|
+
resolvedResult => {
|
|
108
|
+
const frames = (batchStack.get(root) ?? []);
|
|
109
|
+
groupId = frames.length > 0 ? frames[frames.length - 1]!.id : undefined;
|
|
110
|
+
if (isTopLevel)
|
|
111
|
+
commitBatch(root);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
result: resolvedResult,
|
|
115
|
+
marker,
|
|
116
|
+
undo: () => {
|
|
117
|
+
const h = historyGet(root);
|
|
118
|
+
if (groupId && h && h.length > 0) {
|
|
119
|
+
const topGroup = h[h.length - 1]!.groupId ?? `__g#${ h.length - 1 }`;
|
|
120
|
+
if (topGroup === groupId) {
|
|
121
|
+
undoGroups(root, 1);
|
|
122
|
+
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
undoSince(root, marker);
|
|
128
|
+
},
|
|
129
|
+
} satisfies TransactionResult<typeof resolvedResult> as any;
|
|
130
|
+
},
|
|
131
|
+
err => {
|
|
132
|
+
if (isTopLevel)
|
|
133
|
+
rollbackBatch(root);
|
|
134
|
+
else
|
|
135
|
+
undoSince(root, marker);
|
|
136
|
+
|
|
137
|
+
throw err;
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Synchronous result
|
|
141
143
|
const frames = (batchStack.get(root) ?? []);
|
|
142
144
|
groupId = frames.length > 0 ? frames[frames.length - 1]!.id : undefined;
|
|
143
145
|
if (isTopLevel)
|
|
144
146
|
commitBatch(root);
|
|
145
147
|
|
|
146
148
|
return {
|
|
147
|
-
result,
|
|
149
|
+
result: actionResult,
|
|
148
150
|
marker,
|
|
149
|
-
undo:
|
|
151
|
+
undo: () => {
|
|
150
152
|
const h = historyGet(root);
|
|
151
153
|
if (groupId && h && h.length > 0) {
|
|
152
154
|
const topGroup = h[h.length - 1]!.groupId ?? `__g#${ h.length - 1 }`;
|
|
@@ -159,7 +161,7 @@ export const createBatchTransaction = (deps: BatchDeps): BatchAPI => {
|
|
|
159
161
|
|
|
160
162
|
undoSince(root, marker);
|
|
161
163
|
},
|
|
162
|
-
};
|
|
164
|
+
} satisfies TransactionResult<typeof actionResult> as any;
|
|
163
165
|
}
|
|
164
166
|
catch (err) {
|
|
165
167
|
if (isTopLevel)
|
|
@@ -178,6 +180,5 @@ export const createBatchTransaction = (deps: BatchDeps): BatchAPI => {
|
|
|
178
180
|
rollbackBatch,
|
|
179
181
|
batch,
|
|
180
182
|
transaction,
|
|
181
|
-
transactionAsync,
|
|
182
183
|
};
|
|
183
184
|
};
|
package/src/chronicle.ts
CHANGED
|
@@ -1,55 +1,10 @@
|
|
|
1
1
|
import { createChronicleCore } from './api.ts';
|
|
2
|
-
import { createApiMethods } from './api-methods.ts';
|
|
3
|
-
import { createBatchTransaction } from './batch-transaction.ts';
|
|
4
|
-
import { type ConfigureOptions, configureRoot } from './config.ts';
|
|
5
|
-
import type { ChangeListener, ChangeRecord, DiffRecord, ListenerOptions, PathMode, PathSelector } from './types.ts';
|
|
2
|
+
import { type ChronicleApiMethods, createApiMethods } from './api-methods.ts';
|
|
3
|
+
import { type BatchAPI, createBatchTransaction } from './batch-transaction.ts';
|
|
6
4
|
|
|
7
5
|
|
|
8
|
-
interface Chronicle {
|
|
6
|
+
interface Chronicle extends Omit<BatchAPI, 'getBatchFrames'>, ChronicleApiMethods {
|
|
9
7
|
<T extends object>(object: T): T;
|
|
10
|
-
|
|
11
|
-
listen<T extends object>(
|
|
12
|
-
object: T,
|
|
13
|
-
selector: PathSelector<T>,
|
|
14
|
-
listener: ChangeListener,
|
|
15
|
-
modeOrOptions?: PathMode | ListenerOptions,
|
|
16
|
-
maybeOptions?: ListenerOptions
|
|
17
|
-
): () => void;
|
|
18
|
-
onAny(obj: object, listener: ChangeListener, options?: ListenerOptions): () => void;
|
|
19
|
-
pause(obj: object): void;
|
|
20
|
-
resume(obj: object): void;
|
|
21
|
-
flush(obj: object): void;
|
|
22
|
-
getHistory(obj: object): ChangeRecord[];
|
|
23
|
-
clearHistory(obj: object): void;
|
|
24
|
-
reset(obj: object): void;
|
|
25
|
-
markPristine(obj: object): void;
|
|
26
|
-
undo(obj: object, steps?: number): void;
|
|
27
|
-
undoSince(obj: object, historyLengthBefore: number): void;
|
|
28
|
-
diff(obj: object): DiffRecord[];
|
|
29
|
-
isPristine(obj: object): boolean;
|
|
30
|
-
mark(obj: object): number;
|
|
31
|
-
transaction<T extends object, R>(object: T, action: (observed: T) => R): TransactionResult<R>;
|
|
32
|
-
transactionAsync<T extends object, R>(object: T, action: (observed: T) => Promise<R>): Promise<TransactionResult<R>>;
|
|
33
|
-
beginBatch(obj: object): void;
|
|
34
|
-
commitBatch(obj: object): void;
|
|
35
|
-
rollbackBatch(obj: object): void;
|
|
36
|
-
batch<T extends object, R>(object: T, action: (observed: T) => R): R;
|
|
37
|
-
undoGroups(obj: object, groups?: number): void;
|
|
38
|
-
canUndo(obj: object): boolean;
|
|
39
|
-
canRedo(obj: object): boolean;
|
|
40
|
-
clearRedo(obj: object): void;
|
|
41
|
-
redo(obj: object, steps?: number): void;
|
|
42
|
-
redoGroups(obj: object, groups?: number): void;
|
|
43
|
-
configure(
|
|
44
|
-
obj: object,
|
|
45
|
-
options: ConfigureOptions,
|
|
46
|
-
): void;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface TransactionResult<R> {
|
|
50
|
-
result: R;
|
|
51
|
-
marker: number;
|
|
52
|
-
undo: () => void;
|
|
53
8
|
}
|
|
54
9
|
|
|
55
10
|
const core = createChronicleCore({ getBatchFrames: r => batchApi.getBatchFrames(r) });
|
|
@@ -58,43 +13,35 @@ const chronicle = core.chronicle as Chronicle;
|
|
|
58
13
|
const batchApi = createBatchTransaction(core);
|
|
59
14
|
const api = createApiMethods({ getRoot: core.getRoot });
|
|
60
15
|
|
|
61
|
-
chronicle.listen
|
|
62
|
-
chronicle.onAny
|
|
63
|
-
chronicle.pause
|
|
64
|
-
chronicle.resume
|
|
65
|
-
chronicle.flush
|
|
66
|
-
chronicle.getHistory
|
|
67
|
-
chronicle.clearHistory
|
|
68
|
-
chronicle.reset
|
|
69
|
-
chronicle.markPristine
|
|
70
|
-
chronicle.undo
|
|
71
|
-
chronicle.undoSince
|
|
72
|
-
chronicle.diff
|
|
73
|
-
chronicle.isPristine
|
|
74
|
-
chronicle.
|
|
75
|
-
chronicle.
|
|
76
|
-
chronicle.
|
|
77
|
-
chronicle.
|
|
78
|
-
chronicle.
|
|
79
|
-
chronicle.
|
|
80
|
-
chronicle.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
chronicle.
|
|
84
|
-
|
|
85
|
-
chronicle.
|
|
86
|
-
|
|
87
|
-
chronicle.beginBatch
|
|
88
|
-
chronicle.commitBatch
|
|
16
|
+
chronicle.listen = api.listen;
|
|
17
|
+
chronicle.onAny = api.onAny;
|
|
18
|
+
chronicle.pause = api.pause;
|
|
19
|
+
chronicle.resume = api.resume;
|
|
20
|
+
chronicle.flush = api.flush;
|
|
21
|
+
chronicle.getHistory = api.getHistory;
|
|
22
|
+
chronicle.clearHistory = api.clearHistory;
|
|
23
|
+
chronicle.reset = api.reset;
|
|
24
|
+
chronicle.markPristine = api.markPristine;
|
|
25
|
+
chronicle.undo = api.undo;
|
|
26
|
+
chronicle.undoSince = api.undoSince;
|
|
27
|
+
chronicle.diff = api.diff;
|
|
28
|
+
chronicle.isPristine = api.isPristine;
|
|
29
|
+
chronicle.snapshot = api.snapshot;
|
|
30
|
+
chronicle.unwrap = api.unwrap;
|
|
31
|
+
chronicle.merge = api.merge;
|
|
32
|
+
chronicle.mark = api.mark;
|
|
33
|
+
chronicle.undoGroups = api.undoGroups;
|
|
34
|
+
chronicle.canUndo = api.canUndo;
|
|
35
|
+
chronicle.canRedo = api.canRedo;
|
|
36
|
+
chronicle.clearRedo = api.clearRedo;
|
|
37
|
+
chronicle.redo = api.redo;
|
|
38
|
+
chronicle.redoGroups = api.redoGroups;
|
|
39
|
+
chronicle.configure = api.configure;
|
|
40
|
+
chronicle.transaction = batchApi.transaction;
|
|
41
|
+
chronicle.batch = batchApi.batch;
|
|
42
|
+
chronicle.beginBatch = (obj) => batchApi.beginBatch(core.getRoot(obj));
|
|
43
|
+
chronicle.commitBatch = (obj) => batchApi.commitBatch(core.getRoot(obj));
|
|
89
44
|
chronicle.rollbackBatch = (obj) => batchApi.rollbackBatch(core.getRoot(obj));
|
|
90
|
-
chronicle.batch = <T extends object, R>(object: T, action: (observed: T) => R) =>
|
|
91
|
-
batchApi.batch(object, action);
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
chronicle.configure = (obj: object, options: ConfigureOptions) => {
|
|
95
|
-
const root = core.getRoot(obj);
|
|
96
|
-
configureRoot(root, options);
|
|
97
|
-
};
|
|
98
45
|
|
|
99
46
|
export { chronicle };
|
|
100
47
|
export type { Chronicle };
|
package/src/history-recorder.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ensureHistory, getOptions, trimHistoryByGroups } from './history.ts';
|
|
2
|
+
import { cloneWithOptions } from './snapshot-diff.ts';
|
|
2
3
|
import type { ChangeRecord } from './types.ts';
|
|
3
4
|
import { clearRedoCache, isSuspended } from './undo-redo.ts';
|
|
4
5
|
|
|
@@ -34,8 +35,8 @@ export const recordSet = (
|
|
|
34
35
|
const rec: ChangeRecord = {
|
|
35
36
|
path: path.slice(),
|
|
36
37
|
type: 'set',
|
|
37
|
-
oldValue,
|
|
38
|
-
newValue,
|
|
38
|
+
oldValue: cloneWithOptions(root, oldValue),
|
|
39
|
+
newValue: cloneWithOptions(root, newValue),
|
|
39
40
|
timestamp: Date.now(),
|
|
40
41
|
existedBefore,
|
|
41
42
|
groupId,
|
|
@@ -92,7 +93,7 @@ export const recordDelete = (
|
|
|
92
93
|
const rec: ChangeRecord = {
|
|
93
94
|
path: path.slice(),
|
|
94
95
|
type: 'delete',
|
|
95
|
-
oldValue,
|
|
96
|
+
oldValue: cloneWithOptions(root, oldValue),
|
|
96
97
|
newValue: undefined,
|
|
97
98
|
timestamp: Date.now(),
|
|
98
99
|
groupId,
|
|
@@ -133,7 +134,7 @@ export const recordArrayShrinkDeletes = (
|
|
|
133
134
|
const rec: ChangeRecord = {
|
|
134
135
|
path: [ ...basePath, String(index) ],
|
|
135
136
|
type: 'delete',
|
|
136
|
-
oldValue: oldVal,
|
|
137
|
+
oldValue: cloneWithOptions(root, oldVal),
|
|
137
138
|
newValue: undefined,
|
|
138
139
|
timestamp: Date.now(),
|
|
139
140
|
groupId,
|