@buoy-gg/storage 3.0.2 → 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.
- package/README.md +1 -1
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +24 -3
- package/lib/commonjs/storage/components/SelectionActionBar.js +16 -3
- package/lib/commonjs/storage/components/StorageBrowserMode.js +6 -2
- package/lib/commonjs/storage/components/StorageKeyRow.js +95 -6
- package/lib/commonjs/storage/components/StorageKeySection.js +8 -2
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +47 -1
- package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +3 -2
- package/lib/commonjs/storage/stores/storageEventStore.js +7 -4
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +7 -3
- package/lib/commonjs/storage/utils/AsyncStorageListener.js +148 -160
- package/lib/commonjs/storage/utils/asyncStorageCompat.js +89 -0
- package/lib/commonjs/storage/utils/clearAllStorage.js +2 -1
- package/lib/commonjs/storage/utils/mmkvTypeDetection.js +20 -5
- package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +3 -2
- package/lib/commonjs/storage/utils/valueType.js +41 -0
- package/lib/module/storage/components/GameUIStorageBrowser.js +24 -3
- package/lib/module/storage/components/SelectionActionBar.js +17 -3
- package/lib/module/storage/components/StorageBrowserMode.js +6 -2
- package/lib/module/storage/components/StorageKeyRow.js +97 -8
- package/lib/module/storage/components/StorageKeySection.js +8 -2
- package/lib/module/storage/components/StorageModalWithTabs.js +47 -1
- package/lib/module/storage/hooks/useAsyncStorageKeys.js +3 -2
- package/lib/module/storage/stores/storageEventStore.js +7 -4
- package/lib/module/storage/sync/storageSyncAdapter.js +7 -3
- package/lib/module/storage/utils/AsyncStorageListener.js +124 -135
- package/lib/module/storage/utils/asyncStorageCompat.js +81 -0
- package/lib/module/storage/utils/clearAllStorage.js +2 -1
- package/lib/module/storage/utils/mmkvTypeDetection.js +20 -5
- package/lib/module/storage/utils/storageTimeTravelUtils.js +3 -2
- package/lib/module/storage/utils/valueType.js +39 -0
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +5 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts +3 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts +3 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyRow.d.ts +7 -1
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +1 -1
- package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +20 -0
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/asyncStorageCompat.d.ts +30 -0
- package/lib/typescript/storage/utils/asyncStorageCompat.d.ts.map +1 -0
- package/lib/typescript/storage/utils/clearAllStorage.d.ts.map +1 -1
- package/lib/typescript/storage/utils/mmkvTypeDetection.d.ts.map +1 -1
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
- package/lib/typescript/storage/utils/valueType.d.ts +13 -0
- package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
- package/package.json +6 -6
|
@@ -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
|
|
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 (
|
|
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
|
-
|
|
109
|
-
this.
|
|
110
|
-
this.
|
|
111
|
-
this.originalClear =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 (!
|
|
147
|
+
if (!_asyncStorageCompat.asyncStorage || !this.originalSetItem) {
|
|
129
148
|
return;
|
|
130
149
|
}
|
|
131
|
-
|
|
150
|
+
_asyncStorageCompat.asyncStorage.setItem = this.originalSetItem;
|
|
132
151
|
if (this.originalRemoveItem) {
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
162
|
+
_asyncStorageCompat.asyncStorage.multiSet = this.originalMultiSet;
|
|
143
163
|
}
|
|
144
164
|
if (this.originalMultiRemove) {
|
|
145
|
-
|
|
165
|
+
_asyncStorageCompat.asyncStorage.multiRemove = this.originalMultiRemove;
|
|
146
166
|
}
|
|
147
167
|
if (this.originalMultiMerge) {
|
|
148
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
233
|
-
|
|
330
|
+
if (_asyncStorageCompat.asyncStorage) {
|
|
331
|
+
_asyncStorageCompat.asyncStorage.setItem = swizzled_setItem;
|
|
234
332
|
}
|
|
235
333
|
|
|
236
334
|
// Swizzle removeItem
|
|
237
|
-
if (
|
|
238
|
-
|
|
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
|
|
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 (
|
|
264
|
-
|
|
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
|
|
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 (
|
|
291
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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 (!
|
|
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
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = exports.asyncStorageCaps = exports.asyncStorage = void 0;
|
|
7
|
+
exports.readMany = readMany;
|
|
8
|
+
exports.removeMany = removeMany;
|
|
9
|
+
exports.writeMany = writeMany;
|
|
10
|
+
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
/**
|
|
13
|
+
* AsyncStorage compatibility adapter (v2 + v3)
|
|
14
|
+
*
|
|
15
|
+
* `@react-native-async-storage/async-storage` 3.x reshaped the default-export
|
|
16
|
+
* API. The single-item methods kept their v2 signatures, but the batch + merge
|
|
17
|
+
* methods changed:
|
|
18
|
+
*
|
|
19
|
+
* v2 (what this package was written against) v3
|
|
20
|
+
* ------------------------------------------ --------------------------------
|
|
21
|
+
* multiGet(keys): [key, value|null][] getMany(keys): Record<key, value|null>
|
|
22
|
+
* multiSet([[k, v]]): void setMany({ k: v }): void
|
|
23
|
+
* multiRemove(keys): void removeMany(keys): void
|
|
24
|
+
* mergeItem / multiMerge removed entirely
|
|
25
|
+
*
|
|
26
|
+
* This module is the single seam between the storage tool and whichever
|
|
27
|
+
* async-storage version the host app installed. The rest of the package always
|
|
28
|
+
* speaks the v2 tuple-shaped API; we translate to v3 here when needed.
|
|
29
|
+
*
|
|
30
|
+
* Reads (`readMany`) are routed through the *unswizzled* native methods, so the
|
|
31
|
+
* event listener can safely call them while capturing previous values without
|
|
32
|
+
* re-triggering itself.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// Loosely typed because the surface differs across major versions; callers go
|
|
36
|
+
// through the helpers below rather than touching this directly.
|
|
37
|
+
|
|
38
|
+
/** The live default-export object (methods may be swizzled by the listener). */
|
|
39
|
+
const asyncStorage = exports.asyncStorage = _asyncStorage.default;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Capability flags describing which API surface the installed version exposes.
|
|
43
|
+
* `multiGet` only exists on v2; `mergeItem` was removed in v3.
|
|
44
|
+
*/
|
|
45
|
+
const asyncStorageCaps = exports.asyncStorageCaps = {
|
|
46
|
+
/** v2 exposes multiGet/multiSet/multiRemove; v3 renamed these to *Many. */
|
|
47
|
+
hasLegacyMultiApi: typeof asyncStorage?.multiGet === "function",
|
|
48
|
+
/** v3 removed mergeItem/multiMerge with no replacement. */
|
|
49
|
+
hasMergeApi: typeof asyncStorage?.mergeItem === "function"
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Batch-read keys, normalized to the v2 `[key, value|null][]` tuple shape and
|
|
54
|
+
* preserving the requested key order regardless of installed version.
|
|
55
|
+
*/
|
|
56
|
+
async function readMany(keys) {
|
|
57
|
+
if (typeof asyncStorage.multiGet === "function") {
|
|
58
|
+
return await asyncStorage.multiGet(keys);
|
|
59
|
+
}
|
|
60
|
+
// v3: getMany returns a Record; rebuild ordered tuples ("what you request is
|
|
61
|
+
// what you get" — missing keys come back as null).
|
|
62
|
+
const record = await asyncStorage.getMany(keys);
|
|
63
|
+
return keys.map(key => [key, record[key] ?? null]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Batch-write key/value pairs. Calls the live method (which may be swizzled by
|
|
68
|
+
* the listener on v2's `multiSet` / v3's `setMany`) so writes still emit events.
|
|
69
|
+
*/
|
|
70
|
+
async function writeMany(pairs) {
|
|
71
|
+
if (typeof asyncStorage.multiSet === "function") {
|
|
72
|
+
return asyncStorage.multiSet(pairs);
|
|
73
|
+
}
|
|
74
|
+
const record = {};
|
|
75
|
+
for (const [key, value] of pairs) record[key] = value;
|
|
76
|
+
return asyncStorage.setMany(record);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Batch-remove keys via the live method (swizzled on v2's `multiRemove` /
|
|
81
|
+
* v3's `removeMany`).
|
|
82
|
+
*/
|
|
83
|
+
async function removeMany(keys) {
|
|
84
|
+
if (typeof asyncStorage.multiRemove === "function") {
|
|
85
|
+
return asyncStorage.multiRemove(keys);
|
|
86
|
+
}
|
|
87
|
+
return asyncStorage.removeMany(keys);
|
|
88
|
+
}
|
|
89
|
+
var _default = exports.default = asyncStorage;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.clearAllAppStorage = clearAllAppStorage;
|
|
7
7
|
exports.clearAllStorageIncludingDevTools = clearAllStorageIncludingDevTools;
|
|
8
8
|
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
9
|
+
var _asyncStorageCompat = require("./asyncStorageCompat");
|
|
9
10
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
10
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
12
|
/**
|
|
@@ -30,7 +31,7 @@ async function clearAllAppStorage() {
|
|
|
30
31
|
// Clearing ${keysToRemove.length} app storage keys
|
|
31
32
|
|
|
32
33
|
// Remove all non-dev-tool keys
|
|
33
|
-
await
|
|
34
|
+
await (0, _asyncStorageCompat.removeMany)(keysToRemove);
|
|
34
35
|
|
|
35
36
|
// Successfully cleared app storage
|
|
36
37
|
}
|
|
@@ -42,16 +42,25 @@ exports.isTypeMatch = isTypeMatch;
|
|
|
42
42
|
* ```
|
|
43
43
|
*/
|
|
44
44
|
function detectMMKVType(instance, key) {
|
|
45
|
-
//
|
|
45
|
+
// MMKV stores no type metadata, so the native getters can cross-read: in
|
|
46
|
+
// particular `getString` returns "" (an empty string, NOT undefined) for many
|
|
47
|
+
// non-string values like numbers and `false`. Trying getString first and
|
|
48
|
+
// trusting its result therefore mis-detects those as empty strings — e.g.
|
|
49
|
+
// a key set to 42 or false would render as "". Probe the getters and only let
|
|
50
|
+
// getString win when it returns a NON-empty string; treat an empty result as
|
|
51
|
+
// ambiguous and prefer the number/boolean getters.
|
|
46
52
|
const stringValue = instance.getString(key);
|
|
47
|
-
|
|
53
|
+
|
|
54
|
+
// A non-empty string is unambiguously a string.
|
|
55
|
+
if (stringValue !== undefined && stringValue !== '') {
|
|
48
56
|
return {
|
|
49
57
|
value: stringValue,
|
|
50
58
|
type: 'string'
|
|
51
59
|
};
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
//
|
|
62
|
+
// Empty getString result is ambiguous (genuine "" vs a number/boolean the
|
|
63
|
+
// native layer decoded to ""). Prefer typed getters when they have a value.
|
|
55
64
|
const numberValue = instance.getNumber(key);
|
|
56
65
|
if (numberValue !== undefined) {
|
|
57
66
|
return {
|
|
@@ -59,8 +68,6 @@ function detectMMKVType(instance, key) {
|
|
|
59
68
|
type: 'number'
|
|
60
69
|
};
|
|
61
70
|
}
|
|
62
|
-
|
|
63
|
-
// Try boolean
|
|
64
71
|
const booleanValue = instance.getBoolean(key);
|
|
65
72
|
if (booleanValue !== undefined) {
|
|
66
73
|
return {
|
|
@@ -69,6 +76,14 @@ function detectMMKVType(instance, key) {
|
|
|
69
76
|
};
|
|
70
77
|
}
|
|
71
78
|
|
|
79
|
+
// getString returned "" and nothing else matched -> a genuine empty string.
|
|
80
|
+
if (stringValue === '') {
|
|
81
|
+
return {
|
|
82
|
+
value: '',
|
|
83
|
+
type: 'string'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
72
87
|
// Try buffer (least common)
|
|
73
88
|
const bufferValue = instance.getBuffer(key);
|
|
74
89
|
if (bufferValue !== undefined) {
|
|
@@ -7,6 +7,7 @@ exports.canUndo = canUndo;
|
|
|
7
7
|
exports.jumpToState = jumpToState;
|
|
8
8
|
exports.undoOperation = undoOperation;
|
|
9
9
|
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
10
|
+
var _asyncStorageCompat = require("./asyncStorageCompat");
|
|
10
11
|
var _AsyncStorageListener = require("./AsyncStorageListener");
|
|
11
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
13
|
/**
|
|
@@ -79,7 +80,7 @@ async function undoOperation(event) {
|
|
|
79
80
|
// Restore all cleared key-value pairs
|
|
80
81
|
const pairsToRestore = data.prevPairs.filter(([, value]) => value !== null);
|
|
81
82
|
if (pairsToRestore.length > 0) {
|
|
82
|
-
await
|
|
83
|
+
await (0, _asyncStorageCompat.writeMany)(pairsToRestore);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
break;
|
|
@@ -131,7 +132,7 @@ async function jumpToState(events, targetEventIndex) {
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
if (pairsToSet.length > 0) {
|
|
134
|
-
await
|
|
135
|
+
await (0, _asyncStorageCompat.writeMany)(pairsToSet);
|
|
135
136
|
}
|
|
136
137
|
} finally {
|
|
137
138
|
// Always resume capture
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.getValuePreview = getValuePreview;
|
|
6
7
|
exports.getValueTypeLabel = getValueTypeLabel;
|
|
8
|
+
exports.getValueTypeWithPreview = getValueTypeWithPreview;
|
|
7
9
|
/** Return a concise string describing the JavaScript type of the provided value. */
|
|
8
10
|
function getValueTypeLabel(value) {
|
|
9
11
|
if (value === null) return "null";
|
|
@@ -15,4 +17,43 @@ function getValueTypeLabel(value) {
|
|
|
15
17
|
if (type === "number") return "number";
|
|
16
18
|
if (type === "string") return "string";
|
|
17
19
|
return "unknown";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Longest value preview rendered inline next to the type label. */
|
|
23
|
+
const MAX_INLINE_PREVIEW = 40;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Short, human-readable preview of a value for inline display next to the type
|
|
27
|
+
* label, or `null` when the value is too large/complex to preview (long
|
|
28
|
+
* strings, objects, arrays). Lets the user read simple values without opening
|
|
29
|
+
* the card.
|
|
30
|
+
*/
|
|
31
|
+
function getValuePreview(value) {
|
|
32
|
+
switch (typeof value) {
|
|
33
|
+
case "boolean":
|
|
34
|
+
return value ? "true" : "false";
|
|
35
|
+
case "number":
|
|
36
|
+
return Number.isFinite(value) ? String(value) : null;
|
|
37
|
+
case "string":
|
|
38
|
+
{
|
|
39
|
+
if (value.length === 0) return '""';
|
|
40
|
+
if (value.length > MAX_INLINE_PREVIEW) return null;
|
|
41
|
+
// Collapse whitespace so multi-line/tabbed values stay on one line.
|
|
42
|
+
return `"${value.replace(/\s+/g, " ")}"`;
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
return null;
|
|
46
|
+
// objects, arrays, null, undefined
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Type label optionally followed by a short value preview — e.g. `boolean · true`,
|
|
52
|
+
* `number · 42`, `string · "hello"`. Falls back to just the type label when the
|
|
53
|
+
* value is too large to preview.
|
|
54
|
+
*/
|
|
55
|
+
function getValueTypeWithPreview(value) {
|
|
56
|
+
const label = getValueTypeLabel(value);
|
|
57
|
+
const preview = getValuePreview(value);
|
|
58
|
+
return preview ? `${label} · ${preview}` : label;
|
|
18
59
|
}
|