@buoy-gg/storage 7.0.15 → 7.0.17
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/lib/commonjs/index.js +1 -0
- package/lib/commonjs/storage/utils/snapshotProvider.js +279 -0
- package/lib/module/index.js +3 -0
- package/lib/module/storage/utils/snapshotProvider.js +277 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +2 -2
- package/lib/typescript/storage/utils/snapshotProvider.d.ts +2 -0
- package/lib/typescript/storage/utils/snapshotProvider.d.ts.map +1 -0
- package/package.json +9 -6
package/lib/commonjs/index.js
CHANGED
|
@@ -303,6 +303,7 @@ Object.defineProperty(exports, "validateStorageValue", {
|
|
|
303
303
|
return _setStorageValue.validateStorageValue;
|
|
304
304
|
}
|
|
305
305
|
});
|
|
306
|
+
require("./storage/utils/snapshotProvider");
|
|
306
307
|
var _preset = require("./preset");
|
|
307
308
|
var _StorageSection = require("./storage/components/StorageSection");
|
|
308
309
|
var _StorageModalWithTabs = require("./storage/components/StorageModalWithTabs");
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _asyncStorage = _interopRequireDefault(require("@react-native-async-storage/async-storage"));
|
|
4
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
5
|
+
var _asyncStorageCompat = require("./asyncStorageCompat");
|
|
6
|
+
var _clearAllStorage = require("./clearAllStorage");
|
|
7
|
+
var _MMKVInstanceRegistry = require("./MMKVInstanceRegistry");
|
|
8
|
+
var _mmkvTypeDetection = require("./mmkvTypeDetection");
|
|
9
|
+
var _SecureStoreRegistry = require("./SecureStoreRegistry");
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
/**
|
|
12
|
+
* Time Machine snapshot provider for device storage (AsyncStorage + MMKV +
|
|
13
|
+
* SecureStore).
|
|
14
|
+
*
|
|
15
|
+
* Registered into @buoy-gg/shared-ui's stateSnapshotRegistry at module init;
|
|
16
|
+
* consumed by @buoy-gg/time-machine.
|
|
17
|
+
*
|
|
18
|
+
* Buoy's own keys (isDevToolsStorageKey — @react_buoy*, @buoy*, buoy-*,
|
|
19
|
+
* legacy patterns) are excluded on BOTH capture and restore, so a snapshot
|
|
20
|
+
* can never clobber dev-tool settings. Restore is a diff: app keys missing
|
|
21
|
+
* from the snapshot are deleted, snapshot entries are written back.
|
|
22
|
+
*
|
|
23
|
+
* Known-lossy by design (each surfaces a warning/skip):
|
|
24
|
+
* - MMKV ArrayBuffer values are never transported.
|
|
25
|
+
* - SecureStore biometric keys are never read or written (auth prompt).
|
|
26
|
+
* - SecureStore keys outside the registered descriptor list are invisible.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
function deleteMMKVKey(instance, key) {
|
|
30
|
+
// v4 renamed delete() to remove() — support both
|
|
31
|
+
if (instance.remove) instance.remove(key);else instance.delete?.(key);
|
|
32
|
+
}
|
|
33
|
+
function status() {
|
|
34
|
+
return {
|
|
35
|
+
canCapture: true,
|
|
36
|
+
canRestore: true
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function capture() {
|
|
40
|
+
const warnings = [];
|
|
41
|
+
const appKeys = (await _asyncStorage.default.getAllKeys()).filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k));
|
|
42
|
+
const asyncPairs = (await (0, _asyncStorageCompat.readMany)(appKeys)).filter(pair => pair[1] !== null);
|
|
43
|
+
const mmkv = {};
|
|
44
|
+
for (const {
|
|
45
|
+
id,
|
|
46
|
+
instance
|
|
47
|
+
} of _MMKVInstanceRegistry.mmkvInstanceRegistry.getAll()) {
|
|
48
|
+
try {
|
|
49
|
+
mmkv[id] = instance.getAllKeys().filter(key => !(0, _sharedUi.isDevToolsStorageKey)(key)).map(key => {
|
|
50
|
+
const {
|
|
51
|
+
value,
|
|
52
|
+
type: valueType
|
|
53
|
+
} = (0, _mmkvTypeDetection.detectMMKVType)(instance, key);
|
|
54
|
+
if (valueType === "buffer") {
|
|
55
|
+
warnings.push(`mmkv "${id}"/"${key}": ArrayBuffer value not capturable`);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
key,
|
|
59
|
+
value,
|
|
60
|
+
valueType
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
} catch {
|
|
64
|
+
warnings.push(`mmkv "${id}": unreadable, captured without entries`);
|
|
65
|
+
mmkv[id] = [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const secure = {};
|
|
69
|
+
const secureModule = _SecureStoreRegistry.secureStoreRegistry.getModule();
|
|
70
|
+
for (const {
|
|
71
|
+
key,
|
|
72
|
+
options
|
|
73
|
+
} of _SecureStoreRegistry.secureStoreRegistry.getAll()) {
|
|
74
|
+
if (options.requireAuthentication) {
|
|
75
|
+
warnings.push(`secure "${key}": biometric-protected, not capturable`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let value = null;
|
|
79
|
+
if (secureModule) {
|
|
80
|
+
try {
|
|
81
|
+
value = await secureModule.getItemAsync(key, options);
|
|
82
|
+
} catch {
|
|
83
|
+
warnings.push(`secure "${key}": unreadable, captured as unset`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
secure[key] = value;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
data: {
|
|
90
|
+
async: asyncPairs,
|
|
91
|
+
mmkv,
|
|
92
|
+
secure
|
|
93
|
+
},
|
|
94
|
+
warnings
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function restore(data) {
|
|
98
|
+
const snapshot = data;
|
|
99
|
+
const result = {
|
|
100
|
+
ok: true,
|
|
101
|
+
applied: 0,
|
|
102
|
+
skipped: [],
|
|
103
|
+
warnings: []
|
|
104
|
+
};
|
|
105
|
+
if (!snapshot) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
applied: 0,
|
|
109
|
+
skipped: [],
|
|
110
|
+
warnings: ["empty storage snapshot"]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── AsyncStorage: delete app keys missing from the snapshot, write the rest ──
|
|
115
|
+
try {
|
|
116
|
+
const snapPairs = (snapshot.async ?? []).filter(([k]) => !(0, _sharedUi.isDevToolsStorageKey)(k));
|
|
117
|
+
const snapKeys = new Set(snapPairs.map(([k]) => k));
|
|
118
|
+
const liveKeys = (await _asyncStorage.default.getAllKeys()).filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k));
|
|
119
|
+
const toRemove = liveKeys.filter(k => !snapKeys.has(k));
|
|
120
|
+
if (toRemove.length > 0) await (0, _asyncStorageCompat.removeMany)(toRemove);
|
|
121
|
+
if (snapPairs.length > 0) await (0, _asyncStorageCompat.writeMany)(snapPairs);
|
|
122
|
+
result.applied += snapPairs.length + toRemove.length;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
result.ok = false;
|
|
125
|
+
result.warnings.push(`asyncstorage restore failed: ${String(e)}`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── MMKV: per registered writable instance, diff against the snapshot ──
|
|
129
|
+
for (const [id, entries] of Object.entries(snapshot.mmkv ?? {})) {
|
|
130
|
+
const registered = _MMKVInstanceRegistry.mmkvInstanceRegistry.get(id);
|
|
131
|
+
if (!registered) {
|
|
132
|
+
result.skipped.push({
|
|
133
|
+
id: `mmkv:${id}`,
|
|
134
|
+
reason: "instance not registered"
|
|
135
|
+
});
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (registered.instance.isReadOnly) {
|
|
139
|
+
result.skipped.push({
|
|
140
|
+
id: `mmkv:${id}`,
|
|
141
|
+
reason: "instance registered read-only"
|
|
142
|
+
});
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const {
|
|
146
|
+
instance
|
|
147
|
+
} = registered;
|
|
148
|
+
try {
|
|
149
|
+
const snapKeys = new Set(entries.map(e => e.key));
|
|
150
|
+
const liveKeys = instance.getAllKeys().filter(key => !(0, _sharedUi.isDevToolsStorageKey)(key));
|
|
151
|
+
for (const key of liveKeys) {
|
|
152
|
+
if (!snapKeys.has(key)) {
|
|
153
|
+
deleteMMKVKey(instance, key);
|
|
154
|
+
result.applied += 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
for (const entry of entries) {
|
|
158
|
+
if ((0, _sharedUi.isDevToolsStorageKey)(entry.key)) continue;
|
|
159
|
+
if (entry.valueType === "buffer") {
|
|
160
|
+
result.skipped.push({
|
|
161
|
+
id: `mmkv:${id}/${entry.key}`,
|
|
162
|
+
reason: "ArrayBuffer values cannot be restored"
|
|
163
|
+
});
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
instance.set(entry.key, entry.value);
|
|
167
|
+
result.applied += 1;
|
|
168
|
+
}
|
|
169
|
+
} catch (e) {
|
|
170
|
+
result.ok = false;
|
|
171
|
+
result.warnings.push(`mmkv "${id}" restore failed: ${String(e)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── SecureStore: diff over the registered, non-biometric descriptor list ──
|
|
176
|
+
const secureSnap = snapshot.secure ?? {};
|
|
177
|
+
for (const {
|
|
178
|
+
key,
|
|
179
|
+
options
|
|
180
|
+
} of _SecureStoreRegistry.secureStoreRegistry.getAll()) {
|
|
181
|
+
if (options.requireAuthentication) {
|
|
182
|
+
if (key in secureSnap) {
|
|
183
|
+
result.skipped.push({
|
|
184
|
+
id: `secure:${key}`,
|
|
185
|
+
reason: "biometric-protected keys are never written"
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
if (key in secureSnap) {
|
|
192
|
+
const value = secureSnap[key];
|
|
193
|
+
if (value === null) await _SecureStoreRegistry.secureStoreRegistry.deleteValue(key);else await _SecureStoreRegistry.secureStoreRegistry.setValue(key, value);
|
|
194
|
+
} else {
|
|
195
|
+
// Registered key with no snapshot entry — snapshot predates it; unset.
|
|
196
|
+
await _SecureStoreRegistry.secureStoreRegistry.deleteValue(key);
|
|
197
|
+
}
|
|
198
|
+
result.applied += 1;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
result.ok = false;
|
|
201
|
+
result.warnings.push(`secure "${key}" restore failed: ${String(e)}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const key of Object.keys(secureSnap)) {
|
|
205
|
+
if (!_SecureStoreRegistry.secureStoreRegistry.getAll().some(e => e.key === key)) {
|
|
206
|
+
result.skipped.push({
|
|
207
|
+
id: `secure:${key}`,
|
|
208
|
+
reason: "key no longer registered with registerSecureStoreKeys"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Wipe to fresh-install state (Buoy keys preserved). Used by baseline restore. */
|
|
216
|
+
async function clear() {
|
|
217
|
+
const result = {
|
|
218
|
+
ok: true,
|
|
219
|
+
applied: 0,
|
|
220
|
+
skipped: [],
|
|
221
|
+
warnings: []
|
|
222
|
+
};
|
|
223
|
+
try {
|
|
224
|
+
await (0, _clearAllStorage.clearAllAppStorage)();
|
|
225
|
+
result.applied += 1;
|
|
226
|
+
} catch (e) {
|
|
227
|
+
result.ok = false;
|
|
228
|
+
result.warnings.push(`asyncstorage clear failed: ${String(e)}`);
|
|
229
|
+
}
|
|
230
|
+
for (const {
|
|
231
|
+
id,
|
|
232
|
+
instance
|
|
233
|
+
} of _MMKVInstanceRegistry.mmkvInstanceRegistry.getAll()) {
|
|
234
|
+
if (instance.isReadOnly) {
|
|
235
|
+
result.skipped.push({
|
|
236
|
+
id: `mmkv:${id}`,
|
|
237
|
+
reason: "instance registered read-only"
|
|
238
|
+
});
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
for (const key of instance.getAllKeys().filter(k => !(0, _sharedUi.isDevToolsStorageKey)(k))) {
|
|
243
|
+
deleteMMKVKey(instance, key);
|
|
244
|
+
result.applied += 1;
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
result.ok = false;
|
|
248
|
+
result.warnings.push(`mmkv "${id}" clear failed: ${String(e)}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
for (const {
|
|
252
|
+
key,
|
|
253
|
+
options
|
|
254
|
+
} of _SecureStoreRegistry.secureStoreRegistry.getAll()) {
|
|
255
|
+
if (options.requireAuthentication) {
|
|
256
|
+
result.skipped.push({
|
|
257
|
+
id: `secure:${key}`,
|
|
258
|
+
reason: "biometric-protected keys are never written"
|
|
259
|
+
});
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
await _SecureStoreRegistry.secureStoreRegistry.deleteValue(key);
|
|
264
|
+
result.applied += 1;
|
|
265
|
+
} catch (e) {
|
|
266
|
+
result.ok = false;
|
|
267
|
+
result.warnings.push(`secure "${key}" clear failed: ${String(e)}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
(0, _sharedUi.registerStateSnapshotProvider)({
|
|
273
|
+
id: "storage",
|
|
274
|
+
label: "Storage",
|
|
275
|
+
status,
|
|
276
|
+
capture,
|
|
277
|
+
restore,
|
|
278
|
+
clear
|
|
279
|
+
});
|
package/lib/module/index.js
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* bypassing the tool's intended usage patterns.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
// Side-effect: registers the Time Machine snapshot provider
|
|
15
|
+
import "./storage/utils/snapshotProvider";
|
|
16
|
+
|
|
14
17
|
// =============================================================================
|
|
15
18
|
// PRESET (Primary entry point for users)
|
|
16
19
|
// =============================================================================
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Time Machine snapshot provider for device storage (AsyncStorage + MMKV +
|
|
5
|
+
* SecureStore).
|
|
6
|
+
*
|
|
7
|
+
* Registered into @buoy-gg/shared-ui's stateSnapshotRegistry at module init;
|
|
8
|
+
* consumed by @buoy-gg/time-machine.
|
|
9
|
+
*
|
|
10
|
+
* Buoy's own keys (isDevToolsStorageKey — @react_buoy*, @buoy*, buoy-*,
|
|
11
|
+
* legacy patterns) are excluded on BOTH capture and restore, so a snapshot
|
|
12
|
+
* can never clobber dev-tool settings. Restore is a diff: app keys missing
|
|
13
|
+
* from the snapshot are deleted, snapshot entries are written back.
|
|
14
|
+
*
|
|
15
|
+
* Known-lossy by design (each surfaces a warning/skip):
|
|
16
|
+
* - MMKV ArrayBuffer values are never transported.
|
|
17
|
+
* - SecureStore biometric keys are never read or written (auth prompt).
|
|
18
|
+
* - SecureStore keys outside the registered descriptor list are invisible.
|
|
19
|
+
*/
|
|
20
|
+
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
21
|
+
import { isDevToolsStorageKey, registerStateSnapshotProvider } from "@buoy-gg/shared-ui";
|
|
22
|
+
import { readMany, writeMany, removeMany } from "./asyncStorageCompat";
|
|
23
|
+
import { clearAllAppStorage } from "./clearAllStorage";
|
|
24
|
+
import { mmkvInstanceRegistry } from "./MMKVInstanceRegistry";
|
|
25
|
+
import { detectMMKVType } from "./mmkvTypeDetection";
|
|
26
|
+
import { secureStoreRegistry } from "./SecureStoreRegistry";
|
|
27
|
+
function deleteMMKVKey(instance, key) {
|
|
28
|
+
// v4 renamed delete() to remove() — support both
|
|
29
|
+
if (instance.remove) instance.remove(key);else instance.delete?.(key);
|
|
30
|
+
}
|
|
31
|
+
function status() {
|
|
32
|
+
return {
|
|
33
|
+
canCapture: true,
|
|
34
|
+
canRestore: true
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function capture() {
|
|
38
|
+
const warnings = [];
|
|
39
|
+
const appKeys = (await AsyncStorage.getAllKeys()).filter(k => !isDevToolsStorageKey(k));
|
|
40
|
+
const asyncPairs = (await readMany(appKeys)).filter(pair => pair[1] !== null);
|
|
41
|
+
const mmkv = {};
|
|
42
|
+
for (const {
|
|
43
|
+
id,
|
|
44
|
+
instance
|
|
45
|
+
} of mmkvInstanceRegistry.getAll()) {
|
|
46
|
+
try {
|
|
47
|
+
mmkv[id] = instance.getAllKeys().filter(key => !isDevToolsStorageKey(key)).map(key => {
|
|
48
|
+
const {
|
|
49
|
+
value,
|
|
50
|
+
type: valueType
|
|
51
|
+
} = detectMMKVType(instance, key);
|
|
52
|
+
if (valueType === "buffer") {
|
|
53
|
+
warnings.push(`mmkv "${id}"/"${key}": ArrayBuffer value not capturable`);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
key,
|
|
57
|
+
value,
|
|
58
|
+
valueType
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
} catch {
|
|
62
|
+
warnings.push(`mmkv "${id}": unreadable, captured without entries`);
|
|
63
|
+
mmkv[id] = [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const secure = {};
|
|
67
|
+
const secureModule = secureStoreRegistry.getModule();
|
|
68
|
+
for (const {
|
|
69
|
+
key,
|
|
70
|
+
options
|
|
71
|
+
} of secureStoreRegistry.getAll()) {
|
|
72
|
+
if (options.requireAuthentication) {
|
|
73
|
+
warnings.push(`secure "${key}": biometric-protected, not capturable`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
let value = null;
|
|
77
|
+
if (secureModule) {
|
|
78
|
+
try {
|
|
79
|
+
value = await secureModule.getItemAsync(key, options);
|
|
80
|
+
} catch {
|
|
81
|
+
warnings.push(`secure "${key}": unreadable, captured as unset`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
secure[key] = value;
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
data: {
|
|
88
|
+
async: asyncPairs,
|
|
89
|
+
mmkv,
|
|
90
|
+
secure
|
|
91
|
+
},
|
|
92
|
+
warnings
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function restore(data) {
|
|
96
|
+
const snapshot = data;
|
|
97
|
+
const result = {
|
|
98
|
+
ok: true,
|
|
99
|
+
applied: 0,
|
|
100
|
+
skipped: [],
|
|
101
|
+
warnings: []
|
|
102
|
+
};
|
|
103
|
+
if (!snapshot) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
applied: 0,
|
|
107
|
+
skipped: [],
|
|
108
|
+
warnings: ["empty storage snapshot"]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── AsyncStorage: delete app keys missing from the snapshot, write the rest ──
|
|
113
|
+
try {
|
|
114
|
+
const snapPairs = (snapshot.async ?? []).filter(([k]) => !isDevToolsStorageKey(k));
|
|
115
|
+
const snapKeys = new Set(snapPairs.map(([k]) => k));
|
|
116
|
+
const liveKeys = (await AsyncStorage.getAllKeys()).filter(k => !isDevToolsStorageKey(k));
|
|
117
|
+
const toRemove = liveKeys.filter(k => !snapKeys.has(k));
|
|
118
|
+
if (toRemove.length > 0) await removeMany(toRemove);
|
|
119
|
+
if (snapPairs.length > 0) await writeMany(snapPairs);
|
|
120
|
+
result.applied += snapPairs.length + toRemove.length;
|
|
121
|
+
} catch (e) {
|
|
122
|
+
result.ok = false;
|
|
123
|
+
result.warnings.push(`asyncstorage restore failed: ${String(e)}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── MMKV: per registered writable instance, diff against the snapshot ──
|
|
127
|
+
for (const [id, entries] of Object.entries(snapshot.mmkv ?? {})) {
|
|
128
|
+
const registered = mmkvInstanceRegistry.get(id);
|
|
129
|
+
if (!registered) {
|
|
130
|
+
result.skipped.push({
|
|
131
|
+
id: `mmkv:${id}`,
|
|
132
|
+
reason: "instance not registered"
|
|
133
|
+
});
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (registered.instance.isReadOnly) {
|
|
137
|
+
result.skipped.push({
|
|
138
|
+
id: `mmkv:${id}`,
|
|
139
|
+
reason: "instance registered read-only"
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const {
|
|
144
|
+
instance
|
|
145
|
+
} = registered;
|
|
146
|
+
try {
|
|
147
|
+
const snapKeys = new Set(entries.map(e => e.key));
|
|
148
|
+
const liveKeys = instance.getAllKeys().filter(key => !isDevToolsStorageKey(key));
|
|
149
|
+
for (const key of liveKeys) {
|
|
150
|
+
if (!snapKeys.has(key)) {
|
|
151
|
+
deleteMMKVKey(instance, key);
|
|
152
|
+
result.applied += 1;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (isDevToolsStorageKey(entry.key)) continue;
|
|
157
|
+
if (entry.valueType === "buffer") {
|
|
158
|
+
result.skipped.push({
|
|
159
|
+
id: `mmkv:${id}/${entry.key}`,
|
|
160
|
+
reason: "ArrayBuffer values cannot be restored"
|
|
161
|
+
});
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
instance.set(entry.key, entry.value);
|
|
165
|
+
result.applied += 1;
|
|
166
|
+
}
|
|
167
|
+
} catch (e) {
|
|
168
|
+
result.ok = false;
|
|
169
|
+
result.warnings.push(`mmkv "${id}" restore failed: ${String(e)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── SecureStore: diff over the registered, non-biometric descriptor list ──
|
|
174
|
+
const secureSnap = snapshot.secure ?? {};
|
|
175
|
+
for (const {
|
|
176
|
+
key,
|
|
177
|
+
options
|
|
178
|
+
} of secureStoreRegistry.getAll()) {
|
|
179
|
+
if (options.requireAuthentication) {
|
|
180
|
+
if (key in secureSnap) {
|
|
181
|
+
result.skipped.push({
|
|
182
|
+
id: `secure:${key}`,
|
|
183
|
+
reason: "biometric-protected keys are never written"
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
if (key in secureSnap) {
|
|
190
|
+
const value = secureSnap[key];
|
|
191
|
+
if (value === null) await secureStoreRegistry.deleteValue(key);else await secureStoreRegistry.setValue(key, value);
|
|
192
|
+
} else {
|
|
193
|
+
// Registered key with no snapshot entry — snapshot predates it; unset.
|
|
194
|
+
await secureStoreRegistry.deleteValue(key);
|
|
195
|
+
}
|
|
196
|
+
result.applied += 1;
|
|
197
|
+
} catch (e) {
|
|
198
|
+
result.ok = false;
|
|
199
|
+
result.warnings.push(`secure "${key}" restore failed: ${String(e)}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
for (const key of Object.keys(secureSnap)) {
|
|
203
|
+
if (!secureStoreRegistry.getAll().some(e => e.key === key)) {
|
|
204
|
+
result.skipped.push({
|
|
205
|
+
id: `secure:${key}`,
|
|
206
|
+
reason: "key no longer registered with registerSecureStoreKeys"
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Wipe to fresh-install state (Buoy keys preserved). Used by baseline restore. */
|
|
214
|
+
async function clear() {
|
|
215
|
+
const result = {
|
|
216
|
+
ok: true,
|
|
217
|
+
applied: 0,
|
|
218
|
+
skipped: [],
|
|
219
|
+
warnings: []
|
|
220
|
+
};
|
|
221
|
+
try {
|
|
222
|
+
await clearAllAppStorage();
|
|
223
|
+
result.applied += 1;
|
|
224
|
+
} catch (e) {
|
|
225
|
+
result.ok = false;
|
|
226
|
+
result.warnings.push(`asyncstorage clear failed: ${String(e)}`);
|
|
227
|
+
}
|
|
228
|
+
for (const {
|
|
229
|
+
id,
|
|
230
|
+
instance
|
|
231
|
+
} of mmkvInstanceRegistry.getAll()) {
|
|
232
|
+
if (instance.isReadOnly) {
|
|
233
|
+
result.skipped.push({
|
|
234
|
+
id: `mmkv:${id}`,
|
|
235
|
+
reason: "instance registered read-only"
|
|
236
|
+
});
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
for (const key of instance.getAllKeys().filter(k => !isDevToolsStorageKey(k))) {
|
|
241
|
+
deleteMMKVKey(instance, key);
|
|
242
|
+
result.applied += 1;
|
|
243
|
+
}
|
|
244
|
+
} catch (e) {
|
|
245
|
+
result.ok = false;
|
|
246
|
+
result.warnings.push(`mmkv "${id}" clear failed: ${String(e)}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
for (const {
|
|
250
|
+
key,
|
|
251
|
+
options
|
|
252
|
+
} of secureStoreRegistry.getAll()) {
|
|
253
|
+
if (options.requireAuthentication) {
|
|
254
|
+
result.skipped.push({
|
|
255
|
+
id: `secure:${key}`,
|
|
256
|
+
reason: "biometric-protected keys are never written"
|
|
257
|
+
});
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
await secureStoreRegistry.deleteValue(key);
|
|
262
|
+
result.applied += 1;
|
|
263
|
+
} catch (e) {
|
|
264
|
+
result.ok = false;
|
|
265
|
+
result.warnings.push(`secure "${key}" clear failed: ${String(e)}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return result;
|
|
269
|
+
}
|
|
270
|
+
registerStateSnapshotProvider({
|
|
271
|
+
id: "storage",
|
|
272
|
+
label: "Storage",
|
|
273
|
+
status,
|
|
274
|
+
capture,
|
|
275
|
+
restore,
|
|
276
|
+
clear
|
|
277
|
+
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* Internal listeners and capture controls are not exported to prevent
|
|
9
9
|
* bypassing the tool's intended usage patterns.
|
|
10
10
|
*/
|
|
11
|
+
import "./storage/utils/snapshotProvider";
|
|
11
12
|
export { storageToolPreset, createStorageTool } from "./preset";
|
|
12
13
|
export { StorageSection } from "./storage/components/StorageSection";
|
|
13
14
|
export { StorageModalWithTabs, StorageToolView, } from "./storage/components/StorageModalWithTabs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -43,7 +43,7 @@ export declare const storageSyncAdapter: {
|
|
|
43
43
|
/** Undo one event, addressed by the `id` the store assigned it. */
|
|
44
44
|
"timeTravel.undo": (params: unknown) => Promise<{
|
|
45
45
|
undone: boolean;
|
|
46
|
-
action: "
|
|
46
|
+
action: "mergeItem" | "multiSet" | "multiRemove" | "clear" | "setItem" | "removeItem" | "multiMerge";
|
|
47
47
|
key: string | undefined;
|
|
48
48
|
}>;
|
|
49
49
|
/**
|
|
@@ -53,7 +53,7 @@ export declare const storageSyncAdapter: {
|
|
|
53
53
|
*/
|
|
54
54
|
"timeTravel.jump": (params: unknown) => Promise<{
|
|
55
55
|
jumped: boolean;
|
|
56
|
-
scope: "
|
|
56
|
+
scope: "key" | "all";
|
|
57
57
|
replayed: number;
|
|
58
58
|
of: number;
|
|
59
59
|
key: string | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshotProvider.d.ts","sourceRoot":"","sources":["../../../../src/storage/utils/snapshotProvider.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/storage",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.17",
|
|
4
4
|
"description": "Browse & edit AsyncStorage, MMKV & SecureStore inside your React Native app, with change history. Part of Buoy devtools.",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
|
@@ -24,13 +24,16 @@
|
|
|
24
24
|
"files": [
|
|
25
25
|
"lib"
|
|
26
26
|
],
|
|
27
|
-
"sideEffects":
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"**/snapshotProvider.js",
|
|
29
|
+
"**/snapshotProvider.ts"
|
|
30
|
+
],
|
|
28
31
|
"dependencies": {
|
|
29
|
-
"@buoy-gg/floating-tools-core": "^7.0.
|
|
30
|
-
"@buoy-gg/shared-ui": "^7.0.
|
|
32
|
+
"@buoy-gg/floating-tools-core": "^7.0.17",
|
|
33
|
+
"@buoy-gg/shared-ui": "^7.0.17"
|
|
31
34
|
},
|
|
32
35
|
"peerDependencies": {
|
|
33
|
-
"@buoy-gg/license": "7.0.
|
|
36
|
+
"@buoy-gg/license": "7.0.17",
|
|
34
37
|
"@react-native-async-storage/async-storage": "^2.0.0 || ^3.1.0",
|
|
35
38
|
"react": "*",
|
|
36
39
|
"react-native": "*"
|
|
@@ -40,7 +43,7 @@
|
|
|
40
43
|
"@types/react": "^19.1.0",
|
|
41
44
|
"@types/react-native": "^0.73.0",
|
|
42
45
|
"typescript": "~5.8.3",
|
|
43
|
-
"@buoy-gg/license": "^7.0.
|
|
46
|
+
"@buoy-gg/license": "^7.0.17"
|
|
44
47
|
},
|
|
45
48
|
"react-native-builder-bob": {
|
|
46
49
|
"source": "src",
|