@stonecrop/stonecrop 0.6.3 → 0.7.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/dist/src/index.d.ts +4 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -0
- package/dist/src/schema-validator.d.ts +132 -0
- package/dist/src/schema-validator.d.ts.map +1 -0
- package/dist/src/schema-validator.js +315 -0
- package/dist/stonecrop.d.ts +140 -14
- package/dist/stonecrop.js +949 -730
- package/dist/stonecrop.js.map +1 -1
- package/dist/stonecrop.umd.cjs +3 -3
- package/dist/stonecrop.umd.cjs.map +1 -1
- package/package.json +20 -20
- package/src/index.ts +9 -0
- package/src/schema-validator.ts +427 -0
- package/dist/composable.js +0 -348
- package/dist/composables/operation-log.js +0 -221
- package/dist/doctype.js +0 -73
- package/dist/exceptions.js +0 -16
- package/dist/field-triggers.js +0 -564
- package/dist/index.js +0 -18
- package/dist/operation-log-DB-dGNT9.js +0 -593
- package/dist/operation-log-DB-dGNT9.js.map +0 -1
- package/dist/plugins/index.js +0 -90
- package/dist/registry.js +0 -72
- package/dist/src/stores/data.d.ts +0 -11
- package/dist/src/stores/data.d.ts.map +0 -1
- package/dist/src/stores/xstate.d.ts +0 -31
- package/dist/src/stores/xstate.d.ts.map +0 -1
- package/dist/stores/data.js +0 -7
- package/dist/stores/hst.js +0 -483
- package/dist/stores/index.js +0 -12
- package/dist/stores/operation-log.js +0 -571
- package/dist/stores/xstate.js +0 -29
- package/dist/types/field-triggers.js +0 -4
- package/dist/types/index.js +0 -4
- package/dist/types/operation-log.js +0 -0
- package/dist/types/registry.js +0 -0
|
@@ -1,571 +0,0 @@
|
|
|
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
|
-
});
|
package/dist/stores/xstate.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { defineStore } from 'pinia';
|
|
2
|
-
import { xstate } from 'pinia-xstate';
|
|
3
|
-
import { createMachine } from 'xstate';
|
|
4
|
-
export const counterMachine = createMachine({
|
|
5
|
-
id: 'counter',
|
|
6
|
-
initial: 'active',
|
|
7
|
-
context: {
|
|
8
|
-
count: 0,
|
|
9
|
-
},
|
|
10
|
-
states: {
|
|
11
|
-
active: {
|
|
12
|
-
on: {
|
|
13
|
-
INC: { actions: 'increment' },
|
|
14
|
-
DEC: { actions: 'decrement' },
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
}, {
|
|
19
|
-
actions: {
|
|
20
|
-
increment: context => {
|
|
21
|
-
context.count = context.count + 1;
|
|
22
|
-
},
|
|
23
|
-
decrement: context => {
|
|
24
|
-
context.count = context.count - 1;
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
// create a store using the xstate middleware
|
|
29
|
-
export const useCounterStore = defineStore(counterMachine.id, xstate(counterMachine));
|
package/dist/types/index.js
DELETED
|
File without changes
|
package/dist/types/registry.js
DELETED
|
File without changes
|