@buoy-gg/storage 1.7.7 → 2.1.1

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 (61) hide show
  1. package/lib/commonjs/index.js +219 -16
  2. package/lib/commonjs/storage/components/DiffViewer/themes/diffThemes.js +35 -44
  3. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +9 -23
  4. package/lib/commonjs/storage/components/SelectionActionBar.js +8 -22
  5. package/lib/commonjs/storage/components/StorageActionButtons.js +8 -22
  6. package/lib/commonjs/storage/components/StorageActions.js +8 -22
  7. package/lib/commonjs/storage/components/StorageEventActionButton.js +120 -0
  8. package/lib/commonjs/storage/components/StorageEventCard.js +112 -0
  9. package/lib/commonjs/storage/components/StorageEventDetailContent.js +331 -822
  10. package/lib/commonjs/storage/components/StorageModalWithTabs.js +43 -200
  11. package/lib/commonjs/storage/hooks/useStorageEvents.js +98 -0
  12. package/lib/commonjs/storage/index.js +111 -2
  13. package/lib/commonjs/storage/stores/storageEventStore.js +243 -0
  14. package/lib/commonjs/storage/utils/AsyncStorageListener.js +164 -35
  15. package/lib/commonjs/storage/utils/index.js +37 -0
  16. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +251 -0
  17. package/lib/module/index.js +74 -3
  18. package/lib/module/storage/components/DiffViewer/themes/diffThemes.js +35 -44
  19. package/lib/module/storage/components/GameUIStorageBrowser.js +9 -23
  20. package/lib/module/storage/components/SelectionActionBar.js +9 -24
  21. package/lib/module/storage/components/StorageActionButtons.js +9 -24
  22. package/lib/module/storage/components/StorageActions.js +9 -24
  23. package/lib/module/storage/components/StorageEventActionButton.js +117 -0
  24. package/lib/module/storage/components/StorageEventCard.js +107 -0
  25. package/lib/module/storage/components/StorageEventDetailContent.js +332 -824
  26. package/lib/module/storage/components/StorageModalWithTabs.js +45 -202
  27. package/lib/module/storage/hooks/useStorageEvents.js +95 -0
  28. package/lib/module/storage/index.js +7 -1
  29. package/lib/module/storage/stores/storageEventStore.js +231 -0
  30. package/lib/module/storage/utils/AsyncStorageListener.js +159 -33
  31. package/lib/module/storage/utils/index.js +4 -1
  32. package/lib/module/storage/utils/storageTimeTravelUtils.js +245 -0
  33. package/lib/typescript/index.d.ts +36 -1
  34. package/lib/typescript/index.d.ts.map +1 -1
  35. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts +1 -1
  36. package/lib/typescript/storage/components/DiffViewer/themes/diffThemes.d.ts.map +1 -1
  37. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  38. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
  39. package/lib/typescript/storage/components/StorageActionButtons.d.ts +0 -2
  40. package/lib/typescript/storage/components/StorageActionButtons.d.ts.map +1 -1
  41. package/lib/typescript/storage/components/StorageActions.d.ts.map +1 -1
  42. package/lib/typescript/storage/components/StorageEventActionButton.d.ts +37 -0
  43. package/lib/typescript/storage/components/StorageEventActionButton.d.ts.map +1 -0
  44. package/lib/typescript/storage/components/StorageEventCard.d.ts +40 -0
  45. package/lib/typescript/storage/components/StorageEventCard.d.ts.map +1 -0
  46. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +11 -3
  47. package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
  48. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  49. package/lib/typescript/storage/hooks/useStorageEvents.d.ts +51 -0
  50. package/lib/typescript/storage/hooks/useStorageEvents.d.ts.map +1 -0
  51. package/lib/typescript/storage/index.d.ts +4 -0
  52. package/lib/typescript/storage/index.d.ts.map +1 -1
  53. package/lib/typescript/storage/stores/storageEventStore.d.ts +113 -0
  54. package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -0
  55. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +38 -1
  56. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
  57. package/lib/typescript/storage/utils/index.d.ts +2 -1
  58. package/lib/typescript/storage/utils/index.d.ts.map +1 -1
  59. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +35 -0
  60. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -0
  61. package/package.json +20 -4
@@ -36,6 +36,7 @@ class AsyncStorageListener {
36
36
  listeners = [];
37
37
  isListening = false;
38
38
  isInitialized = false;
39
+ isPaused = false;
39
40
 
40
41
  // Keys to ignore for dev tools to prevent self-triggering
41
42
  // Only ignore specific keys that would cause infinite loops in the storage browser
@@ -149,9 +150,14 @@ class AsyncStorageListener {
149
150
  *
150
151
  * @param event - The AsyncStorage event to emit
151
152
  *
152
- * @performance Skips processing when no listeners are registered
153
+ * @performance Skips processing when no listeners are registered or when paused
153
154
  */
154
155
  emit(event) {
156
+ // Skip emitting if paused (used during time-travel operations)
157
+ if (this.isPaused) {
158
+ return;
159
+ }
160
+
155
161
  // Skip emitting if there are no listeners
156
162
  if (this.listeners.length === 0) {
157
163
  return;
@@ -194,16 +200,25 @@ class AsyncStorageListener {
194
200
 
195
201
  // Swizzle setItem
196
202
  const swizzled_setItem = async (key, value) => {
197
- // Only emit event if key is not ignored
203
+ // Only capture and emit if key is not ignored
198
204
  if (!this.shouldIgnoreKey(key)) {
205
+ // Capture previous value before the operation
206
+ const prevValue = this.originalSetItem ? await AsyncStorage.getItem(key) : null;
207
+
208
+ // Execute the operation
209
+ const result = this.originalSetItem ? await this.originalSetItem(key, value) : undefined;
210
+
211
+ // Emit event with previous value
199
212
  this.emit({
200
213
  action: "setItem",
201
214
  timestamp: new Date(),
202
215
  data: {
203
216
  key,
204
- value
217
+ value,
218
+ prevValue
205
219
  }
206
220
  });
221
+ return result;
207
222
  }
208
223
  return this.originalSetItem ? this.originalSetItem(key, value) : Promise.resolve();
209
224
  };
@@ -217,19 +232,24 @@ class AsyncStorageListener {
217
232
  // Swizzle removeItem
218
233
  if (AsyncStorage) {
219
234
  AsyncStorage.removeItem = async key => {
220
- // Intercepted removeItem
221
-
222
- // Only emit event if key is not ignored
235
+ // Only capture and emit if key is not ignored
223
236
  if (!this.shouldIgnoreKey(key)) {
237
+ // Capture previous value before the operation
238
+ const prevValue = this.originalRemoveItem ? await AsyncStorage.getItem(key) : null;
239
+
240
+ // Execute the operation
241
+ const result = this.originalRemoveItem ? await this.originalRemoveItem(key) : undefined;
242
+
243
+ // Emit event with previous value
224
244
  this.emit({
225
245
  action: "removeItem",
226
246
  timestamp: new Date(),
227
247
  data: {
228
- key
248
+ key,
249
+ prevValue
229
250
  }
230
251
  });
231
- } else {
232
- // Ignoring removeItem for ignored key
252
+ return result;
233
253
  }
234
254
  return this.originalRemoveItem ? this.originalRemoveItem(key) : Promise.resolve();
235
255
  };
@@ -238,20 +258,25 @@ class AsyncStorageListener {
238
258
  // Swizzle mergeItem
239
259
  if (AsyncStorage) {
240
260
  AsyncStorage.mergeItem = async (key, value) => {
241
- // Intercepted mergeItem operation
242
-
243
- // Only emit event if key is not ignored
261
+ // Only capture and emit if key is not ignored
244
262
  if (!this.shouldIgnoreKey(key)) {
263
+ // Capture previous value before the operation
264
+ const prevValue = this.originalMergeItem ? await AsyncStorage.getItem(key) : null;
265
+
266
+ // Execute the operation
267
+ const result = this.originalMergeItem ? await this.originalMergeItem(key, value) : undefined;
268
+
269
+ // Emit event with previous value
245
270
  this.emit({
246
271
  action: "mergeItem",
247
272
  timestamp: new Date(),
248
273
  data: {
249
274
  key,
250
- value
275
+ value,
276
+ prevValue
251
277
  }
252
278
  });
253
- } else {
254
- // Ignoring mergeItem for ignored key
279
+ return result;
255
280
  }
256
281
  return this.originalMergeItem ? this.originalMergeItem(key, value) : Promise.resolve();
257
282
  };
@@ -260,32 +285,64 @@ class AsyncStorageListener {
260
285
  // Swizzle clear
261
286
  if (AsyncStorage) {
262
287
  AsyncStorage.clear = async () => {
263
- // Intercepted clear operation
288
+ // Capture all key-value pairs before clearing
289
+ let prevPairs = [];
290
+ try {
291
+ const allKeys = await AsyncStorage.getAllKeys();
292
+ // Filter out ignored keys
293
+ const keysToCapture = allKeys.filter(key => !this.shouldIgnoreKey(key));
294
+ if (keysToCapture.length > 0) {
295
+ const keyValuePairs = await AsyncStorage.multiGet(keysToCapture);
296
+ prevPairs = keyValuePairs;
297
+ }
298
+ } catch {
299
+ // Failed to capture previous state
300
+ }
301
+
302
+ // Execute the operation
303
+ const result = this.originalClear ? await this.originalClear() : undefined;
304
+
305
+ // Emit event with previous pairs
264
306
  this.emit({
265
307
  action: "clear",
266
- timestamp: new Date()
308
+ timestamp: new Date(),
309
+ data: {
310
+ prevPairs
311
+ }
267
312
  });
268
- return this.originalClear ? this.originalClear() : Promise.resolve();
313
+ return result;
269
314
  };
270
315
  }
271
316
 
272
317
  // Swizzle multiSet
273
318
  if (AsyncStorage) {
274
319
  AsyncStorage.multiSet = async keyValuePairs => {
275
- // Intercepted multiSet operation with multiple pairs
276
-
277
320
  // Filter out ignored keys
278
321
  const filteredPairs = keyValuePairs.filter(([key]) => !this.shouldIgnoreKey(key));
279
322
  if (filteredPairs.length > 0) {
323
+ // Capture previous values for all keys being set
324
+ const keysToSet = filteredPairs.map(([key]) => key);
325
+ let prevPairs = [];
326
+ try {
327
+ const existingValues = await AsyncStorage.multiGet(keysToSet);
328
+ prevPairs = existingValues;
329
+ } catch {
330
+ // Failed to capture previous values
331
+ }
332
+
333
+ // Execute the operation
334
+ const result = this.originalMultiSet ? await this.originalMultiSet(keyValuePairs) : undefined;
335
+
336
+ // Emit event with previous values
280
337
  this.emit({
281
338
  action: "multiSet",
282
339
  timestamp: new Date(),
283
340
  data: {
284
- pairs: filteredPairs
341
+ pairs: filteredPairs,
342
+ prevPairs
285
343
  }
286
344
  });
287
- } else {
288
- // All keys in multiSet are ignored
345
+ return result;
289
346
  }
290
347
  return this.originalMultiSet ? this.originalMultiSet(keyValuePairs) : Promise.resolve();
291
348
  };
@@ -294,20 +351,31 @@ class AsyncStorageListener {
294
351
  // Swizzle multiRemove
295
352
  if (AsyncStorage) {
296
353
  AsyncStorage.multiRemove = async keys => {
297
- // Intercepted multiRemove operation with multiple keys
298
-
299
354
  // Filter out ignored keys
300
355
  const filteredKeys = keys.filter(key => !this.shouldIgnoreKey(key));
301
356
  if (filteredKeys.length > 0) {
357
+ // Capture previous values for all keys being removed
358
+ let prevPairs = [];
359
+ try {
360
+ const existingValues = await AsyncStorage.multiGet(filteredKeys);
361
+ prevPairs = existingValues;
362
+ } catch {
363
+ // Failed to capture previous values
364
+ }
365
+
366
+ // Execute the operation
367
+ const result = this.originalMultiRemove ? await this.originalMultiRemove(keys) : undefined;
368
+
369
+ // Emit event with previous values
302
370
  this.emit({
303
371
  action: "multiRemove",
304
372
  timestamp: new Date(),
305
373
  data: {
306
- keys: filteredKeys
374
+ keys: filteredKeys,
375
+ prevPairs
307
376
  }
308
377
  });
309
- } else {
310
- // All keys in multiRemove are ignored
378
+ return result;
311
379
  }
312
380
  return this.originalMultiRemove ? this.originalMultiRemove(keys) : Promise.resolve();
313
381
  };
@@ -316,20 +384,32 @@ class AsyncStorageListener {
316
384
  // Swizzle multiMerge if available
317
385
  if (this.originalMultiMerge && AsyncStorage) {
318
386
  AsyncStorage.multiMerge = async keyValuePairs => {
319
- // Intercepted multiMerge operation with multiple pairs
320
-
321
387
  // Filter out ignored keys
322
388
  const filteredPairs = keyValuePairs.filter(([key]) => !this.shouldIgnoreKey(key));
323
389
  if (filteredPairs.length > 0) {
390
+ // Capture previous values for all keys being merged
391
+ const keysToMerge = filteredPairs.map(([key]) => key);
392
+ let prevPairs = [];
393
+ try {
394
+ const existingValues = await AsyncStorage.multiGet(keysToMerge);
395
+ prevPairs = existingValues;
396
+ } catch {
397
+ // Failed to capture previous values
398
+ }
399
+
400
+ // Execute the operation
401
+ const result = this.originalMultiMerge ? await this.originalMultiMerge(keyValuePairs) : undefined;
402
+
403
+ // Emit event with previous values
324
404
  this.emit({
325
405
  action: "multiMerge",
326
406
  timestamp: new Date(),
327
407
  data: {
328
- pairs: filteredPairs
408
+ pairs: filteredPairs,
409
+ prevPairs
329
410
  }
330
411
  });
331
- } else {
332
- // All keys in multiMerge are ignored
412
+ return result;
333
413
  }
334
414
  return this.originalMultiMerge ? this.originalMultiMerge(keyValuePairs) : Promise.resolve();
335
415
  };
@@ -435,6 +515,32 @@ class AsyncStorageListener {
435
515
  get listenerCount() {
436
516
  return this.listeners.length;
437
517
  }
518
+
519
+ /**
520
+ * Pause event emission (used during time-travel operations)
521
+ *
522
+ * When paused, storage operations still execute but events are not emitted.
523
+ * This prevents circular event triggering during replay/jump operations.
524
+ */
525
+ pause() {
526
+ this.isPaused = true;
527
+ }
528
+
529
+ /**
530
+ * Resume event emission after pausing
531
+ */
532
+ resume() {
533
+ this.isPaused = false;
534
+ }
535
+
536
+ /**
537
+ * Check if event emission is currently paused
538
+ *
539
+ * @returns True if paused, false otherwise
540
+ */
541
+ get paused() {
542
+ return this.isPaused;
543
+ }
438
544
  }
439
545
 
440
546
  /**
@@ -484,6 +590,26 @@ export const isListening = () => asyncStorageListener.isActive;
484
590
  */
485
591
  export const getListenerCount = () => asyncStorageListener.listenerCount;
486
592
 
593
+ /**
594
+ * Pause event emission (used during time-travel operations)
595
+ *
596
+ * When paused, storage operations still execute but events are not emitted.
597
+ * This prevents circular event triggering during replay/jump operations.
598
+ */
599
+ export const pauseCapture = () => asyncStorageListener.pause();
600
+
601
+ /**
602
+ * Resume event emission after pausing
603
+ */
604
+ export const resumeCapture = () => asyncStorageListener.resume();
605
+
606
+ /**
607
+ * Check if event emission is currently paused
608
+ *
609
+ * @returns True if paused, false otherwise
610
+ */
611
+ export const isPaused = () => asyncStorageListener.paused;
612
+
487
613
  /**
488
614
  * Export the singleton instance for advanced usage
489
615
  *
@@ -7,11 +7,14 @@ export { clearAllAppStorage } from "./clearAllStorage";
7
7
  export { isMMKVAvailable, getMMKVClass, getMMKVUnavailableMessage } from "./mmkvAvailability";
8
8
 
9
9
  // AsyncStorage Event Listener
10
- export { startListening, stopListening, addListener, removeAllListeners, isListening, getListenerCount } from "./AsyncStorageListener";
10
+ export { startListening, stopListening, addListener, removeAllListeners, isListening, getListenerCount, pauseCapture, resumeCapture, isPaused } from "./AsyncStorageListener";
11
11
 
12
12
  // Re-export default listener instance
13
13
  export { default as asyncStorageListener } from "./AsyncStorageListener";
14
14
 
15
+ // Storage Time-Travel Utilities
16
+ export { undoOperation, jumpToState, canUndo } from "./storageTimeTravelUtils";
17
+
15
18
  // MMKV Instance Registry
16
19
  export { registerMMKVInstance, unregisterMMKVInstance, mmkvInstanceRegistry } from "./MMKVInstanceRegistry";
17
20
 
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Storage Time-Travel Utilities
5
+ *
6
+ * Provides jump and undo functionality for AsyncStorage operations.
7
+ */
8
+
9
+ import AsyncStorage from "@react-native-async-storage/async-storage";
10
+ import { pauseCapture, resumeCapture } from "./AsyncStorageListener";
11
+
12
+ /**
13
+ * Undo a single storage operation
14
+ *
15
+ * Restores the previous value(s) before the operation occurred.
16
+ * This pauses event capture to avoid creating new events during restoration.
17
+ *
18
+ * @param event - The storage event to undo
19
+ */
20
+ export async function undoOperation(event) {
21
+ const {
22
+ action,
23
+ data
24
+ } = event;
25
+
26
+ // Pause capture to avoid creating events during restoration
27
+ pauseCapture();
28
+ try {
29
+ switch (action) {
30
+ case "setItem":
31
+ case "mergeItem":
32
+ if (data?.key) {
33
+ if (data.prevValue === null || data.prevValue === undefined) {
34
+ // Key didn't exist before, remove it
35
+ await AsyncStorage.removeItem(data.key);
36
+ } else {
37
+ // Restore previous value
38
+ await AsyncStorage.setItem(data.key, data.prevValue);
39
+ }
40
+ }
41
+ break;
42
+ case "removeItem":
43
+ if (data?.key && data.prevValue !== null && data.prevValue !== undefined) {
44
+ // Restore the removed value
45
+ await AsyncStorage.setItem(data.key, data.prevValue);
46
+ }
47
+ break;
48
+ case "multiSet":
49
+ case "multiMerge":
50
+ if (data?.prevPairs && data.prevPairs.length > 0) {
51
+ // Restore all previous values
52
+ for (const [key, value] of data.prevPairs) {
53
+ if (value === null) {
54
+ await AsyncStorage.removeItem(key);
55
+ } else {
56
+ await AsyncStorage.setItem(key, value);
57
+ }
58
+ }
59
+ }
60
+ break;
61
+ case "multiRemove":
62
+ if (data?.prevPairs && data.prevPairs.length > 0) {
63
+ // Restore all removed values
64
+ for (const [key, value] of data.prevPairs) {
65
+ if (value !== null) {
66
+ await AsyncStorage.setItem(key, value);
67
+ }
68
+ }
69
+ }
70
+ break;
71
+ case "clear":
72
+ if (data?.prevPairs && data.prevPairs.length > 0) {
73
+ // Restore all cleared key-value pairs
74
+ const pairsToRestore = data.prevPairs.filter(([, value]) => value !== null);
75
+ if (pairsToRestore.length > 0) {
76
+ await AsyncStorage.multiSet(pairsToRestore);
77
+ }
78
+ }
79
+ break;
80
+ default:
81
+ throw new Error(`Cannot undo unknown action type: ${action}`);
82
+ }
83
+ } finally {
84
+ // Always resume capture, even if an error occurred
85
+ resumeCapture();
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Jump to a specific point in storage history
91
+ *
92
+ * This reconstructs the storage state as it was after a specific event
93
+ * by clearing storage and replaying all events up to that point.
94
+ *
95
+ * Note: This is a destructive operation - it will clear current storage
96
+ * and replay events to reach the target state.
97
+ *
98
+ * @param events - All events in chronological order (oldest first)
99
+ * @param targetEventIndex - The index of the event to jump to (0-based)
100
+ */
101
+ export async function jumpToState(events, targetEventIndex) {
102
+ if (targetEventIndex < 0 || targetEventIndex >= events.length) {
103
+ throw new Error(`Invalid event index: ${targetEventIndex}`);
104
+ }
105
+
106
+ // Pause capture to avoid creating events during replay
107
+ pauseCapture();
108
+ try {
109
+ // Clear all storage first
110
+ await AsyncStorage.clear();
111
+
112
+ // Build up the state by applying each event up to and including the target
113
+ // We use the final values from each event, not the operations themselves
114
+ const stateMap = new Map();
115
+ for (let i = 0; i <= targetEventIndex; i++) {
116
+ const event = events[i];
117
+ applyEventToStateMap(event, stateMap);
118
+ }
119
+
120
+ // Apply the final state
121
+ const pairsToSet = [];
122
+ for (const [key, value] of stateMap) {
123
+ if (value !== null) {
124
+ pairsToSet.push([key, value]);
125
+ }
126
+ }
127
+ if (pairsToSet.length > 0) {
128
+ await AsyncStorage.multiSet(pairsToSet);
129
+ }
130
+ } finally {
131
+ // Always resume capture
132
+ resumeCapture();
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Helper to apply an event's effects to a state map
138
+ */
139
+ function applyEventToStateMap(event, stateMap) {
140
+ const {
141
+ action,
142
+ data
143
+ } = event;
144
+ switch (action) {
145
+ case "setItem":
146
+ if (data?.key && data?.value !== undefined) {
147
+ stateMap.set(data.key, data.value);
148
+ }
149
+ break;
150
+ case "removeItem":
151
+ if (data?.key) {
152
+ stateMap.set(data.key, null);
153
+ }
154
+ break;
155
+ case "mergeItem":
156
+ if (data?.key && data?.value !== undefined) {
157
+ const existing = stateMap.get(data.key);
158
+ if (existing) {
159
+ try {
160
+ // Merge JSON objects
161
+ const existingObj = JSON.parse(existing);
162
+ const newObj = JSON.parse(data.value);
163
+ const merged = {
164
+ ...existingObj,
165
+ ...newObj
166
+ };
167
+ stateMap.set(data.key, JSON.stringify(merged));
168
+ } catch {
169
+ // If not valid JSON, just overwrite
170
+ stateMap.set(data.key, data.value);
171
+ }
172
+ } else {
173
+ stateMap.set(data.key, data.value);
174
+ }
175
+ }
176
+ break;
177
+ case "multiSet":
178
+ if (data?.pairs) {
179
+ for (const [key, value] of data.pairs) {
180
+ stateMap.set(key, value);
181
+ }
182
+ }
183
+ break;
184
+ case "multiRemove":
185
+ if (data?.keys) {
186
+ for (const key of data.keys) {
187
+ stateMap.set(key, null);
188
+ }
189
+ }
190
+ break;
191
+ case "multiMerge":
192
+ if (data?.pairs) {
193
+ for (const [key, value] of data.pairs) {
194
+ const existing = stateMap.get(key);
195
+ if (existing) {
196
+ try {
197
+ const existingObj = JSON.parse(existing);
198
+ const newObj = JSON.parse(value);
199
+ const merged = {
200
+ ...existingObj,
201
+ ...newObj
202
+ };
203
+ stateMap.set(key, JSON.stringify(merged));
204
+ } catch {
205
+ stateMap.set(key, value);
206
+ }
207
+ } else {
208
+ stateMap.set(key, value);
209
+ }
210
+ }
211
+ }
212
+ break;
213
+ case "clear":
214
+ // Clear the entire state map
215
+ stateMap.clear();
216
+ break;
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Check if an operation can be undone
222
+ *
223
+ * Some operations may not have the previous value captured.
224
+ */
225
+ export function canUndo(event) {
226
+ const {
227
+ action,
228
+ data
229
+ } = event;
230
+ switch (action) {
231
+ case "setItem":
232
+ case "mergeItem":
233
+ case "removeItem":
234
+ // Can undo if we have the key
235
+ return !!data?.key;
236
+ case "multiSet":
237
+ case "multiRemove":
238
+ case "multiMerge":
239
+ case "clear":
240
+ // Can undo if we have previous pairs
241
+ return !!data?.prevPairs && data.prevPairs.length > 0;
242
+ default:
243
+ return false;
244
+ }
245
+ }
@@ -1,3 +1,38 @@
1
+ /**
2
+ * @buoy-gg/storage
3
+ *
4
+ * Storage monitoring tool for React Native DevTools.
5
+ * Supports AsyncStorage and MMKV.
6
+ *
7
+ * PUBLIC API - Only these exports are supported for external use.
8
+ * Internal listeners and capture controls are not exported to prevent
9
+ * bypassing the tool's intended usage patterns.
10
+ */
1
11
  export { storageToolPreset, createStorageTool } from "./preset";
2
- export * from "./storage";
12
+ export { StorageSection } from "./storage/components/StorageSection";
13
+ export { StorageModalWithTabs } from "./storage/components/StorageModalWithTabs";
14
+ export { StorageKeyCard } from "./storage/components/StorageKeyCard";
15
+ export { StorageKeyStatsSection } from "./storage/components/StorageKeyStats";
16
+ export { StorageKeySection } from "./storage/components/StorageKeySection";
17
+ export { StorageBrowserMode } from "./storage/components/StorageBrowserMode";
18
+ export { StorageEventsSection } from "./storage/components/StorageEventsSection";
19
+ export { StorageEventCard, getValueType, type StorageEventCardData, type StorageValueType, } from "./storage/components/StorageEventCard";
20
+ export { StorageEventDetailContent, StorageEventDetailFooter, } from "./storage/components/StorageEventDetailContent";
21
+ export { MMKVInstanceSelector } from "./storage/components/MMKVInstanceSelector";
22
+ export { MMKVInstanceInfoPanel } from "./storage/components/MMKVInstanceInfoPanel";
23
+ export { useAsyncStorageKeys } from "./storage/hooks/useAsyncStorageKeys";
24
+ export { useMMKVKeys, useMultiMMKVKeys } from "./storage/hooks/useMMKVKeys";
25
+ export { useMMKVInstances, useMMKVInstance, useMMKVInstanceExists, } from "./storage/hooks/useMMKVInstances";
26
+ export { useStorageEvents, type UseStorageEventsOptions, type UseStorageEventsResult, } from "./storage/hooks/useStorageEvents";
27
+ export type { StorageEvent, StorageEventListener, StorageEventCallback, } from "./storage/stores/storageEventStore";
28
+ export { clearAllAppStorage } from "./storage/utils/clearAllStorage";
29
+ export { isMMKVAvailable, getMMKVClass, getMMKVUnavailableMessage, } from "./storage/utils/mmkvAvailability";
30
+ export { detectMMKVType, formatMMKVValue, isTypeMatch, type MMKVValueType, type MMKVValueInfo, } from "./storage/utils/mmkvTypeDetection";
31
+ export { undoOperation, jumpToState, canUndo, } from "./storage/utils/storageTimeTravelUtils";
32
+ export type { AsyncStorageEvent, AsyncStorageEventListener, } from "./storage/utils/AsyncStorageListener";
33
+ export type { MMKVEvent, MMKVEventListener, } from "./storage/utils/MMKVListener";
34
+ export type { MMKVInstanceInfo } from "./storage/utils/MMKVInstanceRegistry";
35
+ export { registerMMKVInstance, unregisterMMKVInstance, } from "./storage/utils/MMKVInstanceRegistry";
36
+ /** @internal */
37
+ export { storageEventStore } from "./storage/stores/storageEventStore";
3
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAGhE,cAAc,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKhE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAKnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,GAC5B,MAAM,kCAAkC,CAAC;AAK1C,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,oCAAoC,CAAC;AAK5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACL,eAAe,EACf,YAAY,EACZ,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,cAAc,EACd,eAAe,EACf,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,GACR,MAAM,wCAAwC,CAAC;AAChD,YAAY,EACV,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,SAAS,EACT,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAK7E,OAAO,EACL,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,sCAAsC,CAAC;AAK9C,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC"}
@@ -50,7 +50,7 @@ export interface DiffTheme {
50
50
  export declare const gitClassicTheme: DiffTheme;
51
51
  /**
52
52
  * Dev Tools Default Theme
53
- * Clean dark theme using our gameUIColors
53
+ * Clean dark theme matching Buoy website brand colors
54
54
  */
55
55
  export declare const devToolsDefaultTheme: DiffTheme;
56
56
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"diffThemes.d.ts","sourceRoot":"","sources":["../../../../../../src/storage/components/DiffViewer/themes/diffThemes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IAGpB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IAGzB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAG1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IAGtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAG7B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IAGzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IAGnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IAGrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAG5B,cAAc,EAAE,MAAM,CAAC;IAGvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,SA2C7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAqDlC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU;;;CAGb,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,UAAU,CAAC"}
1
+ {"version":3,"file":"diffThemes.d.ts","sourceRoot":"","sources":["../../../../../../src/storage/components/DiffViewer/themes/diffThemes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IAGpB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IAGzB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAG1B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IAGtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oBAAoB,EAAE,MAAM,CAAC;IAG7B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,MAAM,CAAC;IAGzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IAGnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IAGrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAG5B,cAAc,EAAE,MAAM,CAAC;IAGvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,SA2C7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAwDlC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU;;;CAGb,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,UAAU,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"GameUIStorageBrowser.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/GameUIStorageBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,gBAAgB,EACjB,MAAM,OAAO,CAAC;AASf,OAAO,EAAkB,kBAAkB,EAAmB,MAAM,UAAU,CAAC;AAsE/E,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,UAAU,yBAAyB;IACjC,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC;CAC1C;AAED,wBAAgB,oBAAoB,CAAC,EACnC,mBAAwB,EACxB,WAAmB,EACnB,eAA0C,EAC1C,eAAe,EACf,YAAY,EACZ,WAAgB,EAChB,cAAc,GACf,EAAE,yBAAyB,+BAuwB3B"}
1
+ {"version":3,"file":"GameUIStorageBrowser.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/GameUIStorageBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,gBAAgB,EACjB,MAAM,OAAO,CAAC;AASf,OAAO,EAAkB,kBAAkB,EAAmB,MAAM,UAAU,CAAC;AAgD/E,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,UAAU,yBAAyB;IACjC,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC;CAC1C;AAED,wBAAgB,oBAAoB,CAAC,EACnC,mBAAwB,EACxB,WAAmB,EACnB,eAA0C,EAC1C,eAAe,EACf,YAAY,EACZ,WAAgB,EAChB,cAAc,GACf,EAAE,yBAAyB,+BA6wB3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"SelectionActionBar.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/SelectionActionBar.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AA0B1C,UAAU,uBAAuB;IAC/B,4BAA4B;IAC5B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,kCAAkC;IAClC,aAAa,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACpD,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,mCAAmC;IACnC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,EAAE,uBAAuB,sCAuIzB"}
1
+ {"version":3,"file":"SelectionActionBar.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/SelectionActionBar.tsx"],"names":[],"mappings":"AAKA,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1C,UAAU,uBAAuB;IAC/B,4BAA4B;IAC5B,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,kCAAkC;IAClC,aAAa,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACpD,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,kCAAkC;IAClC,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,mCAAmC;IACnC,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,gBAAgB,GACjB,EAAE,uBAAuB,sCA2IzB"}
@@ -1,8 +1,6 @@
1
1
  interface StorageActionButtonsProps {
2
2
  /** Value to copy to clipboard */
3
3
  copyValue: unknown;
4
- /** @deprecated Use copyValue instead */
5
- onCopy?: () => void;
6
4
  mmkvInstances?: Array<{
7
5
  id: string;
8
6
  instance: any;
@@ -1 +1 @@
1
- {"version":3,"file":"StorageActionButtons.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/StorageActionButtons.tsx"],"names":[],"mappings":"AA6BA,UAAU,yBAAyB;IACjC,iCAAiC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACrD,iBAAiB,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACxD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,uCAAuC;IACvC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wCAAwC;IACxC,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,+BAA+B;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,aAAkB,EAClB,iBAAyB,EACzB,eAAe,EACf,YAAoB,EACpB,kBAAkB,EAClB,aAAiB,GAClB,EAAE,yBAAyB,+BA6M3B"}
1
+ {"version":3,"file":"StorageActionButtons.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/StorageActionButtons.tsx"],"names":[],"mappings":"AAOA,UAAU,yBAAyB;IACjC,iCAAiC;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE,CAAC,CAAC;IACrD,iBAAiB,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IACxD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,uCAAuC;IACvC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,wCAAwC;IACxC,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,+BAA+B;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,oBAAoB,CAAC,EACnC,SAAS,EACT,aAAkB,EAClB,iBAAyB,EACzB,eAAe,EACf,YAAoB,EACpB,kBAAkB,EAClB,aAAiB,GAClB,EAAE,yBAAyB,+BAiN3B"}