@buoy-gg/storage 3.0.1 → 4.0.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 (71) hide show
  1. package/README.md +1 -1
  2. package/lib/commonjs/index.js +7 -0
  3. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +25 -4
  4. package/lib/commonjs/storage/components/GameUIStorageStats.js +2 -2
  5. package/lib/commonjs/storage/components/SelectionActionBar.js +16 -3
  6. package/lib/commonjs/storage/components/StorageBrowserMode.js +6 -2
  7. package/lib/commonjs/storage/components/StorageEventDetailContent.js +2 -2
  8. package/lib/commonjs/storage/components/StorageKeyCard.js +5 -5
  9. package/lib/commonjs/storage/components/StorageKeyRow.js +97 -8
  10. package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
  11. package/lib/commonjs/storage/components/StorageModalWithTabs.js +47 -1
  12. package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +15 -3
  13. package/lib/commonjs/storage/hooks/useMMKVKeys.js +20 -2
  14. package/lib/commonjs/storage/stores/storageEventStore.js +84 -0
  15. package/lib/commonjs/storage/sync/storageSyncAdapter.js +53 -0
  16. package/lib/commonjs/storage/utils/AsyncStorageListener.js +148 -160
  17. package/lib/commonjs/storage/utils/asyncStorageCompat.js +89 -0
  18. package/lib/commonjs/storage/utils/clearAllStorage.js +2 -1
  19. package/lib/commonjs/storage/utils/mmkvTypeDetection.js +20 -5
  20. package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +3 -2
  21. package/lib/commonjs/storage/utils/valueType.js +41 -0
  22. package/lib/module/index.js +5 -0
  23. package/lib/module/storage/components/GameUIStorageBrowser.js +26 -4
  24. package/lib/module/storage/components/GameUIStorageStats.js +3 -2
  25. package/lib/module/storage/components/SelectionActionBar.js +17 -3
  26. package/lib/module/storage/components/StorageBrowserMode.js +6 -2
  27. package/lib/module/storage/components/StorageEventDetailContent.js +2 -2
  28. package/lib/module/storage/components/StorageKeyCard.js +5 -5
  29. package/lib/module/storage/components/StorageKeyRow.js +99 -10
  30. package/lib/module/storage/components/StorageKeySection.js +10 -4
  31. package/lib/module/storage/components/StorageModalWithTabs.js +47 -1
  32. package/lib/module/storage/hooks/useAsyncStorageKeys.js +16 -4
  33. package/lib/module/storage/hooks/useMMKVKeys.js +21 -3
  34. package/lib/module/storage/stores/storageEventStore.js +84 -0
  35. package/lib/module/storage/sync/storageSyncAdapter.js +48 -0
  36. package/lib/module/storage/utils/AsyncStorageListener.js +124 -135
  37. package/lib/module/storage/utils/asyncStorageCompat.js +81 -0
  38. package/lib/module/storage/utils/clearAllStorage.js +2 -1
  39. package/lib/module/storage/utils/mmkvTypeDetection.js +20 -5
  40. package/lib/module/storage/utils/storageTimeTravelUtils.js +3 -2
  41. package/lib/module/storage/utils/valueType.js +39 -0
  42. package/lib/typescript/index.d.ts +1 -0
  43. package/lib/typescript/index.d.ts.map +1 -1
  44. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +5 -1
  45. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  46. package/lib/typescript/storage/components/GameUIStorageStats.d.ts.map +1 -1
  47. package/lib/typescript/storage/components/SelectionActionBar.d.ts +3 -1
  48. package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
  49. package/lib/typescript/storage/components/StorageBrowserMode.d.ts +3 -1
  50. package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
  51. package/lib/typescript/storage/components/StorageKeyRow.d.ts +7 -1
  52. package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
  53. package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
  54. package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
  55. package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
  56. package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +1 -1
  57. package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +1 -1
  58. package/lib/typescript/storage/stores/storageEventStore.d.ts +8 -0
  59. package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -1
  60. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +31 -0
  61. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -0
  62. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +20 -0
  63. package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
  64. package/lib/typescript/storage/utils/asyncStorageCompat.d.ts +30 -0
  65. package/lib/typescript/storage/utils/asyncStorageCompat.d.ts.map +1 -0
  66. package/lib/typescript/storage/utils/clearAllStorage.d.ts.map +1 -1
  67. package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts.map +1 -1
  68. package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
  69. package/lib/typescript/storage/utils/valueType.d.ts +13 -0
  70. package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
  71. package/package.json +6 -6
@@ -7,6 +7,8 @@ exports.subscribeToStorageEvents = exports.storageEventStore = exports.onStorage
7
7
  var _sharedUi = require("@buoy-gg/shared-ui");
8
8
  var _AsyncStorageListener = require("../utils/AsyncStorageListener");
9
9
  var _MMKVListener = require("../utils/MMKVListener");
10
+ var _MMKVInstanceRegistry = require("../utils/MMKVInstanceRegistry");
11
+ var _mmkvTypeDetection = require("../utils/mmkvTypeDetection");
10
12
  /**
11
13
  * Storage Event Store
12
14
  *
@@ -53,6 +55,7 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
53
55
  // Unsubscribe functions for raw listeners
54
56
  asyncStorageUnsubscribe = null;
55
57
  mmkvUnsubscribe = null;
58
+ hasScannedInitialState = false;
56
59
  constructor() {
57
60
  super({
58
61
  storeName: "storage",
@@ -60,6 +63,84 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
60
63
  });
61
64
  }
62
65
 
66
+ /**
67
+ * Scan all registered MMKV instances and AsyncStorage for existing keys,
68
+ * creating synthetic events so that getEvents() includes the current state
69
+ * (not just changes made after capture started). Only runs once per store
70
+ * lifetime to avoid duplicates.
71
+ */
72
+ async scanExistingState() {
73
+ if (this.hasScannedInitialState) return;
74
+ this.hasScannedInitialState = true;
75
+ const now = new Date();
76
+
77
+ // ── MMKV: synchronous scan ──
78
+ const instances = _MMKVInstanceRegistry.mmkvInstanceRegistry.getAll();
79
+ for (const {
80
+ id: instanceId,
81
+ instance
82
+ } of instances) {
83
+ try {
84
+ const keys = instance.getAllKeys();
85
+ for (const key of keys) {
86
+ const {
87
+ value,
88
+ type: valueType
89
+ } = (0, _mmkvTypeDetection.detectMMKVType)(instance, key);
90
+ const actionMap = {
91
+ string: "set.string",
92
+ number: "set.number",
93
+ boolean: "set.boolean",
94
+ buffer: "set.buffer"
95
+ };
96
+ const action = actionMap[valueType] ?? "set.string";
97
+ this.addEvent({
98
+ action,
99
+ timestamp: now,
100
+ instanceId,
101
+ data: {
102
+ key,
103
+ value,
104
+ valueType
105
+ },
106
+ storageType: "mmkv",
107
+ id: nextStorageEventId()
108
+ });
109
+ }
110
+ } catch {
111
+ // Instance may not be accessible — skip silently
112
+ }
113
+ }
114
+
115
+ // ── AsyncStorage: async scan ──
116
+ try {
117
+ const {
118
+ asyncStorage,
119
+ readMany
120
+ } = require("../utils/asyncStorageCompat");
121
+ if (asyncStorage) {
122
+ const allKeys = await asyncStorage.getAllKeys();
123
+ if (allKeys.length > 0) {
124
+ const pairs = await readMany(allKeys);
125
+ for (const [key, rawValue] of pairs) {
126
+ this.addEvent({
127
+ action: "setItem",
128
+ timestamp: now,
129
+ data: {
130
+ key,
131
+ value: rawValue ?? undefined
132
+ },
133
+ storageType: "async",
134
+ id: nextStorageEventId()
135
+ });
136
+ }
137
+ }
138
+ }
139
+ } catch {
140
+ // AsyncStorage not available — skip silently
141
+ }
142
+ }
143
+
63
144
  /**
64
145
  * Start capturing storage events from both AsyncStorage and MMKV
65
146
  */
@@ -92,6 +173,9 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
92
173
  this.addEvent(storageEvent);
93
174
  });
94
175
  }
176
+
177
+ // Scan existing keys so getEvents() includes pre-existing state
178
+ await this.scanExistingState();
95
179
  }
96
180
 
97
181
  /**
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.storageSyncAdapter = void 0;
7
+ var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
8
+ var _asyncStorageCompat = require("../utils/asyncStorageCompat");
9
+ var _storageEventStore = require("../stores/storageEventStore");
10
+ var _clearAllStorage = require("../utils/clearAllStorage");
11
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
+ /**
13
+ * Sync adapter for the storage tool, consumed by @buoy-gg/external-sync's
14
+ * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
15
+ * this package doesn't need a dependency on it).
16
+ *
17
+ * Subscribing starts the underlying capture lifecycle (including the initial
18
+ * key scan), so storage events are only recorded while a dashboard is
19
+ * watching.
20
+ *
21
+ * The `async.*` actions mirror the AsyncStorage API so the desktop dashboard
22
+ * can proxy its browse/edit mode against the device's real storage.
23
+ */
24
+ const storageSyncAdapter = exports.storageSyncAdapter = {
25
+ version: 1,
26
+ getSnapshot: () => _storageEventStore.storageEventStore.getEvents(),
27
+ subscribe: onChange => _storageEventStore.storageEventStore.subscribeToEvents(onChange),
28
+ actions: {
29
+ clearEvents: () => {
30
+ _storageEventStore.storageEventStore.clearEvents();
31
+ },
32
+ /** Clears all app storage keys, preserving dev tools settings. */
33
+ clearAppStorage: () => (0, _clearAllStorage.clearAllAppStorage)(),
34
+ // ── Remote AsyncStorage proxy (desktop browse/edit mode) ──
35
+ // Single-item methods share the same signature across async-storage v2/v3;
36
+ // batch methods go through the compat helpers (which translate to v3's
37
+ // getMany/setMany/removeMany) and keep the v2 tuple/pairs shape on the wire.
38
+ "async.getAllKeys": () => _asyncStorage.default.getAllKeys(),
39
+ "async.multiGet": params => (0, _asyncStorageCompat.readMany)(params.keys),
40
+ "async.getItem": params => _asyncStorage.default.getItem(params.key),
41
+ "async.setItem": params => {
42
+ const {
43
+ key,
44
+ value
45
+ } = params;
46
+ return _asyncStorage.default.setItem(key, value);
47
+ },
48
+ "async.removeItem": params => _asyncStorage.default.removeItem(params.key),
49
+ "async.multiRemove": params => (0, _asyncStorageCompat.removeMany)(params.keys),
50
+ "async.multiSet": params => (0, _asyncStorageCompat.writeMany)(params.pairs),
51
+ "async.clear": () => _asyncStorage.default.clear()
52
+ }
53
+ };
@@ -4,10 +4,11 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.stopListening = exports.startListening = exports.resumeCapture = exports.removeAllListeners = exports.pauseCapture = exports.isPaused = exports.isListening = exports.getListenerCount = exports.default = exports.addListener = void 0;
7
- var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
8
- function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
+ var _asyncStorageCompat = require("./asyncStorageCompat");
9
8
  // AsyncStorage method signatures - matching the actual AsyncStorage API
10
9
 
10
+ // v3-only batch methods (replace multiSet/multiRemove)
11
+
11
12
  // Event types for AsyncStorage operations
12
13
 
13
14
  /**
@@ -18,6 +19,12 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
18
19
  * and emits events to registered listeners. It maintains the original functionality
19
20
  * while providing observability for debugging and development tools.
20
21
  *
22
+ * Supports both async-storage v2 (multiGet/multiSet/multiRemove/mergeItem) and
23
+ * v3 (getMany/setMany/removeMany, no merge). On v3 the renamed batch methods are
24
+ * swizzled and normalized back to the same v2-shaped events, so the rest of the
25
+ * tool — event store, time-travel, desktop sync — is version-agnostic. v3 dropped
26
+ * the merge API entirely, so no merge events can occur there.
27
+ *
21
28
  * @example
22
29
  * ```typescript
23
30
  * // Start listening to all AsyncStorage operations
@@ -58,9 +65,13 @@ class AsyncStorageListener {
58
65
  originalRemoveItem = null;
59
66
  originalMergeItem = null;
60
67
  originalClear = null;
68
+ // v2 batch originals
61
69
  originalMultiSet = null;
62
70
  originalMultiRemove = null;
63
71
  originalMultiMerge = null;
72
+ // v3 batch originals
73
+ originalSetMany = null;
74
+ originalRemoveMany = null;
64
75
 
65
76
  /**
66
77
  * Determines if a storage key should be ignored to prevent infinite loops
@@ -99,19 +110,27 @@ class AsyncStorageListener {
99
110
  }
100
111
 
101
112
  // Check if methods are already swizzled by checking the function name
102
- if (_asyncStorage.default.setItem.name === "swizzled_setItem") {
113
+ if (_asyncStorageCompat.asyncStorage.setItem.name === "swizzled_setItem") {
103
114
  // Don't store swizzled methods as originals
104
115
  return false;
105
116
  }
106
117
 
107
- // Store original methods (these should be the real AsyncStorage methods)
108
- this.originalSetItem = _asyncStorage.default.setItem.bind(_asyncStorage.default);
109
- this.originalRemoveItem = _asyncStorage.default.removeItem.bind(_asyncStorage.default);
110
- this.originalMergeItem = _asyncStorage.default.mergeItem.bind(_asyncStorage.default);
111
- this.originalClear = _asyncStorage.default.clear.bind(_asyncStorage.default);
112
- this.originalMultiSet = _asyncStorage.default.multiSet.bind(_asyncStorage.default);
113
- this.originalMultiRemove = _asyncStorage.default.multiRemove.bind(_asyncStorage.default);
114
- this.originalMultiMerge = _asyncStorage.default.multiMerge ? _asyncStorage.default.multiMerge.bind(_asyncStorage.default) : null;
118
+ // Store original methods (these should be the real AsyncStorage methods).
119
+ // Single-item + clear are identical across v2/v3.
120
+ this.originalSetItem = _asyncStorageCompat.asyncStorage.setItem.bind(_asyncStorageCompat.asyncStorage);
121
+ this.originalRemoveItem = _asyncStorageCompat.asyncStorage.removeItem.bind(_asyncStorageCompat.asyncStorage);
122
+ this.originalClear = _asyncStorageCompat.asyncStorage.clear.bind(_asyncStorageCompat.asyncStorage);
123
+ if (_asyncStorageCompat.asyncStorageCaps.hasLegacyMultiApi) {
124
+ // v2: multiSet/multiRemove + merge API
125
+ this.originalMultiSet = _asyncStorageCompat.asyncStorage.multiSet.bind(_asyncStorageCompat.asyncStorage);
126
+ this.originalMultiRemove = _asyncStorageCompat.asyncStorage.multiRemove.bind(_asyncStorageCompat.asyncStorage);
127
+ this.originalMergeItem = _asyncStorageCompat.asyncStorage.mergeItem.bind(_asyncStorageCompat.asyncStorage);
128
+ this.originalMultiMerge = _asyncStorageCompat.asyncStorage.multiMerge ? _asyncStorageCompat.asyncStorage.multiMerge.bind(_asyncStorageCompat.asyncStorage) : null;
129
+ } else {
130
+ // v3: setMany/removeMany; merge API was removed (nothing to capture)
131
+ this.originalSetMany = _asyncStorageCompat.asyncStorage.setMany.bind(_asyncStorageCompat.asyncStorage);
132
+ this.originalRemoveMany = _asyncStorageCompat.asyncStorage.removeMany.bind(_asyncStorageCompat.asyncStorage);
133
+ }
115
134
 
116
135
  // Original methods stored successfully
117
136
  this.isInitialized = true;
@@ -125,27 +144,35 @@ class AsyncStorageListener {
125
144
  * AsyncStorage methods that were saved during initialization.
126
145
  */
127
146
  restoreOriginalMethods() {
128
- if (!_asyncStorage.default || !this.originalSetItem) {
147
+ if (!_asyncStorageCompat.asyncStorage || !this.originalSetItem) {
129
148
  return;
130
149
  }
131
- _asyncStorage.default.setItem = this.originalSetItem;
150
+ _asyncStorageCompat.asyncStorage.setItem = this.originalSetItem;
132
151
  if (this.originalRemoveItem) {
133
- _asyncStorage.default.removeItem = this.originalRemoveItem;
134
- }
135
- if (this.originalMergeItem) {
136
- _asyncStorage.default.mergeItem = this.originalMergeItem;
152
+ _asyncStorageCompat.asyncStorage.removeItem = this.originalRemoveItem;
137
153
  }
138
154
  if (this.originalClear) {
139
- _asyncStorage.default.clear = this.originalClear;
155
+ _asyncStorageCompat.asyncStorage.clear = this.originalClear;
156
+ }
157
+ // v2 batch + merge
158
+ if (this.originalMergeItem) {
159
+ _asyncStorageCompat.asyncStorage.mergeItem = this.originalMergeItem;
140
160
  }
141
161
  if (this.originalMultiSet) {
142
- _asyncStorage.default.multiSet = this.originalMultiSet;
162
+ _asyncStorageCompat.asyncStorage.multiSet = this.originalMultiSet;
143
163
  }
144
164
  if (this.originalMultiRemove) {
145
- _asyncStorage.default.multiRemove = this.originalMultiRemove;
165
+ _asyncStorageCompat.asyncStorage.multiRemove = this.originalMultiRemove;
146
166
  }
147
167
  if (this.originalMultiMerge) {
148
- _asyncStorage.default.multiMerge = this.originalMultiMerge;
168
+ _asyncStorageCompat.asyncStorage.multiMerge = this.originalMultiMerge;
169
+ }
170
+ // v3 batch
171
+ if (this.originalSetMany) {
172
+ _asyncStorageCompat.asyncStorage.setMany = this.originalSetMany;
173
+ }
174
+ if (this.originalRemoveMany) {
175
+ _asyncStorageCompat.asyncStorage.removeMany = this.originalRemoveMany;
149
176
  }
150
177
  }
151
178
 
@@ -175,6 +202,77 @@ class AsyncStorageListener {
175
202
  });
176
203
  }
177
204
 
205
+ /**
206
+ * Shared core for batch writes (v2 multiSet/multiMerge, v3 setMany).
207
+ *
208
+ * Captures previous values for the affected keys, runs the original native
209
+ * operation, and emits a normalized event. `action` distinguishes a plain set
210
+ * from a merge so time-travel can replay them differently.
211
+ */
212
+ async handleBatchSet(pairs, runOriginal, action = "multiSet") {
213
+ // Filter out ignored keys
214
+ const filteredPairs = pairs.filter(([key]) => !this.shouldIgnoreKey(key));
215
+ if (filteredPairs.length === 0) {
216
+ return runOriginal();
217
+ }
218
+
219
+ // Capture previous values for all keys being written
220
+ const keysToSet = filteredPairs.map(([key]) => key);
221
+ let prevPairs = [];
222
+ try {
223
+ prevPairs = await (0, _asyncStorageCompat.readMany)(keysToSet);
224
+ } catch {
225
+ // Failed to capture previous values
226
+ }
227
+
228
+ // Execute the operation
229
+ const result = await runOriginal();
230
+
231
+ // Emit event with previous values
232
+ this.emit({
233
+ action,
234
+ timestamp: new Date(),
235
+ data: {
236
+ pairs: filteredPairs,
237
+ prevPairs
238
+ }
239
+ });
240
+ return result;
241
+ }
242
+
243
+ /**
244
+ * Shared core for batch removals (v2 multiRemove, v3 removeMany).
245
+ */
246
+ async handleBatchRemove(keys, runOriginal) {
247
+ // Filter out ignored keys
248
+ const filteredKeys = keys.filter(key => !this.shouldIgnoreKey(key));
249
+ if (filteredKeys.length === 0) {
250
+ return runOriginal();
251
+ }
252
+
253
+ // Capture previous values for all keys being removed
254
+ let prevPairs = [];
255
+ try {
256
+ prevPairs = await (0, _asyncStorageCompat.readMany)(filteredKeys);
257
+ } catch {
258
+ // Failed to capture previous values
259
+ }
260
+
261
+ // Execute the operation
262
+ const result = await runOriginal();
263
+
264
+ // Emit event with previous values
265
+ this.emit({
266
+ action: "multiRemove",
267
+ timestamp: new Date(),
268
+ data: {
269
+ keys: filteredKeys,
270
+ prevPairs
271
+ }
272
+ });
273
+ return result;
274
+ }
275
+
178
276
  /**
179
277
  * Start intercepting AsyncStorage operations by swizzling methods
180
278
  *
@@ -196,7 +294,7 @@ class AsyncStorageListener {
196
294
  }
197
295
 
198
296
  // Check if methods are already swizzled (this can happen if initialize was called twice somehow)
199
- if (_asyncStorage.default && _asyncStorage.default.setItem.name === "swizzled_setItem") {
297
+ if (_asyncStorageCompat.asyncStorage && _asyncStorageCompat.asyncStorage.setItem.name === "swizzled_setItem") {
200
298
  this.restoreOriginalMethods();
201
299
  }
202
300
 
@@ -207,7 +305,7 @@ class AsyncStorageListener {
207
305
  // Only capture and emit if key is not ignored
208
306
  if (!this.shouldIgnoreKey(key)) {
209
307
  // Capture previous value before the operation
210
- const prevValue = this.originalSetItem ? await _asyncStorage.default.getItem(key) : null;
308
+ const prevValue = this.originalSetItem ? await _asyncStorageCompat.asyncStorage.getItem(key) : null;
211
309
 
212
310
  // Execute the operation
213
311
  const result = this.originalSetItem ? await this.originalSetItem(key, value) : undefined;
@@ -229,17 +327,17 @@ class AsyncStorageListener {
229
327
  Object.defineProperty(swizzled_setItem, "name", {
230
328
  value: "swizzled_setItem"
231
329
  });
232
- if (_asyncStorage.default) {
233
- _asyncStorage.default.setItem = swizzled_setItem;
330
+ if (_asyncStorageCompat.asyncStorage) {
331
+ _asyncStorageCompat.asyncStorage.setItem = swizzled_setItem;
234
332
  }
235
333
 
236
334
  // Swizzle removeItem
237
- if (_asyncStorage.default) {
238
- _asyncStorage.default.removeItem = async key => {
335
+ if (_asyncStorageCompat.asyncStorage) {
336
+ _asyncStorageCompat.asyncStorage.removeItem = async key => {
239
337
  // Only capture and emit if key is not ignored
240
338
  if (!this.shouldIgnoreKey(key)) {
241
339
  // Capture previous value before the operation
242
- const prevValue = this.originalRemoveItem ? await _asyncStorage.default.getItem(key) : null;
340
+ const prevValue = this.originalRemoveItem ? await _asyncStorageCompat.asyncStorage.getItem(key) : null;
243
341
 
244
342
  // Execute the operation
245
343
  const result = this.originalRemoveItem ? await this.originalRemoveItem(key) : undefined;
@@ -259,13 +357,13 @@ class AsyncStorageListener {
259
357
  };
260
358
  }
261
359
 
262
- // Swizzle mergeItem
263
- if (_asyncStorage.default) {
264
- _asyncStorage.default.mergeItem = async (key, value) => {
360
+ // Swizzle mergeItem (v2 only — v3 removed the merge API)
361
+ if (_asyncStorageCompat.asyncStorageCaps.hasMergeApi && _asyncStorageCompat.asyncStorage) {
362
+ _asyncStorageCompat.asyncStorage.mergeItem = async (key, value) => {
265
363
  // Only capture and emit if key is not ignored
266
364
  if (!this.shouldIgnoreKey(key)) {
267
365
  // Capture previous value before the operation
268
- const prevValue = this.originalMergeItem ? await _asyncStorage.default.getItem(key) : null;
366
+ const prevValue = this.originalMergeItem ? await _asyncStorageCompat.asyncStorage.getItem(key) : null;
269
367
 
270
368
  // Execute the operation
271
369
  const result = this.originalMergeItem ? await this.originalMergeItem(key, value) : undefined;
@@ -287,17 +385,16 @@ class AsyncStorageListener {
287
385
  }
288
386
 
289
387
  // Swizzle clear
290
- if (_asyncStorage.default) {
291
- _asyncStorage.default.clear = async () => {
388
+ if (_asyncStorageCompat.asyncStorage) {
389
+ _asyncStorageCompat.asyncStorage.clear = async () => {
292
390
  // Capture all key-value pairs before clearing
293
391
  let prevPairs = [];
294
392
  try {
295
- const allKeys = await _asyncStorage.default.getAllKeys();
393
+ const allKeys = await _asyncStorageCompat.asyncStorage.getAllKeys();
296
394
  // Filter out ignored keys
297
395
  const keysToCapture = allKeys.filter(key => !this.shouldIgnoreKey(key));
298
396
  if (keysToCapture.length > 0) {
299
- const keyValuePairs = await _asyncStorage.default.multiGet(keysToCapture);
300
- prevPairs = keyValuePairs;
397
+ prevPairs = await (0, _asyncStorageCompat.readMany)(keysToCapture);
301
398
  }
302
399
  } catch {
303
400
  // Failed to capture previous state
@@ -318,105 +415,18 @@ class AsyncStorageListener {
318
415
  };
319
416
  }
320
417
 
321
- // Swizzle multiSet
322
- if (_asyncStorage.default) {
323
- _asyncStorage.default.multiSet = async keyValuePairs => {
324
- // Filter out ignored keys
325
- const filteredPairs = keyValuePairs.filter(([key]) => !this.shouldIgnoreKey(key));
326
- if (filteredPairs.length > 0) {
327
- // Capture previous values for all keys being set
328
- const keysToSet = filteredPairs.map(([key]) => key);
329
- let prevPairs = [];
330
- try {
331
- const existingValues = await _asyncStorage.default.multiGet(keysToSet);
332
- prevPairs = existingValues;
333
- } catch {
334
- // Failed to capture previous values
335
- }
336
-
337
- // Execute the operation
338
- const result = this.originalMultiSet ? await this.originalMultiSet(keyValuePairs) : undefined;
339
-
340
- // Emit event with previous values
341
- this.emit({
342
- action: "multiSet",
343
- timestamp: new Date(),
344
- data: {
345
- pairs: filteredPairs,
346
- prevPairs
347
- }
348
- });
349
- return result;
350
- }
351
- return this.originalMultiSet ? this.originalMultiSet(keyValuePairs) : Promise.resolve();
352
- };
353
- }
354
-
355
- // Swizzle multiRemove
356
- if (_asyncStorage.default) {
357
- _asyncStorage.default.multiRemove = async keys => {
358
- // Filter out ignored keys
359
- const filteredKeys = keys.filter(key => !this.shouldIgnoreKey(key));
360
- if (filteredKeys.length > 0) {
361
- // Capture previous values for all keys being removed
362
- let prevPairs = [];
363
- try {
364
- const existingValues = await _asyncStorage.default.multiGet(filteredKeys);
365
- prevPairs = existingValues;
366
- } catch {
367
- // Failed to capture previous values
368
- }
369
-
370
- // Execute the operation
371
- const result = this.originalMultiRemove ? await this.originalMultiRemove(keys) : undefined;
372
-
373
- // Emit event with previous values
374
- this.emit({
375
- action: "multiRemove",
376
- timestamp: new Date(),
377
- data: {
378
- keys: filteredKeys,
379
- prevPairs
380
- }
381
- });
382
- return result;
383
- }
384
- return this.originalMultiRemove ? this.originalMultiRemove(keys) : Promise.resolve();
385
- };
386
- }
387
-
388
- // Swizzle multiMerge if available
389
- if (this.originalMultiMerge && _asyncStorage.default) {
390
- _asyncStorage.default.multiMerge = async keyValuePairs => {
391
- // Filter out ignored keys
392
- const filteredPairs = keyValuePairs.filter(([key]) => !this.shouldIgnoreKey(key));
393
- if (filteredPairs.length > 0) {
394
- // Capture previous values for all keys being merged
395
- const keysToMerge = filteredPairs.map(([key]) => key);
396
- let prevPairs = [];
397
- try {
398
- const existingValues = await _asyncStorage.default.multiGet(keysToMerge);
399
- prevPairs = existingValues;
400
- } catch {
401
- // Failed to capture previous values
402
- }
403
-
404
- // Execute the operation
405
- const result = this.originalMultiMerge ? await this.originalMultiMerge(keyValuePairs) : undefined;
406
-
407
- // Emit event with previous values
408
- this.emit({
409
- action: "multiMerge",
410
- timestamp: new Date(),
411
- data: {
412
- pairs: filteredPairs,
413
- prevPairs
414
- }
415
- });
416
- return result;
417
- }
418
- return this.originalMultiMerge ? this.originalMultiMerge(keyValuePairs) : Promise.resolve();
419
- };
418
+ // Swizzle the batch methods for whichever API the installed version exposes.
419
+ if (_asyncStorageCompat.asyncStorageCaps.hasLegacyMultiApi && _asyncStorageCompat.asyncStorage) {
420
+ // ── v2: multiSet / multiRemove / multiMerge ──
421
+ _asyncStorageCompat.asyncStorage.multiSet = keyValuePairs => this.handleBatchSet(keyValuePairs, () => this.originalMultiSet ? this.originalMultiSet(keyValuePairs) : Promise.resolve());
422
+ _asyncStorageCompat.asyncStorage.multiRemove = keys => this.handleBatchRemove(keys, () => this.originalMultiRemove ? this.originalMultiRemove(keys) : Promise.resolve());
423
+ if (this.originalMultiMerge) {
424
+ _asyncStorageCompat.asyncStorage.multiMerge = keyValuePairs => this.handleBatchSet(keyValuePairs, () => this.originalMultiMerge ? this.originalMultiMerge(keyValuePairs) : Promise.resolve(), "multiMerge");
425
+ }
426
+ } else if (_asyncStorageCompat.asyncStorage) {
427
+ // ── v3: setMany / removeMany (normalized back to multiSet/multiRemove events) ──
428
+ _asyncStorageCompat.asyncStorage.setMany = entries => this.handleBatchSet(Object.entries(entries), () => this.originalSetMany ? this.originalSetMany(entries) : Promise.resolve());
429
+ _asyncStorageCompat.asyncStorage.removeMany = keys => this.handleBatchRemove(keys, () => this.originalRemoveMany ? this.originalRemoveMany(keys) : Promise.resolve());
420
430
  }
421
431
  this.isListening = true;
422
432
  // Started listening successfully
@@ -432,34 +442,12 @@ class AsyncStorageListener {
432
442
  if (!this.isListening) {
433
443
  return;
434
444
  }
435
- if (!_asyncStorage.default) {
445
+ if (!_asyncStorageCompat.asyncStorage) {
436
446
  return;
437
447
  }
438
448
 
439
449
  // Stopping listener and restoring original methods
440
-
441
- // Restore original methods
442
- if (this.originalSetItem) {
443
- _asyncStorage.default.setItem = this.originalSetItem;
444
- }
445
- if (this.originalRemoveItem) {
446
- _asyncStorage.default.removeItem = this.originalRemoveItem;
447
- }
448
- if (this.originalMergeItem) {
449
- _asyncStorage.default.mergeItem = this.originalMergeItem;
450
- }
451
- if (this.originalClear) {
452
- _asyncStorage.default.clear = this.originalClear;
453
- }
454
- if (this.originalMultiSet) {
455
- _asyncStorage.default.multiSet = this.originalMultiSet;
456
- }
457
- if (this.originalMultiRemove) {
458
- _asyncStorage.default.multiRemove = this.originalMultiRemove;
459
- }
460
- if (this.originalMultiMerge) {
461
- _asyncStorage.default.multiMerge = this.originalMultiMerge;
462
- }
450
+ this.restoreOriginalMethods();
463
451
  this.isListening = false;
464
452
  // Stopped listening successfully
465
453
  }