@stonecrop/stonecrop 0.4.37 → 0.6.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.
Files changed (78) hide show
  1. package/README.md +92 -3
  2. package/dist/src/composable.d.ts +74 -8
  3. package/dist/src/composable.d.ts.map +1 -1
  4. package/dist/src/composable.js +348 -0
  5. package/dist/src/composables/operation-log.d.ts +136 -0
  6. package/dist/src/composables/operation-log.d.ts.map +1 -0
  7. package/dist/src/composables/operation-log.js +221 -0
  8. package/dist/src/doctype.d.ts +9 -1
  9. package/dist/src/doctype.d.ts.map +1 -1
  10. package/dist/{doctype.js → src/doctype.js} +9 -3
  11. package/dist/src/field-triggers.d.ts +178 -0
  12. package/dist/src/field-triggers.d.ts.map +1 -0
  13. package/dist/src/field-triggers.js +564 -0
  14. package/dist/src/index.d.ts +12 -4
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +18 -0
  17. package/dist/src/plugins/index.d.ts +11 -13
  18. package/dist/src/plugins/index.d.ts.map +1 -1
  19. package/dist/src/plugins/index.js +90 -0
  20. package/dist/src/registry.d.ts +9 -3
  21. package/dist/src/registry.d.ts.map +1 -1
  22. package/dist/{registry.js → src/registry.js} +14 -1
  23. package/dist/src/stonecrop.d.ts +350 -114
  24. package/dist/src/stonecrop.d.ts.map +1 -1
  25. package/dist/src/stonecrop.js +251 -0
  26. package/dist/src/stores/hst.d.ts +157 -0
  27. package/dist/src/stores/hst.d.ts.map +1 -0
  28. package/dist/src/stores/hst.js +483 -0
  29. package/dist/src/stores/index.d.ts +5 -1
  30. package/dist/src/stores/index.d.ts.map +1 -1
  31. package/dist/{stores → src/stores}/index.js +4 -1
  32. package/dist/src/stores/operation-log.d.ts +268 -0
  33. package/dist/src/stores/operation-log.d.ts.map +1 -0
  34. package/dist/src/stores/operation-log.js +571 -0
  35. package/dist/src/types/field-triggers.d.ts +186 -0
  36. package/dist/src/types/field-triggers.d.ts.map +1 -0
  37. package/dist/src/types/field-triggers.js +4 -0
  38. package/dist/src/types/index.d.ts +13 -2
  39. package/dist/src/types/index.d.ts.map +1 -1
  40. package/dist/src/types/index.js +4 -0
  41. package/dist/src/types/operation-log.d.ts +165 -0
  42. package/dist/src/types/operation-log.d.ts.map +1 -0
  43. package/dist/src/types/registry.d.ts +11 -0
  44. package/dist/src/types/registry.d.ts.map +1 -0
  45. package/dist/src/types/registry.js +0 -0
  46. package/dist/stonecrop.d.ts +1555 -159
  47. package/dist/stonecrop.js +1974 -7028
  48. package/dist/stonecrop.js.map +1 -1
  49. package/dist/stonecrop.umd.cjs +4 -8
  50. package/dist/stonecrop.umd.cjs.map +1 -1
  51. package/dist/tests/setup.d.ts +5 -0
  52. package/dist/tests/setup.d.ts.map +1 -0
  53. package/dist/tests/setup.js +15 -0
  54. package/package.json +6 -5
  55. package/src/composable.ts +481 -31
  56. package/src/composables/operation-log.ts +254 -0
  57. package/src/doctype.ts +9 -3
  58. package/src/field-triggers.ts +671 -0
  59. package/src/index.ts +50 -4
  60. package/src/plugins/index.ts +70 -22
  61. package/src/registry.ts +18 -3
  62. package/src/stonecrop.ts +246 -155
  63. package/src/stores/hst.ts +703 -0
  64. package/src/stores/index.ts +6 -1
  65. package/src/stores/operation-log.ts +671 -0
  66. package/src/types/field-triggers.ts +201 -0
  67. package/src/types/index.ts +17 -6
  68. package/src/types/operation-log.ts +205 -0
  69. package/src/types/registry.ts +10 -0
  70. package/dist/composable.js +0 -50
  71. package/dist/index.js +0 -6
  72. package/dist/plugins/index.js +0 -49
  73. package/dist/src/stores/data.d.ts +0 -11
  74. package/dist/src/stores/data.d.ts.map +0 -1
  75. package/dist/stores/data.js +0 -7
  76. package/src/stores/data.ts +0 -8
  77. /package/dist/{exceptions.js → src/exceptions.js} +0 -0
  78. /package/dist/{types/index.js → src/types/operation-log.js} +0 -0
@@ -0,0 +1,571 @@
1
+ import { defineStore } from 'pinia';
2
+ import { ref, computed, watch } from 'vue';
3
+ import { useLocalStorage } from '@vueuse/core';
4
+ /**
5
+ * Generate a UUID using crypto API or fallback
6
+ */
7
+ function generateId() {
8
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
9
+ return crypto.randomUUID();
10
+ }
11
+ // Fallback for environments without crypto.randomUUID
12
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
13
+ }
14
+ function serializeForBroadcast(message) {
15
+ const serialized = {
16
+ type: message.type,
17
+ clientId: message.clientId,
18
+ timestamp: message.timestamp.toISOString(),
19
+ };
20
+ if (message.operation) {
21
+ serialized.operation = {
22
+ ...message.operation,
23
+ timestamp: message.operation.timestamp.toISOString(),
24
+ };
25
+ }
26
+ if (message.operations) {
27
+ serialized.operations = message.operations.map(op => ({
28
+ ...op,
29
+ timestamp: op.timestamp.toISOString(),
30
+ }));
31
+ }
32
+ return serialized;
33
+ }
34
+ /**
35
+ * Deserialize a message from BroadcastChannel
36
+ * Converts ISO strings back to Date objects
37
+ */
38
+ function deserializeFromBroadcast(serialized) {
39
+ const message = {
40
+ type: serialized.type,
41
+ clientId: serialized.clientId,
42
+ timestamp: new Date(serialized.timestamp),
43
+ };
44
+ if (serialized.operation) {
45
+ message.operation = {
46
+ ...serialized.operation,
47
+ timestamp: new Date(serialized.operation.timestamp),
48
+ };
49
+ }
50
+ if (serialized.operations) {
51
+ message.operations = serialized.operations.map(op => ({
52
+ ...op,
53
+ timestamp: new Date(op.timestamp),
54
+ }));
55
+ }
56
+ return message;
57
+ }
58
+ /**
59
+ * Global HST Operation Log Store
60
+ * Tracks all mutations with full metadata for undo/redo, sync, and audit
61
+ *
62
+ * @public
63
+ */
64
+ export const useOperationLogStore = defineStore('hst-operation-log', () => {
65
+ // Configuration
66
+ const config = ref({
67
+ maxOperations: 100,
68
+ enableCrossTabSync: true,
69
+ autoSyncInterval: 30000,
70
+ enablePersistence: false,
71
+ persistenceKeyPrefix: 'stonecrop-ops',
72
+ });
73
+ // State
74
+ const operations = ref([]);
75
+ const currentIndex = ref(-1); // Points to the last applied operation
76
+ const clientId = ref(generateId());
77
+ const batchMode = ref(false);
78
+ const currentBatch = ref([]);
79
+ const currentBatchId = ref(null);
80
+ // Computed
81
+ const canUndo = computed(() => {
82
+ // Can undo if there are operations and we're not at the beginning
83
+ if (currentIndex.value < 0)
84
+ return false;
85
+ // Check if the operation at currentIndex is reversible
86
+ const operation = operations.value[currentIndex.value];
87
+ return operation?.reversible ?? false;
88
+ });
89
+ const canRedo = computed(() => {
90
+ // Can redo if there are operations ahead of current index
91
+ return currentIndex.value < operations.value.length - 1;
92
+ });
93
+ const undoCount = computed(() => {
94
+ let count = 0;
95
+ for (let i = currentIndex.value; i >= 0; i--) {
96
+ if (operations.value[i]?.reversible)
97
+ count++;
98
+ else
99
+ break;
100
+ }
101
+ return count;
102
+ });
103
+ const redoCount = computed(() => {
104
+ return operations.value.length - 1 - currentIndex.value;
105
+ });
106
+ const undoRedoState = computed(() => ({
107
+ canUndo: canUndo.value,
108
+ canRedo: canRedo.value,
109
+ undoCount: undoCount.value,
110
+ redoCount: redoCount.value,
111
+ currentIndex: currentIndex.value,
112
+ }));
113
+ // Core Methods
114
+ /**
115
+ * Configure the operation log
116
+ */
117
+ function configure(options) {
118
+ config.value = { ...config.value, ...options };
119
+ // Set up persistence if enabled
120
+ if (config.value.enablePersistence) {
121
+ loadFromPersistence();
122
+ setupPersistenceWatcher();
123
+ }
124
+ // Set up cross-tab sync if enabled
125
+ if (config.value.enableCrossTabSync) {
126
+ setupCrossTabSync();
127
+ }
128
+ }
129
+ /**
130
+ * Add an operation to the log
131
+ */
132
+ function addOperation(operation, source = 'user') {
133
+ const fullOperation = {
134
+ ...operation,
135
+ id: generateId(),
136
+ timestamp: new Date(),
137
+ source: source,
138
+ userId: config.value.userId,
139
+ };
140
+ // Apply filter if configured
141
+ if (config.value.operationFilter && !config.value.operationFilter(fullOperation)) {
142
+ return fullOperation.id;
143
+ }
144
+ // If in batch mode, collect operations
145
+ if (batchMode.value) {
146
+ currentBatch.value.push(fullOperation);
147
+ return fullOperation.id;
148
+ }
149
+ // Remove any operations after current index (they become invalid after new operation)
150
+ if (currentIndex.value < operations.value.length - 1) {
151
+ operations.value = operations.value.slice(0, currentIndex.value + 1);
152
+ }
153
+ // Add new operation
154
+ operations.value.push(fullOperation);
155
+ currentIndex.value++;
156
+ // Enforce max operations limit
157
+ if (config.value.maxOperations && operations.value.length > config.value.maxOperations) {
158
+ const overflow = operations.value.length - config.value.maxOperations;
159
+ operations.value = operations.value.slice(overflow);
160
+ currentIndex.value -= overflow;
161
+ }
162
+ // Broadcast to other tabs
163
+ if (config.value.enableCrossTabSync) {
164
+ broadcastOperation(fullOperation);
165
+ }
166
+ return fullOperation.id;
167
+ }
168
+ /**
169
+ * Start batch mode - collect multiple operations
170
+ */
171
+ function startBatch() {
172
+ batchMode.value = true;
173
+ currentBatch.value = [];
174
+ currentBatchId.value = generateId();
175
+ }
176
+ /**
177
+ * Commit batch - create a single batch operation
178
+ */
179
+ function commitBatch(description) {
180
+ if (!batchMode.value || currentBatch.value.length === 0) {
181
+ batchMode.value = false;
182
+ currentBatch.value = [];
183
+ currentBatchId.value = null;
184
+ return null;
185
+ }
186
+ const batchId = currentBatchId.value;
187
+ const allReversible = currentBatch.value.every(op => op.reversible);
188
+ // Create parent batch operation
189
+ const batchOperation = {
190
+ id: batchId,
191
+ type: 'batch',
192
+ path: '', // Batch doesn't have a single path
193
+ fieldname: '',
194
+ beforeValue: null,
195
+ afterValue: null,
196
+ doctype: currentBatch.value[0]?.doctype || '',
197
+ timestamp: new Date(),
198
+ source: 'user',
199
+ reversible: allReversible,
200
+ irreversibleReason: allReversible ? undefined : 'Contains irreversible operations',
201
+ childOperationIds: currentBatch.value.map(op => op.id),
202
+ metadata: { description },
203
+ };
204
+ // Add parent operation ID to all children
205
+ currentBatch.value.forEach(op => {
206
+ op.parentOperationId = batchId;
207
+ });
208
+ // Add all operations to the log
209
+ operations.value.push(...currentBatch.value, batchOperation);
210
+ currentIndex.value = operations.value.length - 1;
211
+ // Broadcast batch
212
+ if (config.value.enableCrossTabSync) {
213
+ broadcastBatch(currentBatch.value, batchOperation);
214
+ }
215
+ // Reset batch state
216
+ const result = batchId;
217
+ batchMode.value = false;
218
+ currentBatch.value = [];
219
+ currentBatchId.value = null;
220
+ return result;
221
+ }
222
+ /**
223
+ * Cancel batch mode without committing
224
+ */
225
+ function cancelBatch() {
226
+ batchMode.value = false;
227
+ currentBatch.value = [];
228
+ currentBatchId.value = null;
229
+ }
230
+ /**
231
+ * Undo the last operation
232
+ */
233
+ function undo(store) {
234
+ if (!canUndo.value)
235
+ return false;
236
+ const operation = operations.value[currentIndex.value];
237
+ if (!operation.reversible) {
238
+ // Warn about irreversible operation
239
+ if (typeof console !== 'undefined' && operation.irreversibleReason) {
240
+ // eslint-disable-next-line no-console
241
+ console.warn('Cannot undo irreversible operation:', operation.irreversibleReason);
242
+ }
243
+ return false;
244
+ }
245
+ try {
246
+ // Handle batch operations
247
+ if (operation.type === 'batch' && operation.childOperationIds) {
248
+ // Undo all child operations in reverse order
249
+ for (let i = operation.childOperationIds.length - 1; i >= 0; i--) {
250
+ const childId = operation.childOperationIds[i];
251
+ const childOp = operations.value.find(op => op.id === childId);
252
+ if (childOp) {
253
+ revertOperation(childOp, store);
254
+ }
255
+ }
256
+ }
257
+ else {
258
+ // Undo single operation
259
+ revertOperation(operation, store);
260
+ }
261
+ currentIndex.value--;
262
+ // Broadcast undo to other tabs
263
+ if (config.value.enableCrossTabSync) {
264
+ broadcastUndo(operation);
265
+ }
266
+ return true;
267
+ }
268
+ catch (error) {
269
+ // Log error in development
270
+ if (typeof console !== 'undefined') {
271
+ // eslint-disable-next-line no-console
272
+ console.error('Undo failed:', error);
273
+ }
274
+ return false;
275
+ }
276
+ }
277
+ /**
278
+ * Redo the next operation
279
+ */
280
+ function redo(store) {
281
+ if (!canRedo.value)
282
+ return false;
283
+ const operation = operations.value[currentIndex.value + 1];
284
+ try {
285
+ // Handle batch operations
286
+ if (operation.type === 'batch' && operation.childOperationIds) {
287
+ // Redo all child operations in order
288
+ for (const childId of operation.childOperationIds) {
289
+ const childOp = operations.value.find(op => op.id === childId);
290
+ if (childOp) {
291
+ applyOperation(childOp, store);
292
+ }
293
+ }
294
+ }
295
+ else {
296
+ // Redo single operation
297
+ applyOperation(operation, store);
298
+ }
299
+ currentIndex.value++;
300
+ // Broadcast redo to other tabs
301
+ if (config.value.enableCrossTabSync) {
302
+ broadcastRedo(operation);
303
+ }
304
+ return true;
305
+ }
306
+ catch (error) {
307
+ // Log error in development
308
+ if (typeof console !== 'undefined') {
309
+ // eslint-disable-next-line no-console
310
+ console.error('Redo failed:', error);
311
+ }
312
+ return false;
313
+ }
314
+ }
315
+ /**
316
+ * Revert an operation (apply beforeValue)
317
+ */
318
+ function revertOperation(operation, store) {
319
+ // Both 'set' and 'delete' operations can be reverted by setting to beforeValue
320
+ if ((operation.type === 'set' || operation.type === 'delete') && store && typeof store.set === 'function') {
321
+ store.set(operation.path, operation.beforeValue, 'undo');
322
+ }
323
+ // Note: 'transition' operations are marked as non-reversible, so they won't reach here
324
+ // Note: 'batch' operations are handled separately in the undo function
325
+ }
326
+ /**
327
+ * Apply an operation (apply afterValue)
328
+ */
329
+ function applyOperation(operation, store) {
330
+ // Both 'set' and 'delete' operations can be applied by setting to afterValue
331
+ if ((operation.type === 'set' || operation.type === 'delete') && store && typeof store.set === 'function') {
332
+ store.set(operation.path, operation.afterValue, 'redo');
333
+ }
334
+ // Note: 'transition' operations are marked as non-reversible, so they won't reach here
335
+ // Note: 'batch' operations are handled separately in the redo function
336
+ }
337
+ /**
338
+ * Get operation log snapshot for debugging
339
+ */
340
+ function getSnapshot() {
341
+ const reversibleOps = operations.value.filter(op => op.reversible).length;
342
+ const timestamps = operations.value.map(op => op.timestamp);
343
+ return {
344
+ operations: [...operations.value],
345
+ currentIndex: currentIndex.value,
346
+ totalOperations: operations.value.length,
347
+ reversibleOperations: reversibleOps,
348
+ irreversibleOperations: operations.value.length - reversibleOps,
349
+ oldestOperation: timestamps.length > 0 ? new Date(Math.min(...timestamps.map(t => t.getTime()))) : undefined,
350
+ newestOperation: timestamps.length > 0 ? new Date(Math.max(...timestamps.map(t => t.getTime()))) : undefined,
351
+ };
352
+ }
353
+ /**
354
+ * Clear all operations
355
+ */
356
+ function clear() {
357
+ operations.value = [];
358
+ currentIndex.value = -1;
359
+ }
360
+ /**
361
+ * Get operations for a specific doctype and recordId
362
+ */
363
+ function getOperationsFor(doctype, recordId) {
364
+ return operations.value.filter(op => op.doctype === doctype && (recordId === undefined || op.recordId === recordId));
365
+ }
366
+ /**
367
+ * Mark an operation as irreversible
368
+ * @param operationId - The ID of the operation to mark
369
+ * @param reason - The reason why the operation is irreversible
370
+ */
371
+ function markIrreversible(operationId, reason) {
372
+ const operation = operations.value.find(op => op.id === operationId);
373
+ if (operation) {
374
+ operation.reversible = false;
375
+ operation.irreversibleReason = reason;
376
+ }
377
+ }
378
+ /**
379
+ * Log an action execution (stateless actions like print, email, etc.)
380
+ * These operations are tracked but typically not reversible
381
+ * @param doctype - The doctype the action was executed on
382
+ * @param actionName - The name of the action that was executed
383
+ * @param recordIds - Optional array of record IDs the action was executed on
384
+ * @param result - The result of the action execution
385
+ * @param error - Optional error message if action failed
386
+ * @returns The operation ID
387
+ */
388
+ function logAction(doctype, actionName, recordIds, result = 'success', error) {
389
+ const operation = {
390
+ type: 'action',
391
+ path: recordIds && recordIds.length > 0 ? `${doctype}.${recordIds[0]}` : doctype,
392
+ fieldname: '',
393
+ beforeValue: null,
394
+ afterValue: null,
395
+ doctype,
396
+ recordId: recordIds && recordIds.length > 0 ? recordIds[0] : undefined,
397
+ reversible: false, // Actions are typically not reversible
398
+ actionName,
399
+ actionRecordIds: recordIds,
400
+ actionResult: result,
401
+ actionError: error,
402
+ };
403
+ return addOperation(operation);
404
+ }
405
+ // Cross-tab synchronization
406
+ let broadcastChannel = null;
407
+ function setupCrossTabSync() {
408
+ if (typeof window === 'undefined' || !window.BroadcastChannel)
409
+ return;
410
+ broadcastChannel = new BroadcastChannel('stonecrop-operation-log');
411
+ broadcastChannel.addEventListener('message', (event) => {
412
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
413
+ const rawMessage = event.data;
414
+ if (!rawMessage || typeof rawMessage !== 'object')
415
+ return;
416
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
417
+ const message = deserializeFromBroadcast(rawMessage);
418
+ // Ignore messages from this tab
419
+ if (message.clientId === clientId.value)
420
+ return;
421
+ if (message.type === 'operation' && message.operation) {
422
+ // Add operation from another tab
423
+ operations.value.push({ ...message.operation, source: 'sync' });
424
+ currentIndex.value = operations.value.length - 1;
425
+ }
426
+ else if (message.type === 'operation' && message.operations) {
427
+ // Add batch operations from another tab
428
+ operations.value.push(...message.operations.map(op => ({ ...op, source: 'sync' })));
429
+ currentIndex.value = operations.value.length - 1;
430
+ }
431
+ });
432
+ }
433
+ function broadcastOperation(operation) {
434
+ if (!broadcastChannel)
435
+ return;
436
+ const message = {
437
+ type: 'operation',
438
+ operation,
439
+ clientId: clientId.value,
440
+ timestamp: new Date(),
441
+ };
442
+ broadcastChannel.postMessage(serializeForBroadcast(message));
443
+ }
444
+ function broadcastBatch(childOps, batchOp) {
445
+ if (!broadcastChannel)
446
+ return;
447
+ const message = {
448
+ type: 'operation',
449
+ operations: [...childOps, batchOp],
450
+ clientId: clientId.value,
451
+ timestamp: new Date(),
452
+ };
453
+ broadcastChannel.postMessage(serializeForBroadcast(message));
454
+ }
455
+ function broadcastUndo(operation) {
456
+ if (!broadcastChannel)
457
+ return;
458
+ const message = {
459
+ type: 'undo',
460
+ operation,
461
+ clientId: clientId.value,
462
+ timestamp: new Date(),
463
+ };
464
+ broadcastChannel.postMessage(serializeForBroadcast(message));
465
+ }
466
+ function broadcastRedo(operation) {
467
+ if (!broadcastChannel)
468
+ return;
469
+ const message = {
470
+ type: 'redo',
471
+ operation,
472
+ clientId: clientId.value,
473
+ timestamp: new Date(),
474
+ };
475
+ broadcastChannel.postMessage(serializeForBroadcast(message));
476
+ }
477
+ const persistedData = useLocalStorage('stonecrop-ops-operations', null, {
478
+ serializer: {
479
+ read: (v) => {
480
+ try {
481
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
482
+ const data = JSON.parse(v);
483
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
484
+ return data;
485
+ }
486
+ catch {
487
+ return null;
488
+ }
489
+ },
490
+ write: (v) => {
491
+ if (!v)
492
+ return '';
493
+ return JSON.stringify(v);
494
+ },
495
+ },
496
+ });
497
+ function loadFromPersistence() {
498
+ if (typeof window === 'undefined')
499
+ return;
500
+ try {
501
+ const data = persistedData.value;
502
+ if (data && Array.isArray(data.operations)) {
503
+ operations.value = data.operations.map(op => ({
504
+ ...op,
505
+ timestamp: new Date(op.timestamp),
506
+ }));
507
+ currentIndex.value = data.currentIndex ?? -1;
508
+ }
509
+ }
510
+ catch (error) {
511
+ // Log error in development
512
+ if (typeof console !== 'undefined') {
513
+ // eslint-disable-next-line no-console
514
+ console.error('Failed to load operations from persistence:', error);
515
+ }
516
+ }
517
+ }
518
+ function saveToPersistence() {
519
+ if (typeof window === 'undefined')
520
+ return;
521
+ try {
522
+ persistedData.value = {
523
+ operations: operations.value.map(op => ({
524
+ ...op,
525
+ timestamp: op.timestamp.toISOString(),
526
+ })),
527
+ currentIndex: currentIndex.value,
528
+ };
529
+ }
530
+ catch (error) {
531
+ // Log error in development
532
+ if (typeof console !== 'undefined') {
533
+ // eslint-disable-next-line no-console
534
+ console.error('Failed to save operations to persistence:', error);
535
+ }
536
+ }
537
+ }
538
+ function setupPersistenceWatcher() {
539
+ watch([operations, currentIndex], () => {
540
+ if (config.value.enablePersistence) {
541
+ saveToPersistence();
542
+ }
543
+ }, { deep: true });
544
+ }
545
+ return {
546
+ // State
547
+ operations,
548
+ currentIndex,
549
+ config,
550
+ clientId,
551
+ undoRedoState,
552
+ // Computed
553
+ canUndo,
554
+ canRedo,
555
+ undoCount,
556
+ redoCount,
557
+ // Methods
558
+ configure,
559
+ addOperation,
560
+ startBatch,
561
+ commitBatch,
562
+ cancelBatch,
563
+ undo,
564
+ redo,
565
+ clear,
566
+ getOperationsFor,
567
+ getSnapshot,
568
+ markIrreversible,
569
+ logAction,
570
+ };
571
+ });