@buoy-gg/storage 2.1.2 → 2.1.3

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.
@@ -11,7 +11,6 @@ var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/
11
11
  var _sharedUi = require("@buoy-gg/shared-ui");
12
12
  var _StorageBrowserMode = require("./StorageBrowserMode");
13
13
  var _clearAllStorage = require("../utils/clearAllStorage");
14
- var _AsyncStorageListener = require("../utils/AsyncStorageListener");
15
14
  var _useStorageEvents = require("../hooks/useStorageEvents");
16
15
  var _StorageEventDetailContent = require("./StorageEventDetailContent");
17
16
  var _StorageFilterViewV = require("./StorageFilterViewV2");
@@ -44,17 +43,19 @@ function StorageModalWithTabs({
44
43
  const [isSearchActive, setIsSearchActive] = (0, _react.useState)(false);
45
44
  const hasLoadedStorageFilters = (0, _react.useRef)(false);
46
45
 
46
+ // Local state to control subscription (true = subscribed, false = unsubscribed)
47
+ const [isListeningEnabled, setIsListeningEnabled] = (0, _react.useState)(true);
48
+
47
49
  // Event Listener state - using centralized store via hook
50
+ // Pass enabled option to control subscription - this allows events tool to
51
+ // continue receiving events while storage UI has toggled off
48
52
  const {
49
53
  events,
50
54
  clearEvents: clearStorageEvents,
51
- isCapturing,
52
- startCapturing,
53
- stopCapturing
55
+ isCapturing
54
56
  } = (0, _useStorageEvents.useStorageEvents)({
55
- autoStart: false
56
- }); // Don't auto-start, we manage it
57
- const [isListening, setIsListening] = (0, _react.useState)(false);
57
+ enabled: isListeningEnabled
58
+ });
58
59
  const [selectedConversationKey, setSelectedConversationKey] = (0, _react.useState)(null);
59
60
  const [selectedEventIndex, setSelectedEventIndex] = (0, _react.useState)(0);
60
61
  const [showFilters, setShowFilters] = (0, _react.useState)(false);
@@ -67,10 +68,8 @@ function StorageModalWithTabs({
67
68
  const hasLoadedTabState = (0, _react.useRef)(false);
68
69
  const hasLoadedMonitoringState = (0, _react.useRef)(false);
69
70
 
70
- // Sync isListening state with hook's isCapturing
71
- (0, _react.useEffect)(() => {
72
- setIsListening(isCapturing);
73
- }, [isCapturing]);
71
+ // isListening = enabled and capturing (has subscribers)
72
+ const isListening = isListeningEnabled && isCapturing;
74
73
  const handleModeChange = (0, _react.useCallback)(_mode => {
75
74
  // Mode changes handled by JsModal
76
75
  }, []);
@@ -102,9 +101,7 @@ function StorageModalWithTabs({
102
101
  const storedMonitoring = await _asyncStorage.default.getItem(_sharedUi.devToolsStorageKeys.storage.isMonitoring());
103
102
  if (storedMonitoring !== null) {
104
103
  const shouldMonitor = storedMonitoring === "true";
105
- if (shouldMonitor && !isCapturing) {
106
- await startCapturing();
107
- }
104
+ setIsListeningEnabled(shouldMonitor);
108
105
  }
109
106
  hasLoadedMonitoringState.current = true;
110
107
  } catch (error) {
@@ -112,7 +109,7 @@ function StorageModalWithTabs({
112
109
  }
113
110
  };
114
111
  loadMonitoringState();
115
- }, [visible, isCapturing, startCapturing]);
112
+ }, [visible]);
116
113
 
117
114
  // Note: Conversations will appear when storage events are triggered
118
115
  // Click on any conversation to see the unified view with toggle cards
@@ -181,14 +178,10 @@ function StorageModalWithTabs({
181
178
  // Event listener setup - now handled by useStorageEvents hook
182
179
  // The hook subscribes to the centralized storageEventStore
183
180
 
184
- const handleToggleListening = (0, _react.useCallback)(async () => {
185
- if (isListening) {
186
- stopCapturing();
187
- (0, _AsyncStorageListener.stopListening)(); // Also stop the underlying AsyncStorage listener
188
- } else {
189
- await startCapturing();
190
- }
191
- }, [isListening, startCapturing, stopCapturing]);
181
+ const handleToggleListening = (0, _react.useCallback)(() => {
182
+ // Toggle subscription - this actually subscribes/unsubscribes from the store
183
+ setIsListeningEnabled(prev => !prev);
184
+ }, []);
192
185
  const handleClearEvents = (0, _react.useCallback)(() => {
193
186
  clearStorageEvents();
194
187
  setSelectedConversationKey(null);
@@ -10,7 +10,8 @@ var _storageEventStore = require("../stores/storageEventStore");
10
10
  * useStorageEvents Hook
11
11
  *
12
12
  * React hook for subscribing to storage events from the centralized store.
13
- * Provides a clean interface for components to receive storage events.
13
+ * Uses self-managing Subscribable pattern - the storage listener automatically
14
+ * starts when this hook mounts and stops when all subscribers unmount.
14
15
  *
15
16
  * @example
16
17
  * ```typescript
@@ -36,14 +37,21 @@ function useStorageEvents(options = {}) {
36
37
  const {
37
38
  storageType = "all",
38
39
  maxEvents = 500,
39
- autoStart = true
40
+ enabled = true
40
41
  } = options;
41
42
  const [events, setEvents] = (0, _react.useState)([]);
42
- const [isCapturing, setIsCapturing] = (0, _react.useState)(_storageEventStore.storageEventStore.capturing);
43
+ // Track capturing state locally so it updates reactively with the subscription
44
+ const [isCapturing, setIsCapturing] = (0, _react.useState)(enabled);
43
45
 
44
- // Subscribe to store changes
46
+ // Subscribe to store - this automatically starts/stops the storage listener
47
+ // based on subscriber count (Subscribable pattern from TanStack Query)
48
+ // Only subscribe when enabled is true
45
49
  (0, _react.useEffect)(() => {
46
- const unsubscribe = _storageEventStore.storageEventStore.subscribe(allEvents => {
50
+ if (!enabled) {
51
+ setIsCapturing(false);
52
+ return;
53
+ }
54
+ const unsubscribe = _storageEventStore.storageEventStore.subscribeToEvents(allEvents => {
47
55
  // Filter by storage type if specified
48
56
  let filtered = allEvents;
49
57
  if (storageType !== "all") {
@@ -55,44 +63,22 @@ function useStorageEvents(options = {}) {
55
63
  filtered = filtered.slice(0, maxEvents);
56
64
  }
57
65
  setEvents(filtered);
58
- setIsCapturing(_storageEventStore.storageEventStore.capturing);
59
66
  });
67
+
68
+ // Mark as capturing after successful subscription
69
+ setIsCapturing(true);
60
70
  return () => {
61
71
  unsubscribe();
72
+ // Note: Don't set isCapturing=false here because we might be
73
+ // re-subscribing due to dependency change, not actually stopping
62
74
  };
63
- }, [storageType, maxEvents]);
64
-
65
- // Auto-start capturing on mount
66
- (0, _react.useEffect)(() => {
67
- if (autoStart && !_storageEventStore.storageEventStore.capturing) {
68
- _storageEventStore.storageEventStore.startCapturing();
69
- setIsCapturing(true);
70
- }
71
- }, [autoStart]);
75
+ }, [storageType, maxEvents, enabled]);
72
76
  const clearEvents = (0, _react.useCallback)(() => {
73
77
  _storageEventStore.storageEventStore.clearEvents();
74
78
  }, []);
75
- const startCapturing = (0, _react.useCallback)(async () => {
76
- await _storageEventStore.storageEventStore.startCapturing();
77
- setIsCapturing(true);
78
- }, []);
79
- const stopCapturing = (0, _react.useCallback)(() => {
80
- _storageEventStore.storageEventStore.stopCapturing();
81
- setIsCapturing(false);
82
- }, []);
83
- const pauseCapturing = (0, _react.useCallback)(() => {
84
- _storageEventStore.storageEventStore.pauseCapture();
85
- }, []);
86
- const resumeCapturing = (0, _react.useCallback)(() => {
87
- _storageEventStore.storageEventStore.resumeCapture();
88
- }, []);
89
79
  return {
90
80
  events,
91
81
  clearEvents,
92
- isCapturing,
93
- startCapturing,
94
- stopCapturing,
95
- pauseCapturing,
96
- resumeCapturing
82
+ isCapturing
97
83
  };
98
84
  }
@@ -25,10 +25,6 @@ var _exportNames = {
25
25
  MMKVInstanceSelector: true,
26
26
  MMKVInstanceInfoPanel: true,
27
27
  storageEventStore: true,
28
- startStorageCapture: true,
29
- stopStorageCapture: true,
30
- pauseStorageCapture: true,
31
- resumeStorageCapture: true,
32
28
  subscribeToStorageEvents: true,
33
29
  onStorageEvent: true,
34
30
  getStorageEvents: true,
@@ -137,30 +133,6 @@ Object.defineProperty(exports, "onStorageEvent", {
137
133
  return _storageEventStore.onStorageEvent;
138
134
  }
139
135
  });
140
- Object.defineProperty(exports, "pauseStorageCapture", {
141
- enumerable: true,
142
- get: function () {
143
- return _storageEventStore.pauseStorageCapture;
144
- }
145
- });
146
- Object.defineProperty(exports, "resumeStorageCapture", {
147
- enumerable: true,
148
- get: function () {
149
- return _storageEventStore.resumeStorageCapture;
150
- }
151
- });
152
- Object.defineProperty(exports, "startStorageCapture", {
153
- enumerable: true,
154
- get: function () {
155
- return _storageEventStore.startStorageCapture;
156
- }
157
- });
158
- Object.defineProperty(exports, "stopStorageCapture", {
159
- enumerable: true,
160
- get: function () {
161
- return _storageEventStore.stopStorageCapture;
162
- }
163
- });
164
136
  Object.defineProperty(exports, "storageEventStore", {
165
137
  enumerable: true,
166
138
  get: function () {
@@ -3,29 +3,28 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.subscribeToStorageEvents = exports.storageEventStore = exports.stopStorageCapture = exports.startStorageCapture = exports.resumeStorageCapture = exports.pauseStorageCapture = exports.onStorageEvent = exports.isStorageCapturing = exports.getStorageEvents = exports.clearStorageEvents = void 0;
6
+ exports.subscribeToStorageEvents = exports.storageEventStore = exports.onStorageEvent = exports.isStorageCapturing = exports.getStorageEvents = exports.clearStorageEvents = void 0;
7
+ var _sharedUi = require("@buoy-gg/shared-ui");
7
8
  var _AsyncStorageListener = require("../utils/AsyncStorageListener");
8
9
  var _MMKVListener = require("../utils/MMKVListener");
9
10
  /**
10
11
  * Storage Event Store
11
12
  *
12
13
  * Centralized store that aggregates storage events from AsyncStorage and MMKV
13
- * into a single event stream. This provides a single source of truth for all
14
- * storage operations, eliminating duplicate subscriptions across components.
14
+ * into a single event stream. Uses BaseEventStore pattern - the storage listener
15
+ * automatically starts when the first subscriber joins and stops when the last
16
+ * subscriber leaves.
15
17
  *
16
18
  * @example
17
19
  * ```typescript
18
20
  * import { storageEventStore } from '@buoy-gg/storage';
19
21
  *
20
- * // Subscribe to storage events
21
- * const unsubscribe = storageEventStore.subscribe((events) => {
22
+ * // Subscribe to storage events - automatically starts capturing!
23
+ * const unsubscribe = storageEventStore.subscribeToEvents((events) => {
22
24
  * console.log('Storage events:', events);
23
25
  * });
24
26
  *
25
- * // Start capturing (must be called once)
26
- * await storageEventStore.startCapturing();
27
- *
28
- * // Later, clean up
27
+ * // Later, clean up - automatically stops if no other subscribers
29
28
  * unsubscribe();
30
29
  * ```
31
30
  */
@@ -35,65 +34,60 @@ var _MMKVListener = require("../utils/MMKVListener");
35
34
  */
36
35
 
37
36
  /**
38
- * Listener callback type for storage events
37
+ * Listener callback type for storage events array
39
38
  */
40
39
 
41
40
  /**
42
41
  * Listener callback for individual new events
43
42
  */
44
43
 
45
- const MAX_EVENTS = 500;
46
- class StorageEventStore {
47
- events = [];
48
- listeners = new Set();
49
- eventCallbacks = new Set();
50
- isCapturing = false;
51
-
52
- // Unsubscribe functions
44
+ class StorageEventStore extends _sharedUi.BaseEventStore {
45
+ // Unsubscribe functions for raw listeners
53
46
  asyncStorageUnsubscribe = null;
54
47
  mmkvUnsubscribe = null;
48
+ constructor() {
49
+ super({
50
+ storeName: "storage",
51
+ maxEvents: 500
52
+ });
53
+ }
55
54
 
56
55
  /**
57
56
  * Start capturing storage events from both AsyncStorage and MMKV
58
57
  */
59
58
  async startCapturing() {
60
- if (this.isCapturing) {
61
- return;
62
- }
63
-
64
59
  // Start AsyncStorage listening if not already active
65
60
  if (!(0, _AsyncStorageListener.isListening)()) {
66
61
  await (0, _AsyncStorageListener.startListening)();
67
62
  }
68
63
 
69
64
  // Subscribe to AsyncStorage events
70
- this.asyncStorageUnsubscribe = (0, _AsyncStorageListener.addListener)(event => {
71
- const storageEvent = {
72
- ...event,
73
- storageType: "async"
74
- };
75
- this.addEvent(storageEvent);
76
- });
65
+ if (!this.asyncStorageUnsubscribe) {
66
+ this.asyncStorageUnsubscribe = (0, _AsyncStorageListener.addListener)(event => {
67
+ const storageEvent = {
68
+ ...event,
69
+ storageType: "async"
70
+ };
71
+ this.addEvent(storageEvent);
72
+ });
73
+ }
77
74
 
78
75
  // Subscribe to MMKV events
79
- this.mmkvUnsubscribe = (0, _MMKVListener.addMMKVListener)(event => {
80
- const storageEvent = {
81
- ...event,
82
- storageType: "mmkv"
83
- };
84
- this.addEvent(storageEvent);
85
- });
86
- this.isCapturing = true;
76
+ if (!this.mmkvUnsubscribe) {
77
+ this.mmkvUnsubscribe = (0, _MMKVListener.addMMKVListener)(event => {
78
+ const storageEvent = {
79
+ ...event,
80
+ storageType: "mmkv"
81
+ };
82
+ this.addEvent(storageEvent);
83
+ });
84
+ }
87
85
  }
88
86
 
89
87
  /**
90
88
  * Stop capturing storage events
91
89
  */
92
90
  stopCapturing() {
93
- if (!this.isCapturing) {
94
- return;
95
- }
96
-
97
91
  // Unsubscribe from AsyncStorage
98
92
  if (this.asyncStorageUnsubscribe) {
99
93
  this.asyncStorageUnsubscribe();
@@ -105,88 +99,13 @@ class StorageEventStore {
105
99
  this.mmkvUnsubscribe();
106
100
  this.mmkvUnsubscribe = null;
107
101
  }
108
- this.isCapturing = false;
109
- }
110
-
111
- /**
112
- * Pause event capture (used during time-travel operations)
113
- */
114
- pauseCapture() {
115
- (0, _AsyncStorageListener.pauseCapture)();
116
- // MMKV listener doesn't have pause, but it's less common for time-travel
117
- }
118
-
119
- /**
120
- * Resume event capture after pausing
121
- */
122
- resumeCapture() {
123
- (0, _AsyncStorageListener.resumeCapture)();
124
- }
125
-
126
- /**
127
- * Add an event to the store
128
- */
129
- addEvent(event) {
130
- // Add to beginning (newest first)
131
- this.events = [event, ...this.events].slice(0, MAX_EVENTS);
132
-
133
- // Notify event callbacks (for individual events)
134
- this.eventCallbacks.forEach(callback => {
135
- try {
136
- callback(event);
137
- } catch {
138
- // Ignore callback errors
139
- }
140
- });
141
-
142
- // Notify listeners (for full event list)
143
- this.notifyListeners();
144
102
  }
145
103
 
146
104
  /**
147
- * Subscribe to all storage events (receives full event array on each change)
105
+ * Check if currently capturing events (has subscribers)
148
106
  */
149
- subscribe(listener) {
150
- this.listeners.add(listener);
151
-
152
- // Immediately call with current events
153
- listener(this.events);
154
-
155
- // Return unsubscribe function
156
- return () => {
157
- this.listeners.delete(listener);
158
- };
159
- }
160
-
161
- /**
162
- * Subscribe to new events only (receives individual events as they occur)
163
- */
164
- onEvent(callback) {
165
- this.eventCallbacks.add(callback);
166
- return () => {
167
- this.eventCallbacks.delete(callback);
168
- };
169
- }
170
-
171
- /**
172
- * Notify all listeners of changes
173
- */
174
- notifyListeners() {
175
- const events = this.events;
176
- this.listeners.forEach(listener => {
177
- try {
178
- listener(events);
179
- } catch {
180
- // Ignore listener errors
181
- }
182
- });
183
- }
184
-
185
- /**
186
- * Get all events
187
- */
188
- getEvents() {
189
- return this.events;
107
+ isCapturing() {
108
+ return this.asyncStorageUnsubscribe !== null || this.mmkvUnsubscribe !== null;
190
109
  }
191
110
 
192
111
  /**
@@ -195,43 +114,13 @@ class StorageEventStore {
195
114
  getEventsByType(storageType) {
196
115
  return this.events.filter(event => event.storageType === storageType);
197
116
  }
198
-
199
- /**
200
- * Get event count
201
- */
202
- getEventCount() {
203
- return this.events.length;
204
- }
205
-
206
- /**
207
- * Clear all events
208
- */
209
- clearEvents() {
210
- this.events = [];
211
- this.notifyListeners();
212
- }
213
-
214
- /**
215
- * Check if currently capturing events
216
- */
217
- get capturing() {
218
- return this.isCapturing;
219
- }
220
117
  }
221
118
 
222
119
  // Singleton instance
223
120
  const storageEventStore = exports.storageEventStore = new StorageEventStore();
224
121
 
225
122
  // Convenience exports
226
- const startStorageCapture = () => storageEventStore.startCapturing();
227
- exports.startStorageCapture = startStorageCapture;
228
- const stopStorageCapture = () => storageEventStore.stopCapturing();
229
- exports.stopStorageCapture = stopStorageCapture;
230
- const pauseStorageCapture = () => storageEventStore.pauseCapture();
231
- exports.pauseStorageCapture = pauseStorageCapture;
232
- const resumeStorageCapture = () => storageEventStore.resumeCapture();
233
- exports.resumeStorageCapture = resumeStorageCapture;
234
- const subscribeToStorageEvents = listener => storageEventStore.subscribe(listener);
123
+ const subscribeToStorageEvents = listener => storageEventStore.subscribeToEvents(listener);
235
124
  exports.subscribeToStorageEvents = subscribeToStorageEvents;
236
125
  const onStorageEvent = callback => storageEventStore.onEvent(callback);
237
126
  exports.onStorageEvent = onStorageEvent;
@@ -239,5 +128,5 @@ const getStorageEvents = () => storageEventStore.getEvents();
239
128
  exports.getStorageEvents = getStorageEvents;
240
129
  const clearStorageEvents = () => storageEventStore.clearEvents();
241
130
  exports.clearStorageEvents = clearStorageEvents;
242
- const isStorageCapturing = () => storageEventStore.capturing;
131
+ const isStorageCapturing = () => storageEventStore.isCapturing();
243
132
  exports.isStorageCapturing = isStorageCapturing;
@@ -65,7 +65,6 @@ export { storageEventStore } from "./storage/stores/storageEventStore";
65
65
  // NOT EXPORTED (Internal capture controls)
66
66
  // =============================================================================
67
67
  // The following are intentionally NOT exported to prevent bypassing:
68
- // - startStorageCapture, stopStorageCapture, pauseStorageCapture, resumeStorageCapture
69
68
  // - subscribeToStorageEvents, onStorageEvent, getStorageEvents, clearStorageEvents
70
69
  // - isStorageCapturing
71
70
  // - startListening, stopListening, addListener, removeAllListeners (AsyncStorage)
@@ -7,7 +7,6 @@ import { JsModal, ModalHeader, TabSelector, parseValue, devToolsStorageKeys, mac
7
7
  import { StorageBrowserMode } from "./StorageBrowserMode";
8
8
  import { clearAllAppStorage, clearAllStorageIncludingDevTools } from "../utils/clearAllStorage";
9
9
  import { ProFeatureBanner } from "@buoy-gg/shared-ui";
10
- import { stopListening } from "../utils/AsyncStorageListener";
11
10
  import { useStorageEvents } from "../hooks/useStorageEvents";
12
11
  import { StorageEventDetailContent, StorageEventDetailFooter } from "./StorageEventDetailContent";
13
12
  import { StorageFilterViewV2 } from "./StorageFilterViewV2";
@@ -40,17 +39,19 @@ export function StorageModalWithTabs({
40
39
  const [isSearchActive, setIsSearchActive] = useState(false);
41
40
  const hasLoadedStorageFilters = useRef(false);
42
41
 
42
+ // Local state to control subscription (true = subscribed, false = unsubscribed)
43
+ const [isListeningEnabled, setIsListeningEnabled] = useState(true);
44
+
43
45
  // Event Listener state - using centralized store via hook
46
+ // Pass enabled option to control subscription - this allows events tool to
47
+ // continue receiving events while storage UI has toggled off
44
48
  const {
45
49
  events,
46
50
  clearEvents: clearStorageEvents,
47
- isCapturing,
48
- startCapturing,
49
- stopCapturing
51
+ isCapturing
50
52
  } = useStorageEvents({
51
- autoStart: false
52
- }); // Don't auto-start, we manage it
53
- const [isListening, setIsListening] = useState(false);
53
+ enabled: isListeningEnabled
54
+ });
54
55
  const [selectedConversationKey, setSelectedConversationKey] = useState(null);
55
56
  const [selectedEventIndex, setSelectedEventIndex] = useState(0);
56
57
  const [showFilters, setShowFilters] = useState(false);
@@ -63,10 +64,8 @@ export function StorageModalWithTabs({
63
64
  const hasLoadedTabState = useRef(false);
64
65
  const hasLoadedMonitoringState = useRef(false);
65
66
 
66
- // Sync isListening state with hook's isCapturing
67
- useEffect(() => {
68
- setIsListening(isCapturing);
69
- }, [isCapturing]);
67
+ // isListening = enabled and capturing (has subscribers)
68
+ const isListening = isListeningEnabled && isCapturing;
70
69
  const handleModeChange = useCallback(_mode => {
71
70
  // Mode changes handled by JsModal
72
71
  }, []);
@@ -98,9 +97,7 @@ export function StorageModalWithTabs({
98
97
  const storedMonitoring = await AsyncStorage.getItem(devToolsStorageKeys.storage.isMonitoring());
99
98
  if (storedMonitoring !== null) {
100
99
  const shouldMonitor = storedMonitoring === "true";
101
- if (shouldMonitor && !isCapturing) {
102
- await startCapturing();
103
- }
100
+ setIsListeningEnabled(shouldMonitor);
104
101
  }
105
102
  hasLoadedMonitoringState.current = true;
106
103
  } catch (error) {
@@ -108,7 +105,7 @@ export function StorageModalWithTabs({
108
105
  }
109
106
  };
110
107
  loadMonitoringState();
111
- }, [visible, isCapturing, startCapturing]);
108
+ }, [visible]);
112
109
 
113
110
  // Note: Conversations will appear when storage events are triggered
114
111
  // Click on any conversation to see the unified view with toggle cards
@@ -177,14 +174,10 @@ export function StorageModalWithTabs({
177
174
  // Event listener setup - now handled by useStorageEvents hook
178
175
  // The hook subscribes to the centralized storageEventStore
179
176
 
180
- const handleToggleListening = useCallback(async () => {
181
- if (isListening) {
182
- stopCapturing();
183
- stopListening(); // Also stop the underlying AsyncStorage listener
184
- } else {
185
- await startCapturing();
186
- }
187
- }, [isListening, startCapturing, stopCapturing]);
177
+ const handleToggleListening = useCallback(() => {
178
+ // Toggle subscription - this actually subscribes/unsubscribes from the store
179
+ setIsListeningEnabled(prev => !prev);
180
+ }, []);
188
181
  const handleClearEvents = useCallback(() => {
189
182
  clearStorageEvents();
190
183
  setSelectedConversationKey(null);
@@ -4,7 +4,8 @@
4
4
  * useStorageEvents Hook
5
5
  *
6
6
  * React hook for subscribing to storage events from the centralized store.
7
- * Provides a clean interface for components to receive storage events.
7
+ * Uses self-managing Subscribable pattern - the storage listener automatically
8
+ * starts when this hook mounts and stops when all subscribers unmount.
8
9
  *
9
10
  * @example
10
11
  * ```typescript
@@ -33,14 +34,21 @@ export function useStorageEvents(options = {}) {
33
34
  const {
34
35
  storageType = "all",
35
36
  maxEvents = 500,
36
- autoStart = true
37
+ enabled = true
37
38
  } = options;
38
39
  const [events, setEvents] = useState([]);
39
- const [isCapturing, setIsCapturing] = useState(storageEventStore.capturing);
40
+ // Track capturing state locally so it updates reactively with the subscription
41
+ const [isCapturing, setIsCapturing] = useState(enabled);
40
42
 
41
- // Subscribe to store changes
43
+ // Subscribe to store - this automatically starts/stops the storage listener
44
+ // based on subscriber count (Subscribable pattern from TanStack Query)
45
+ // Only subscribe when enabled is true
42
46
  useEffect(() => {
43
- const unsubscribe = storageEventStore.subscribe(allEvents => {
47
+ if (!enabled) {
48
+ setIsCapturing(false);
49
+ return;
50
+ }
51
+ const unsubscribe = storageEventStore.subscribeToEvents(allEvents => {
44
52
  // Filter by storage type if specified
45
53
  let filtered = allEvents;
46
54
  if (storageType !== "all") {
@@ -52,44 +60,22 @@ export function useStorageEvents(options = {}) {
52
60
  filtered = filtered.slice(0, maxEvents);
53
61
  }
54
62
  setEvents(filtered);
55
- setIsCapturing(storageEventStore.capturing);
56
63
  });
64
+
65
+ // Mark as capturing after successful subscription
66
+ setIsCapturing(true);
57
67
  return () => {
58
68
  unsubscribe();
69
+ // Note: Don't set isCapturing=false here because we might be
70
+ // re-subscribing due to dependency change, not actually stopping
59
71
  };
60
- }, [storageType, maxEvents]);
61
-
62
- // Auto-start capturing on mount
63
- useEffect(() => {
64
- if (autoStart && !storageEventStore.capturing) {
65
- storageEventStore.startCapturing();
66
- setIsCapturing(true);
67
- }
68
- }, [autoStart]);
72
+ }, [storageType, maxEvents, enabled]);
69
73
  const clearEvents = useCallback(() => {
70
74
  storageEventStore.clearEvents();
71
75
  }, []);
72
- const startCapturing = useCallback(async () => {
73
- await storageEventStore.startCapturing();
74
- setIsCapturing(true);
75
- }, []);
76
- const stopCapturing = useCallback(() => {
77
- storageEventStore.stopCapturing();
78
- setIsCapturing(false);
79
- }, []);
80
- const pauseCapturing = useCallback(() => {
81
- storageEventStore.pauseCapture();
82
- }, []);
83
- const resumeCapturing = useCallback(() => {
84
- storageEventStore.resumeCapture();
85
- }, []);
86
76
  return {
87
77
  events,
88
78
  clearEvents,
89
- isCapturing,
90
- startCapturing,
91
- stopCapturing,
92
- pauseCapturing,
93
- resumeCapturing
79
+ isCapturing
94
80
  };
95
81
  }
@@ -28,4 +28,4 @@ export * from "./types";
28
28
  export * from "./utils";
29
29
 
30
30
  // Storage Event Store
31
- export { storageEventStore, startStorageCapture, stopStorageCapture, pauseStorageCapture, resumeStorageCapture, subscribeToStorageEvents, onStorageEvent, getStorageEvents, clearStorageEvents, isStorageCapturing } from "./stores/storageEventStore";
31
+ export { storageEventStore, subscribeToStorageEvents, onStorageEvent, getStorageEvents, clearStorageEvents, isStorageCapturing } from "./stores/storageEventStore";
@@ -4,27 +4,26 @@
4
4
  * Storage Event Store
5
5
  *
6
6
  * Centralized store that aggregates storage events from AsyncStorage and MMKV
7
- * into a single event stream. This provides a single source of truth for all
8
- * storage operations, eliminating duplicate subscriptions across components.
7
+ * into a single event stream. Uses BaseEventStore pattern - the storage listener
8
+ * automatically starts when the first subscriber joins and stops when the last
9
+ * subscriber leaves.
9
10
  *
10
11
  * @example
11
12
  * ```typescript
12
13
  * import { storageEventStore } from '@buoy-gg/storage';
13
14
  *
14
- * // Subscribe to storage events
15
- * const unsubscribe = storageEventStore.subscribe((events) => {
15
+ * // Subscribe to storage events - automatically starts capturing!
16
+ * const unsubscribe = storageEventStore.subscribeToEvents((events) => {
16
17
  * console.log('Storage events:', events);
17
18
  * });
18
19
  *
19
- * // Start capturing (must be called once)
20
- * await storageEventStore.startCapturing();
21
- *
22
- * // Later, clean up
20
+ * // Later, clean up - automatically stops if no other subscribers
23
21
  * unsubscribe();
24
22
  * ```
25
23
  */
26
24
 
27
- import { startListening as startAsyncStorageListening, addListener as addAsyncStorageListener, isListening as isAsyncStorageListening, pauseCapture as pauseAsyncStorageCapture, resumeCapture as resumeAsyncStorageCapture } from "../utils/AsyncStorageListener";
25
+ import { BaseEventStore } from "@buoy-gg/shared-ui";
26
+ import { startListening as startAsyncStorageListening, addListener as addAsyncStorageListener, isListening as isAsyncStorageListening } from "../utils/AsyncStorageListener";
28
27
  import { addMMKVListener } from "../utils/MMKVListener";
29
28
 
30
29
  /**
@@ -32,65 +31,60 @@ import { addMMKVListener } from "../utils/MMKVListener";
32
31
  */
33
32
 
34
33
  /**
35
- * Listener callback type for storage events
34
+ * Listener callback type for storage events array
36
35
  */
37
36
 
38
37
  /**
39
38
  * Listener callback for individual new events
40
39
  */
41
40
 
42
- const MAX_EVENTS = 500;
43
- class StorageEventStore {
44
- events = [];
45
- listeners = new Set();
46
- eventCallbacks = new Set();
47
- isCapturing = false;
48
-
49
- // Unsubscribe functions
41
+ class StorageEventStore extends BaseEventStore {
42
+ // Unsubscribe functions for raw listeners
50
43
  asyncStorageUnsubscribe = null;
51
44
  mmkvUnsubscribe = null;
45
+ constructor() {
46
+ super({
47
+ storeName: "storage",
48
+ maxEvents: 500
49
+ });
50
+ }
52
51
 
53
52
  /**
54
53
  * Start capturing storage events from both AsyncStorage and MMKV
55
54
  */
56
55
  async startCapturing() {
57
- if (this.isCapturing) {
58
- return;
59
- }
60
-
61
56
  // Start AsyncStorage listening if not already active
62
57
  if (!isAsyncStorageListening()) {
63
58
  await startAsyncStorageListening();
64
59
  }
65
60
 
66
61
  // Subscribe to AsyncStorage events
67
- this.asyncStorageUnsubscribe = addAsyncStorageListener(event => {
68
- const storageEvent = {
69
- ...event,
70
- storageType: "async"
71
- };
72
- this.addEvent(storageEvent);
73
- });
62
+ if (!this.asyncStorageUnsubscribe) {
63
+ this.asyncStorageUnsubscribe = addAsyncStorageListener(event => {
64
+ const storageEvent = {
65
+ ...event,
66
+ storageType: "async"
67
+ };
68
+ this.addEvent(storageEvent);
69
+ });
70
+ }
74
71
 
75
72
  // Subscribe to MMKV events
76
- this.mmkvUnsubscribe = addMMKVListener(event => {
77
- const storageEvent = {
78
- ...event,
79
- storageType: "mmkv"
80
- };
81
- this.addEvent(storageEvent);
82
- });
83
- this.isCapturing = true;
73
+ if (!this.mmkvUnsubscribe) {
74
+ this.mmkvUnsubscribe = addMMKVListener(event => {
75
+ const storageEvent = {
76
+ ...event,
77
+ storageType: "mmkv"
78
+ };
79
+ this.addEvent(storageEvent);
80
+ });
81
+ }
84
82
  }
85
83
 
86
84
  /**
87
85
  * Stop capturing storage events
88
86
  */
89
87
  stopCapturing() {
90
- if (!this.isCapturing) {
91
- return;
92
- }
93
-
94
88
  // Unsubscribe from AsyncStorage
95
89
  if (this.asyncStorageUnsubscribe) {
96
90
  this.asyncStorageUnsubscribe();
@@ -102,88 +96,13 @@ class StorageEventStore {
102
96
  this.mmkvUnsubscribe();
103
97
  this.mmkvUnsubscribe = null;
104
98
  }
105
- this.isCapturing = false;
106
- }
107
-
108
- /**
109
- * Pause event capture (used during time-travel operations)
110
- */
111
- pauseCapture() {
112
- pauseAsyncStorageCapture();
113
- // MMKV listener doesn't have pause, but it's less common for time-travel
114
- }
115
-
116
- /**
117
- * Resume event capture after pausing
118
- */
119
- resumeCapture() {
120
- resumeAsyncStorageCapture();
121
- }
122
-
123
- /**
124
- * Add an event to the store
125
- */
126
- addEvent(event) {
127
- // Add to beginning (newest first)
128
- this.events = [event, ...this.events].slice(0, MAX_EVENTS);
129
-
130
- // Notify event callbacks (for individual events)
131
- this.eventCallbacks.forEach(callback => {
132
- try {
133
- callback(event);
134
- } catch {
135
- // Ignore callback errors
136
- }
137
- });
138
-
139
- // Notify listeners (for full event list)
140
- this.notifyListeners();
141
99
  }
142
100
 
143
101
  /**
144
- * Subscribe to all storage events (receives full event array on each change)
102
+ * Check if currently capturing events (has subscribers)
145
103
  */
146
- subscribe(listener) {
147
- this.listeners.add(listener);
148
-
149
- // Immediately call with current events
150
- listener(this.events);
151
-
152
- // Return unsubscribe function
153
- return () => {
154
- this.listeners.delete(listener);
155
- };
156
- }
157
-
158
- /**
159
- * Subscribe to new events only (receives individual events as they occur)
160
- */
161
- onEvent(callback) {
162
- this.eventCallbacks.add(callback);
163
- return () => {
164
- this.eventCallbacks.delete(callback);
165
- };
166
- }
167
-
168
- /**
169
- * Notify all listeners of changes
170
- */
171
- notifyListeners() {
172
- const events = this.events;
173
- this.listeners.forEach(listener => {
174
- try {
175
- listener(events);
176
- } catch {
177
- // Ignore listener errors
178
- }
179
- });
180
- }
181
-
182
- /**
183
- * Get all events
184
- */
185
- getEvents() {
186
- return this.events;
104
+ isCapturing() {
105
+ return this.asyncStorageUnsubscribe !== null || this.mmkvUnsubscribe !== null;
187
106
  }
188
107
 
189
108
  /**
@@ -192,40 +111,14 @@ class StorageEventStore {
192
111
  getEventsByType(storageType) {
193
112
  return this.events.filter(event => event.storageType === storageType);
194
113
  }
195
-
196
- /**
197
- * Get event count
198
- */
199
- getEventCount() {
200
- return this.events.length;
201
- }
202
-
203
- /**
204
- * Clear all events
205
- */
206
- clearEvents() {
207
- this.events = [];
208
- this.notifyListeners();
209
- }
210
-
211
- /**
212
- * Check if currently capturing events
213
- */
214
- get capturing() {
215
- return this.isCapturing;
216
- }
217
114
  }
218
115
 
219
116
  // Singleton instance
220
117
  export const storageEventStore = new StorageEventStore();
221
118
 
222
119
  // Convenience exports
223
- export const startStorageCapture = () => storageEventStore.startCapturing();
224
- export const stopStorageCapture = () => storageEventStore.stopCapturing();
225
- export const pauseStorageCapture = () => storageEventStore.pauseCapture();
226
- export const resumeStorageCapture = () => storageEventStore.resumeCapture();
227
- export const subscribeToStorageEvents = listener => storageEventStore.subscribe(listener);
120
+ export const subscribeToStorageEvents = listener => storageEventStore.subscribeToEvents(listener);
228
121
  export const onStorageEvent = callback => storageEventStore.onEvent(callback);
229
122
  export const getStorageEvents = () => storageEventStore.getEvents();
230
123
  export const clearStorageEvents = () => storageEventStore.clearEvents();
231
- export const isStorageCapturing = () => storageEventStore.capturing;
124
+ export const isStorageCapturing = () => storageEventStore.isCapturing();
@@ -1 +1 @@
1
- {"version":3,"file":"StorageModalWithTabs.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/StorageModalWithTabs.tsx"],"names":[],"mappings":"AAyBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AA0B9C,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,UAAU,yBAAyB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC5C;AAqBD,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,mBAAwB,GACzB,EAAE,yBAAyB,sCAqsB3B"}
1
+ {"version":3,"file":"StorageModalWithTabs.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/StorageModalWithTabs.tsx"],"names":[],"mappings":"AAyBA,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAuB9C,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,UAAU,yBAAyB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,GAAG,KAAK,IAAI,CAAC;IACvC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC5C;AAqBD,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,OAAO,EACP,MAAM,EACN,UAAU,EACV,2BAAmC,EACnC,mBAAwB,GACzB,EAAE,yBAAyB,sCA+rB3B"}
@@ -2,7 +2,8 @@
2
2
  * useStorageEvents Hook
3
3
  *
4
4
  * React hook for subscribing to storage events from the centralized store.
5
- * Provides a clean interface for components to receive storage events.
5
+ * Uses self-managing Subscribable pattern - the storage listener automatically
6
+ * starts when this hook mounts and stops when all subscribers unmount.
6
7
  *
7
8
  * @example
8
9
  * ```typescript
@@ -28,24 +29,16 @@ export interface UseStorageEventsOptions {
28
29
  storageType?: "async" | "mmkv" | "all";
29
30
  /** Maximum number of events to keep in state */
30
31
  maxEvents?: number;
31
- /** Auto-start capturing on mount (default: true) */
32
- autoStart?: boolean;
32
+ /** Whether to subscribe to events (default: true). Set to false to unsubscribe. */
33
+ enabled?: boolean;
33
34
  }
34
35
  export interface UseStorageEventsResult {
35
36
  /** All storage events (newest first) */
36
37
  events: StorageEvent[];
37
38
  /** Clear all events */
38
39
  clearEvents: () => void;
39
- /** Whether events are being captured */
40
+ /** Whether events are being captured (has subscribers) */
40
41
  isCapturing: boolean;
41
- /** Start capturing events */
42
- startCapturing: () => Promise<void>;
43
- /** Stop capturing events */
44
- stopCapturing: () => void;
45
- /** Pause capturing (for time-travel) */
46
- pauseCapturing: () => void;
47
- /** Resume capturing after pause */
48
- resumeCapturing: () => void;
49
42
  }
50
43
  export declare function useStorageEvents(options?: UseStorageEventsOptions): UseStorageEventsResult;
51
44
  //# sourceMappingURL=useStorageEvents.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useStorageEvents.d.ts","sourceRoot":"","sources":["../../../../src/storage/hooks/useStorageEvents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AAGrC,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,WAAW,uBAAuB;IACtC,6BAA6B;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;IACvC,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACrC,wCAAwC;IACxC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,uBAAuB;IACvB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,wCAAwC;IACxC,WAAW,EAAE,OAAO,CAAC;IACrB,6BAA6B;IAC7B,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,4BAA4B;IAC5B,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,wCAAwC;IACxC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,mCAAmC;IACnC,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAoExB"}
1
+ {"version":3,"file":"useStorageEvents.d.ts","sourceRoot":"","sources":["../../../../src/storage/hooks/useStorageEvents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAEL,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AAGrC,YAAY,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEhE,MAAM,WAAW,uBAAuB;IACtC,6BAA6B;IAC7B,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;IACvC,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,wCAAwC;IACxC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,uBAAuB;IACvB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,0DAA0D;IAC1D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,uBAA4B,GACpC,sBAAsB,CAkDxB"}
@@ -15,5 +15,5 @@ export { MMKVInstanceSelector } from "./components/MMKVInstanceSelector";
15
15
  export { MMKVInstanceInfoPanel } from "./components/MMKVInstanceInfoPanel";
16
16
  export * from "./types";
17
17
  export * from "./utils";
18
- export { storageEventStore, startStorageCapture, stopStorageCapture, pauseStorageCapture, resumeStorageCapture, subscribeToStorageEvents, onStorageEvent, getStorageEvents, clearStorageEvents, isStorageCapturing, type StorageEvent, type StorageEventListener, type StorageEventCallback, } from "./stores/storageEventStore";
18
+ export { storageEventStore, subscribeToStorageEvents, onStorageEvent, getStorageEvents, clearStorageEvents, isStorageCapturing, type StorageEvent, type StorageEventListener, type StorageEventCallback, } from "./stores/storageEventStore";
19
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,oBAAoB,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjI,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AAG7G,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAGvH,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAG3E,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC;AAGxB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,KAAK,oBAAoB,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjI,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAC;AAG7G,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACpG,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,KAAK,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAGvH,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAG3E,cAAc,SAAS,CAAC;AAGxB,cAAc,SAAS,CAAC;AAGxB,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,4BAA4B,CAAC"}
@@ -2,25 +2,24 @@
2
2
  * Storage Event Store
3
3
  *
4
4
  * Centralized store that aggregates storage events from AsyncStorage and MMKV
5
- * into a single event stream. This provides a single source of truth for all
6
- * storage operations, eliminating duplicate subscriptions across components.
5
+ * into a single event stream. Uses BaseEventStore pattern - the storage listener
6
+ * automatically starts when the first subscriber joins and stops when the last
7
+ * subscriber leaves.
7
8
  *
8
9
  * @example
9
10
  * ```typescript
10
11
  * import { storageEventStore } from '@buoy-gg/storage';
11
12
  *
12
- * // Subscribe to storage events
13
- * const unsubscribe = storageEventStore.subscribe((events) => {
13
+ * // Subscribe to storage events - automatically starts capturing!
14
+ * const unsubscribe = storageEventStore.subscribeToEvents((events) => {
14
15
  * console.log('Storage events:', events);
15
16
  * });
16
17
  *
17
- * // Start capturing (must be called once)
18
- * await storageEventStore.startCapturing();
19
- *
20
- * // Later, clean up
18
+ * // Later, clean up - automatically stops if no other subscribers
21
19
  * unsubscribe();
22
20
  * ```
23
21
  */
22
+ import { BaseEventStore } from "@buoy-gg/shared-ui";
24
23
  import { type AsyncStorageEvent } from "../utils/AsyncStorageListener";
25
24
  import { type MMKVEvent } from "../utils/MMKVListener";
26
25
  /**
@@ -32,78 +31,35 @@ export type StorageEvent = (AsyncStorageEvent & {
32
31
  storageType: "mmkv";
33
32
  });
34
33
  /**
35
- * Listener callback type for storage events
34
+ * Listener callback type for storage events array
36
35
  */
37
36
  export type StorageEventListener = (events: StorageEvent[]) => void;
38
37
  /**
39
38
  * Listener callback for individual new events
40
39
  */
41
40
  export type StorageEventCallback = (event: StorageEvent) => void;
42
- declare class StorageEventStore {
43
- private events;
44
- private listeners;
45
- private eventCallbacks;
46
- private isCapturing;
41
+ declare class StorageEventStore extends BaseEventStore<StorageEvent> {
47
42
  private asyncStorageUnsubscribe;
48
43
  private mmkvUnsubscribe;
44
+ constructor();
49
45
  /**
50
46
  * Start capturing storage events from both AsyncStorage and MMKV
51
47
  */
52
- startCapturing(): Promise<void>;
48
+ protected startCapturing(): Promise<void>;
53
49
  /**
54
50
  * Stop capturing storage events
55
51
  */
56
- stopCapturing(): void;
57
- /**
58
- * Pause event capture (used during time-travel operations)
59
- */
60
- pauseCapture(): void;
61
- /**
62
- * Resume event capture after pausing
63
- */
64
- resumeCapture(): void;
52
+ protected stopCapturing(): void;
65
53
  /**
66
- * Add an event to the store
54
+ * Check if currently capturing events (has subscribers)
67
55
  */
68
- private addEvent;
69
- /**
70
- * Subscribe to all storage events (receives full event array on each change)
71
- */
72
- subscribe(listener: StorageEventListener): () => void;
73
- /**
74
- * Subscribe to new events only (receives individual events as they occur)
75
- */
76
- onEvent(callback: StorageEventCallback): () => void;
77
- /**
78
- * Notify all listeners of changes
79
- */
80
- private notifyListeners;
81
- /**
82
- * Get all events
83
- */
84
- getEvents(): StorageEvent[];
56
+ isCapturing(): boolean;
85
57
  /**
86
58
  * Get events filtered by storage type
87
59
  */
88
60
  getEventsByType(storageType: "async" | "mmkv"): StorageEvent[];
89
- /**
90
- * Get event count
91
- */
92
- getEventCount(): number;
93
- /**
94
- * Clear all events
95
- */
96
- clearEvents(): void;
97
- /**
98
- * Check if currently capturing events
99
- */
100
- get capturing(): boolean;
101
61
  }
102
62
  export declare const storageEventStore: StorageEventStore;
103
- export declare const startStorageCapture: () => Promise<void>;
104
- export declare const stopStorageCapture: () => void;
105
- export declare const pauseStorageCapture: () => void;
106
- export declare const resumeStorageCapture: () => void;
107
63
  export declare const subscribeToStorageEvents: (listener: StorageEventListener) => () => void;
108
64
  export declare const onStorageEvent: (callback: StorageEventCallback) => () => void;
109
65
  export declare const getStorageEvents: () => StorageEvent[];
@@ -1 +1 @@
1
- {"version":3,"file":"storageEventStore.d.ts","sourceRoot":"","sources":["../../../../src/storage/stores/storageEventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAOL,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,uBAAuB,CAAC;AAE/B;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,CAAC,iBAAiB,GAAG;IAAE,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,GAC9C,CAAC,SAAS,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAIjE,cAAM,iBAAiB;IACrB,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,cAAc,CAAwC;IAC9D,OAAO,CAAC,WAAW,CAAS;IAG5B,OAAO,CAAC,uBAAuB,CAA6B;IAC5D,OAAO,CAAC,eAAe,CAA6B;IAEpD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA2BrC;;OAEG;IACH,aAAa,IAAI,IAAI;IAoBrB;;OAEG;IACH,YAAY,IAAI,IAAI;IAKpB;;OAEG;IACH,aAAa,IAAI,IAAI;IAIrB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAiBhB;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAYrD;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,oBAAoB,GAAG,MAAM,IAAI;IAQnD;;OAEG;IACH,OAAO,CAAC,eAAe;IAWvB;;OAEG;IACH,SAAS,IAAI,YAAY,EAAE;IAI3B;;OAEG;IACH,eAAe,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,YAAY,EAAE;IAI9D;;OAEG;IACH,aAAa,IAAI,MAAM;IAIvB;;OAEG;IACH,WAAW,IAAI,IAAI;IAKnB;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF;AAGD,eAAO,MAAM,iBAAiB,mBAA0B,CAAC;AAGzD,eAAO,MAAM,mBAAmB,qBAA2C,CAAC;AAC5E,eAAO,MAAM,kBAAkB,YAA0C,CAAC;AAC1E,eAAO,MAAM,mBAAmB,YAAyC,CAAC;AAC1E,eAAO,MAAM,oBAAoB,YAA0C,CAAC;AAC5E,eAAO,MAAM,wBAAwB,GAAI,UAAU,oBAAoB,WAlFpB,IAmFZ,CAAC;AACxC,eAAO,MAAM,cAAc,GAAI,UAAU,oBAAoB,WArEZ,IAsEZ,CAAC;AACtC,eAAO,MAAM,gBAAgB,sBAAsC,CAAC;AACpE,eAAO,MAAM,kBAAkB,YAAwC,CAAC;AACxE,eAAO,MAAM,kBAAkB,eAAoC,CAAC"}
1
+ {"version":3,"file":"storageEventStore.d.ts","sourceRoot":"","sources":["../../../../src/storage/stores/storageEventStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAIL,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAEL,KAAK,SAAS,EACf,MAAM,uBAAuB,CAAC;AAE/B;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,CAAC,iBAAiB,GAAG;IAAE,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,GAC9C,CAAC,SAAS,GAAG;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,IAAI,CAAC;AAEpE;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAEjE,cAAM,iBAAkB,SAAQ,cAAc,CAAC,YAAY,CAAC;IAE1D,OAAO,CAAC,uBAAuB,CAA6B;IAC5D,OAAO,CAAC,eAAe,CAA6B;;IASpD;;OAEG;cACa,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB/C;;OAEG;IACH,SAAS,CAAC,aAAa,IAAI,IAAI;IAc/B;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,eAAe,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,GAAG,YAAY,EAAE;CAG/D;AAGD,eAAO,MAAM,iBAAiB,mBAA0B,CAAC;AAGzD,eAAO,MAAM,wBAAwB,GAAI,UAAU,oBAAoB,eACxB,CAAC;AAChD,eAAO,MAAM,cAAc,GAAI,UAAU,oBAAoB,eACxB,CAAC;AACtC,eAAO,MAAM,gBAAgB,sBAAsC,CAAC;AACpE,eAAO,MAAM,kBAAkB,YAAwC,CAAC;AACxE,eAAO,MAAM,kBAAkB,eAAwC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/storage",
3
- "version": "2.1.2",
3
+ "version": "2.1.3",
4
4
  "description": "storage package",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -26,8 +26,8 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "dependencies": {
29
- "@buoy-gg/floating-tools-core": "2.1.2",
30
- "@buoy-gg/shared-ui": "2.1.2"
29
+ "@buoy-gg/shared-ui": "2.1.3",
30
+ "@buoy-gg/floating-tools-core": "2.1.3"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@buoy-gg/license": "*",
@@ -40,7 +40,7 @@
40
40
  "@types/react": "^19.1.0",
41
41
  "@types/react-native": "^0.73.0",
42
42
  "typescript": "~5.8.3",
43
- "@buoy-gg/license": "2.1.2"
43
+ "@buoy-gg/license": "2.1.3"
44
44
  },
45
45
  "react-native-builder-bob": {
46
46
  "source": "src",