@buoy-gg/storage 3.0.1 → 3.0.2

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 (33) hide show
  1. package/lib/commonjs/index.js +7 -0
  2. package/lib/commonjs/storage/components/GameUIStorageBrowser.js +1 -1
  3. package/lib/commonjs/storage/components/GameUIStorageStats.js +2 -2
  4. package/lib/commonjs/storage/components/StorageEventDetailContent.js +2 -2
  5. package/lib/commonjs/storage/components/StorageKeyCard.js +5 -5
  6. package/lib/commonjs/storage/components/StorageKeyRow.js +2 -2
  7. package/lib/commonjs/storage/components/StorageKeySection.js +2 -2
  8. package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +12 -1
  9. package/lib/commonjs/storage/hooks/useMMKVKeys.js +20 -2
  10. package/lib/commonjs/storage/stores/storageEventStore.js +81 -0
  11. package/lib/commonjs/storage/sync/storageSyncAdapter.js +49 -0
  12. package/lib/module/index.js +5 -0
  13. package/lib/module/storage/components/GameUIStorageBrowser.js +2 -1
  14. package/lib/module/storage/components/GameUIStorageStats.js +3 -2
  15. package/lib/module/storage/components/StorageEventDetailContent.js +2 -2
  16. package/lib/module/storage/components/StorageKeyCard.js +5 -5
  17. package/lib/module/storage/components/StorageKeyRow.js +2 -2
  18. package/lib/module/storage/components/StorageKeySection.js +2 -2
  19. package/lib/module/storage/hooks/useAsyncStorageKeys.js +13 -2
  20. package/lib/module/storage/hooks/useMMKVKeys.js +21 -3
  21. package/lib/module/storage/stores/storageEventStore.js +81 -0
  22. package/lib/module/storage/sync/storageSyncAdapter.js +44 -0
  23. package/lib/typescript/index.d.ts +1 -0
  24. package/lib/typescript/index.d.ts.map +1 -1
  25. package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
  26. package/lib/typescript/storage/components/GameUIStorageStats.d.ts.map +1 -1
  27. package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +1 -1
  28. package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +1 -1
  29. package/lib/typescript/storage/stores/storageEventStore.d.ts +8 -0
  30. package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -1
  31. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +31 -0
  32. package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -0
  33. package/package.json +5 -5
@@ -153,6 +153,12 @@ Object.defineProperty(exports, "storageEventStore", {
153
153
  return _storageEventStore.storageEventStore;
154
154
  }
155
155
  });
156
+ Object.defineProperty(exports, "storageSyncAdapter", {
157
+ enumerable: true,
158
+ get: function () {
159
+ return _storageSyncAdapter.storageSyncAdapter;
160
+ }
161
+ });
156
162
  Object.defineProperty(exports, "storageToolPreset", {
157
163
  enumerable: true,
158
164
  get: function () {
@@ -234,4 +240,5 @@ var _mmkvAvailability = require("./storage/utils/mmkvAvailability");
234
240
  var _mmkvTypeDetection = require("./storage/utils/mmkvTypeDetection");
235
241
  var _storageTimeTravelUtils = require("./storage/utils/storageTimeTravelUtils");
236
242
  var _MMKVInstanceRegistry = require("./storage/utils/MMKVInstanceRegistry");
243
+ var _storageSyncAdapter = require("./storage/sync/storageSyncAdapter");
237
244
  var _storageEventStore = require("./storage/stores/storageEventStore");
@@ -684,7 +684,7 @@ const styles = _reactNative.StyleSheet.create({
684
684
  paddingBottom: 32
685
685
  },
686
686
  backgroundGrid: {
687
- ..._reactNative.StyleSheet.absoluteFillObject,
687
+ ..._sharedUi.absoluteFill,
688
688
  opacity: 0.006,
689
689
  backgroundColor: _sharedUi.gameUIColors.info
690
690
  },
@@ -517,7 +517,7 @@ const styles = _reactNative.StyleSheet.create({
517
517
  shadowRadius: 8
518
518
  },
519
519
  healthGridOverlay: {
520
- ..._reactNative.StyleSheet.absoluteFillObject,
520
+ ..._sharedUi.absoluteFill,
521
521
  opacity: 0.1,
522
522
  borderWidth: 1,
523
523
  borderColor: "rgba(255, 255, 255, 0.1)",
@@ -540,7 +540,7 @@ const styles = _reactNative.StyleSheet.create({
540
540
  backgroundColor: "rgba(255, 0, 0, 0.02)"
541
541
  },
542
542
  cardGlow: {
543
- ..._reactNative.StyleSheet.absoluteFillObject,
543
+ ..._sharedUi.absoluteFill,
544
544
  opacity: 0.3
545
545
  },
546
546
  cardHeader: {
@@ -241,7 +241,7 @@ function StorageEventDetailContent({
241
241
  children: "CURRENT VALUE"
242
242
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
243
243
  style: styles.valueHeaderBadges,
244
- children: [action && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
244
+ children: [action ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
245
245
  style: [styles.actionBadge, {
246
246
  backgroundColor: `${actionColor}20`
247
247
  }],
@@ -251,7 +251,7 @@ function StorageEventDetailContent({
251
251
  }],
252
252
  children: (0, _storageActionHelpers.translateStorageAction)(action)
253
253
  })
254
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
254
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
255
255
  style: styles.typeBadge,
256
256
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
257
257
  style: styles.typeText,
@@ -150,10 +150,10 @@ function StorageKeyCard({
150
150
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
151
151
  style: styles.storageKeyText,
152
152
  children: storageKey.key
153
- }), storageKey.description && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
153
+ }), storageKey.description ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
154
154
  style: styles.descriptionText,
155
155
  children: storageKey.description
156
- }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
156
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
157
157
  style: styles.cardHeaderMeta,
158
158
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
159
159
  style: [styles.statusBadge, {
@@ -282,13 +282,13 @@ function StorageKeyCard({
282
282
  style: styles.typeHelperText,
283
283
  children: ["Current type: ", (0, _valueType.getValueTypeLabel)(storageKey.value)]
284
284
  })]
285
- }), storageKey.lastUpdated && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
285
+ }), storageKey.lastUpdated ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
286
286
  style: styles.metaInfo,
287
287
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Text, {
288
288
  style: styles.metaLabel,
289
- children: ["Last updated123: ", storageKey.lastUpdated.toLocaleString()]
289
+ children: ["Last updated: ", storageKey.lastUpdated.toLocaleString()]
290
290
  })
291
- })]
291
+ }) : null]
292
292
  }), isExpanded && !hasValue && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
293
293
  style: styles.cardBody,
294
294
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -189,7 +189,7 @@ function StorageKeyRow({
189
189
  style: styles.expandedExpected,
190
190
  children: String(storageKey.expectedValue)
191
191
  })]
192
- }), storageKey.description && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
192
+ }), storageKey.description ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
193
193
  style: styles.expandedRow,
194
194
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
195
195
  style: styles.expandedLabel,
@@ -198,7 +198,7 @@ function StorageKeyRow({
198
198
  style: styles.expandedDescription,
199
199
  children: storageKey.description
200
200
  })]
201
- })]
201
+ }) : null]
202
202
  });
203
203
 
204
204
  // Handle checkbox press in select mode
@@ -60,14 +60,14 @@ function StorageKeySection({
60
60
  if (keys.length === 0) return null;
61
61
  return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
62
62
  style: styles.sectionContainer,
63
- children: [title && /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
63
+ children: [title ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_sharedUi.SectionHeader, {
64
64
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Title, {
65
65
  children: title
66
66
  }), count >= 0 && /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.SectionHeader.Badge, {
67
67
  count: count,
68
68
  color: headerColor
69
69
  })]
70
- }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
70
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
71
71
  style: styles.sectionContent,
72
72
  children: keys.map(storageKey => {
73
73
  // Create unique key by combining storage type, instance ID (if present), and key name
@@ -13,8 +13,18 @@ function useAsyncStorageKeys(requiredStorageKeys = []) {
13
13
  const [isLoading, setIsLoading] = (0, _react.useState)(true);
14
14
  const [error, setError] = (0, _react.useState)(null);
15
15
 
16
+ // Callers routinely pass a fresh array literal (or rely on the `= []`
17
+ // default, which is a new array every render). Depending on its identity
18
+ // would recreate `fetchStorageData` each render, re-fire the effect, and
19
+ // loop AsyncStorage.getAllKeys/multiGet forever. Depend on a stable content
20
+ // signature instead, and read the latest array via a ref inside the fetch.
21
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
22
+ const requiredRef = (0, _react.useRef)(requiredStorageKeys);
23
+ requiredRef.current = requiredStorageKeys;
24
+
16
25
  // Fetch all keys and values from AsyncStorage
17
26
  const fetchStorageData = (0, _react.useCallback)(async () => {
27
+ const requiredStorageKeys = requiredRef.current;
18
28
  setIsLoading(true);
19
29
  setError(null);
20
30
  try {
@@ -111,7 +121,8 @@ function useAsyncStorageKeys(requiredStorageKeys = []) {
111
121
  } finally {
112
122
  setIsLoading(false);
113
123
  }
114
- }, [requiredStorageKeys]);
124
+ // eslint-disable-next-line react-hooks/exhaustive-deps
125
+ }, [requiredSignature]);
115
126
 
116
127
  // Initial fetch
117
128
  (0, _react.useEffect)(() => {
@@ -76,8 +76,16 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
76
76
  const [isLoading, setIsLoading] = (0, _react.useState)(true);
77
77
  const [error, setError] = (0, _react.useState)(null);
78
78
 
79
+ // Depend on a stable content signature rather than the array identity — a
80
+ // fresh `requiredStorageKeys` literal each render would otherwise loop the
81
+ // fetch effect. Read the latest array via ref inside the fetch.
82
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
83
+ const requiredRef = (0, _react.useRef)(requiredStorageKeys);
84
+ requiredRef.current = requiredStorageKeys;
85
+
79
86
  // Fetch all keys and values from MMKV instance
80
87
  const fetchStorageData = (0, _react.useCallback)(() => {
88
+ const requiredStorageKeys = requiredRef.current;
81
89
  setIsLoading(true);
82
90
  setError(null);
83
91
  try {
@@ -209,7 +217,8 @@ function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
209
217
  } finally {
210
218
  setIsLoading(false);
211
219
  }
212
- }, [instance, instanceId, requiredStorageKeys]);
220
+ // eslint-disable-next-line react-hooks/exhaustive-deps
221
+ }, [instance, instanceId, requiredSignature]);
213
222
 
214
223
  // Initial fetch
215
224
  (0, _react.useEffect)(() => {
@@ -255,7 +264,15 @@ function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
255
264
  const [storageKeys, setStorageKeys] = (0, _react.useState)([]);
256
265
  const [isLoading, setIsLoading] = (0, _react.useState)(true);
257
266
  const [error, setError] = (0, _react.useState)(null);
267
+
268
+ // Depend on a stable content signature rather than the array identity — a
269
+ // fresh `requiredStorageKeys` literal each render would otherwise loop the
270
+ // fetch effect. Read the latest array via ref inside the fetch.
271
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
272
+ const requiredRef = (0, _react.useRef)(requiredStorageKeys);
273
+ requiredRef.current = requiredStorageKeys;
258
274
  const fetchStorageData = (0, _react.useCallback)(() => {
275
+ const requiredStorageKeys = requiredRef.current;
259
276
  setIsLoading(true);
260
277
  setError(null);
261
278
  try {
@@ -349,7 +366,8 @@ function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
349
366
  } finally {
350
367
  setIsLoading(false);
351
368
  }
352
- }, [instances, requiredStorageKeys]);
369
+ // eslint-disable-next-line react-hooks/exhaustive-deps
370
+ }, [instances, requiredSignature]);
353
371
  (0, _react.useEffect)(() => {
354
372
  fetchStorageData();
355
373
  }, [fetchStorageData]);
@@ -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,81 @@ 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 AsyncStorage = require("@react-native-async-storage/async-storage").default;
118
+ if (AsyncStorage) {
119
+ const allKeys = await AsyncStorage.getAllKeys();
120
+ if (allKeys.length > 0) {
121
+ const pairs = await AsyncStorage.multiGet(allKeys);
122
+ for (const [key, rawValue] of pairs) {
123
+ this.addEvent({
124
+ action: "setItem",
125
+ timestamp: now,
126
+ data: {
127
+ key,
128
+ value: rawValue ?? undefined
129
+ },
130
+ storageType: "async",
131
+ id: nextStorageEventId()
132
+ });
133
+ }
134
+ }
135
+ }
136
+ } catch {
137
+ // AsyncStorage not available — skip silently
138
+ }
139
+ }
140
+
63
141
  /**
64
142
  * Start capturing storage events from both AsyncStorage and MMKV
65
143
  */
@@ -92,6 +170,9 @@ class StorageEventStore extends _sharedUi.BaseEventStore {
92
170
  this.addEvent(storageEvent);
93
171
  });
94
172
  }
173
+
174
+ // Scan existing keys so getEvents() includes pre-existing state
175
+ await this.scanExistingState();
95
176
  }
96
177
 
97
178
  /**
@@ -0,0 +1,49 @@
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 _storageEventStore = require("../stores/storageEventStore");
9
+ var _clearAllStorage = require("../utils/clearAllStorage");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ /**
12
+ * Sync adapter for the storage tool, consumed by @buoy-gg/external-sync's
13
+ * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
14
+ * this package doesn't need a dependency on it).
15
+ *
16
+ * Subscribing starts the underlying capture lifecycle (including the initial
17
+ * key scan), so storage events are only recorded while a dashboard is
18
+ * watching.
19
+ *
20
+ * The `async.*` actions mirror the AsyncStorage API so the desktop dashboard
21
+ * can proxy its browse/edit mode against the device's real storage.
22
+ */
23
+ const storageSyncAdapter = exports.storageSyncAdapter = {
24
+ version: 1,
25
+ getSnapshot: () => _storageEventStore.storageEventStore.getEvents(),
26
+ subscribe: onChange => _storageEventStore.storageEventStore.subscribeToEvents(onChange),
27
+ actions: {
28
+ clearEvents: () => {
29
+ _storageEventStore.storageEventStore.clearEvents();
30
+ },
31
+ /** Clears all app storage keys, preserving dev tools settings. */
32
+ clearAppStorage: () => (0, _clearAllStorage.clearAllAppStorage)(),
33
+ // ── Remote AsyncStorage proxy (desktop browse/edit mode) ──
34
+ "async.getAllKeys": () => _asyncStorage.default.getAllKeys(),
35
+ "async.multiGet": params => _asyncStorage.default.multiGet(params.keys),
36
+ "async.getItem": params => _asyncStorage.default.getItem(params.key),
37
+ "async.setItem": params => {
38
+ const {
39
+ key,
40
+ value
41
+ } = params;
42
+ return _asyncStorage.default.setItem(key, value);
43
+ },
44
+ "async.removeItem": params => _asyncStorage.default.removeItem(params.key),
45
+ "async.multiRemove": params => _asyncStorage.default.multiRemove(params.keys),
46
+ "async.multiSet": params => _asyncStorage.default.multiSet(params.pairs),
47
+ "async.clear": () => _asyncStorage.default.clear()
48
+ }
49
+ };
@@ -55,6 +55,11 @@ export { undoOperation, jumpToState, canUndo } from "./storage/utils/storageTime
55
55
  // =============================================================================
56
56
  export { registerMMKVInstance, unregisterMMKVInstance } from "./storage/utils/MMKVInstanceRegistry";
57
57
 
58
+ // =============================================================================
59
+ // EXTERNAL SYNC (Adapter for @buoy-gg/external-sync's useExternalSync)
60
+ // =============================================================================
61
+ export { storageSyncAdapter } from "./storage/sync/storageSyncAdapter";
62
+
58
63
  // =============================================================================
59
64
  // INTERNAL EXPORTS (For @buoy-gg/* packages only - not part of public API)
60
65
  // =============================================================================
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useMemo, useCallback, useState, useEffect } from "react";
4
4
  import { StyleSheet, Text, View, TouchableOpacity, ScrollView } from "react-native";
5
+ import { absoluteFill } from "@buoy-gg/shared-ui";
5
6
  import { Database, Zap, ProBadge, ProUpgradeModal } from "@buoy-gg/shared-ui";
6
7
  import { isDevToolsStorageKey } from "@buoy-gg/shared-ui";
7
8
  import { StorageKeySection } from "./StorageKeySection";
@@ -682,7 +683,7 @@ const styles = StyleSheet.create({
682
683
  paddingBottom: 32
683
684
  },
684
685
  backgroundGrid: {
685
- ...StyleSheet.absoluteFillObject,
686
+ ...absoluteFill,
686
687
  opacity: 0.006,
687
688
  backgroundColor: gameUIColors.info
688
689
  },
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useEffect, useRef } from "react";
4
4
  import { StyleSheet, View, Text, Animated } from "react-native";
5
+ import { absoluteFill } from "@buoy-gg/shared-ui";
5
6
  import { Database, Shield, AlertCircle, CheckCircle2, XCircle, Eye, Zap, gameUIColors, macOSColors } from "@buoy-gg/shared-ui";
6
7
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
8
  // Use macOS colors
@@ -513,7 +514,7 @@ const styles = StyleSheet.create({
513
514
  shadowRadius: 8
514
515
  },
515
516
  healthGridOverlay: {
516
- ...StyleSheet.absoluteFillObject,
517
+ ...absoluteFill,
517
518
  opacity: 0.1,
518
519
  borderWidth: 1,
519
520
  borderColor: "rgba(255, 255, 255, 0.1)",
@@ -536,7 +537,7 @@ const styles = StyleSheet.create({
536
537
  backgroundColor: "rgba(255, 0, 0, 0.02)"
537
538
  },
538
539
  cardGlow: {
539
- ...StyleSheet.absoluteFillObject,
540
+ ...absoluteFill,
540
541
  opacity: 0.3
541
542
  },
542
543
  cardHeader: {
@@ -235,7 +235,7 @@ export function StorageEventDetailContent({
235
235
  children: "CURRENT VALUE"
236
236
  }), /*#__PURE__*/_jsxs(View, {
237
237
  style: styles.valueHeaderBadges,
238
- children: [action && /*#__PURE__*/_jsx(View, {
238
+ children: [action ? /*#__PURE__*/_jsx(View, {
239
239
  style: [styles.actionBadge, {
240
240
  backgroundColor: `${actionColor}20`
241
241
  }],
@@ -245,7 +245,7 @@ export function StorageEventDetailContent({
245
245
  }],
246
246
  children: translateStorageAction(action)
247
247
  })
248
- }), /*#__PURE__*/_jsx(View, {
248
+ }) : null, /*#__PURE__*/_jsx(View, {
249
249
  style: styles.typeBadge,
250
250
  children: /*#__PURE__*/_jsx(Text, {
251
251
  style: styles.typeText,
@@ -147,10 +147,10 @@ export function StorageKeyCard({
147
147
  children: [/*#__PURE__*/_jsx(Text, {
148
148
  style: styles.storageKeyText,
149
149
  children: storageKey.key
150
- }), storageKey.description && /*#__PURE__*/_jsx(Text, {
150
+ }), storageKey.description ? /*#__PURE__*/_jsx(Text, {
151
151
  style: styles.descriptionText,
152
152
  children: storageKey.description
153
- }), /*#__PURE__*/_jsxs(View, {
153
+ }) : null, /*#__PURE__*/_jsxs(View, {
154
154
  style: styles.cardHeaderMeta,
155
155
  children: [/*#__PURE__*/_jsx(View, {
156
156
  style: [styles.statusBadge, {
@@ -279,13 +279,13 @@ export function StorageKeyCard({
279
279
  style: styles.typeHelperText,
280
280
  children: ["Current type: ", getValueTypeLabel(storageKey.value)]
281
281
  })]
282
- }), storageKey.lastUpdated && /*#__PURE__*/_jsx(View, {
282
+ }), storageKey.lastUpdated ? /*#__PURE__*/_jsx(View, {
283
283
  style: styles.metaInfo,
284
284
  children: /*#__PURE__*/_jsxs(Text, {
285
285
  style: styles.metaLabel,
286
- children: ["Last updated123: ", storageKey.lastUpdated.toLocaleString()]
286
+ children: ["Last updated: ", storageKey.lastUpdated.toLocaleString()]
287
287
  })
288
- })]
288
+ }) : null]
289
289
  }), isExpanded && !hasValue && /*#__PURE__*/_jsxs(View, {
290
290
  style: styles.cardBody,
291
291
  children: [/*#__PURE__*/_jsxs(View, {
@@ -186,7 +186,7 @@ export function StorageKeyRow({
186
186
  style: styles.expandedExpected,
187
187
  children: String(storageKey.expectedValue)
188
188
  })]
189
- }), storageKey.description && /*#__PURE__*/_jsxs(View, {
189
+ }), storageKey.description ? /*#__PURE__*/_jsxs(View, {
190
190
  style: styles.expandedRow,
191
191
  children: [/*#__PURE__*/_jsx(Text, {
192
192
  style: styles.expandedLabel,
@@ -195,7 +195,7 @@ export function StorageKeyRow({
195
195
  style: styles.expandedDescription,
196
196
  children: storageKey.description
197
197
  })]
198
- })]
198
+ }) : null]
199
199
  });
200
200
 
201
201
  // Handle checkbox press in select mode
@@ -56,14 +56,14 @@ export function StorageKeySection({
56
56
  if (keys.length === 0) return null;
57
57
  return /*#__PURE__*/_jsxs(View, {
58
58
  style: styles.sectionContainer,
59
- children: [title && /*#__PURE__*/_jsxs(SectionHeader, {
59
+ children: [title ? /*#__PURE__*/_jsxs(SectionHeader, {
60
60
  children: [/*#__PURE__*/_jsx(SectionHeader.Title, {
61
61
  children: title
62
62
  }), count >= 0 && /*#__PURE__*/_jsx(SectionHeader.Badge, {
63
63
  count: count,
64
64
  color: headerColor
65
65
  })]
66
- }), /*#__PURE__*/_jsx(View, {
66
+ }) : null, /*#__PURE__*/_jsx(View, {
67
67
  style: styles.sectionContent,
68
68
  children: keys.map(storageKey => {
69
69
  // Create unique key by combining storage type, instance ID (if present), and key name
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- import { useState, useEffect, useCallback } from 'react';
3
+ import { useState, useEffect, useCallback, useRef } from 'react';
4
4
  import AsyncStorage from '@react-native-async-storage/async-storage';
5
5
  export function useAsyncStorageKeys(requiredStorageKeys = []) {
6
6
  // State management
@@ -8,8 +8,18 @@ export function useAsyncStorageKeys(requiredStorageKeys = []) {
8
8
  const [isLoading, setIsLoading] = useState(true);
9
9
  const [error, setError] = useState(null);
10
10
 
11
+ // Callers routinely pass a fresh array literal (or rely on the `= []`
12
+ // default, which is a new array every render). Depending on its identity
13
+ // would recreate `fetchStorageData` each render, re-fire the effect, and
14
+ // loop AsyncStorage.getAllKeys/multiGet forever. Depend on a stable content
15
+ // signature instead, and read the latest array via a ref inside the fetch.
16
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
17
+ const requiredRef = useRef(requiredStorageKeys);
18
+ requiredRef.current = requiredStorageKeys;
19
+
11
20
  // Fetch all keys and values from AsyncStorage
12
21
  const fetchStorageData = useCallback(async () => {
22
+ const requiredStorageKeys = requiredRef.current;
13
23
  setIsLoading(true);
14
24
  setError(null);
15
25
  try {
@@ -106,7 +116,8 @@ export function useAsyncStorageKeys(requiredStorageKeys = []) {
106
116
  } finally {
107
117
  setIsLoading(false);
108
118
  }
109
- }, [requiredStorageKeys]);
119
+ // eslint-disable-next-line react-hooks/exhaustive-deps
120
+ }, [requiredSignature]);
110
121
 
111
122
  // Initial fetch
112
123
  useEffect(() => {
@@ -13,7 +13,7 @@
13
13
  * - No multiGet - must fetch keys individually
14
14
  */
15
15
 
16
- import { useState, useEffect, useCallback } from 'react';
16
+ import { useState, useEffect, useCallback, useRef } from 'react';
17
17
  import { isMMKVAvailable } from '../utils/mmkvAvailability';
18
18
 
19
19
  // Conditionally import MMKV types
@@ -73,8 +73,16 @@ export function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
73
73
  const [isLoading, setIsLoading] = useState(true);
74
74
  const [error, setError] = useState(null);
75
75
 
76
+ // Depend on a stable content signature rather than the array identity — a
77
+ // fresh `requiredStorageKeys` literal each render would otherwise loop the
78
+ // fetch effect. Read the latest array via ref inside the fetch.
79
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
80
+ const requiredRef = useRef(requiredStorageKeys);
81
+ requiredRef.current = requiredStorageKeys;
82
+
76
83
  // Fetch all keys and values from MMKV instance
77
84
  const fetchStorageData = useCallback(() => {
85
+ const requiredStorageKeys = requiredRef.current;
78
86
  setIsLoading(true);
79
87
  setError(null);
80
88
  try {
@@ -206,7 +214,8 @@ export function useMMKVKeys(instance, instanceId, requiredStorageKeys = []) {
206
214
  } finally {
207
215
  setIsLoading(false);
208
216
  }
209
- }, [instance, instanceId, requiredStorageKeys]);
217
+ // eslint-disable-next-line react-hooks/exhaustive-deps
218
+ }, [instance, instanceId, requiredSignature]);
210
219
 
211
220
  // Initial fetch
212
221
  useEffect(() => {
@@ -252,7 +261,15 @@ export function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
252
261
  const [storageKeys, setStorageKeys] = useState([]);
253
262
  const [isLoading, setIsLoading] = useState(true);
254
263
  const [error, setError] = useState(null);
264
+
265
+ // Depend on a stable content signature rather than the array identity — a
266
+ // fresh `requiredStorageKeys` literal each render would otherwise loop the
267
+ // fetch effect. Read the latest array via ref inside the fetch.
268
+ const requiredSignature = JSON.stringify(requiredStorageKeys);
269
+ const requiredRef = useRef(requiredStorageKeys);
270
+ requiredRef.current = requiredStorageKeys;
255
271
  const fetchStorageData = useCallback(() => {
272
+ const requiredStorageKeys = requiredRef.current;
256
273
  setIsLoading(true);
257
274
  setError(null);
258
275
  try {
@@ -346,7 +363,8 @@ export function useMultiMMKVKeys(instances, requiredStorageKeys = []) {
346
363
  } finally {
347
364
  setIsLoading(false);
348
365
  }
349
- }, [instances, requiredStorageKeys]);
366
+ // eslint-disable-next-line react-hooks/exhaustive-deps
367
+ }, [instances, requiredSignature]);
350
368
  useEffect(() => {
351
369
  fetchStorageData();
352
370
  }, [fetchStorageData]);
@@ -25,6 +25,8 @@
25
25
  import { BaseEventStore } from "@buoy-gg/shared-ui";
26
26
  import { startListening as startAsyncStorageListening, addListener as addAsyncStorageListener, isListening as isAsyncStorageListening } from "../utils/AsyncStorageListener";
27
27
  import { addMMKVListener } from "../utils/MMKVListener";
28
+ import { mmkvInstanceRegistry } from "../utils/MMKVInstanceRegistry";
29
+ import { detectMMKVType } from "../utils/mmkvTypeDetection";
28
30
 
29
31
  /**
30
32
  * Unified storage event type combining AsyncStorage and MMKV events.
@@ -50,6 +52,7 @@ class StorageEventStore extends BaseEventStore {
50
52
  // Unsubscribe functions for raw listeners
51
53
  asyncStorageUnsubscribe = null;
52
54
  mmkvUnsubscribe = null;
55
+ hasScannedInitialState = false;
53
56
  constructor() {
54
57
  super({
55
58
  storeName: "storage",
@@ -57,6 +60,81 @@ class StorageEventStore extends BaseEventStore {
57
60
  });
58
61
  }
59
62
 
63
+ /**
64
+ * Scan all registered MMKV instances and AsyncStorage for existing keys,
65
+ * creating synthetic events so that getEvents() includes the current state
66
+ * (not just changes made after capture started). Only runs once per store
67
+ * lifetime to avoid duplicates.
68
+ */
69
+ async scanExistingState() {
70
+ if (this.hasScannedInitialState) return;
71
+ this.hasScannedInitialState = true;
72
+ const now = new Date();
73
+
74
+ // ── MMKV: synchronous scan ──
75
+ const instances = mmkvInstanceRegistry.getAll();
76
+ for (const {
77
+ id: instanceId,
78
+ instance
79
+ } of instances) {
80
+ try {
81
+ const keys = instance.getAllKeys();
82
+ for (const key of keys) {
83
+ const {
84
+ value,
85
+ type: valueType
86
+ } = detectMMKVType(instance, key);
87
+ const actionMap = {
88
+ string: "set.string",
89
+ number: "set.number",
90
+ boolean: "set.boolean",
91
+ buffer: "set.buffer"
92
+ };
93
+ const action = actionMap[valueType] ?? "set.string";
94
+ this.addEvent({
95
+ action,
96
+ timestamp: now,
97
+ instanceId,
98
+ data: {
99
+ key,
100
+ value,
101
+ valueType
102
+ },
103
+ storageType: "mmkv",
104
+ id: nextStorageEventId()
105
+ });
106
+ }
107
+ } catch {
108
+ // Instance may not be accessible — skip silently
109
+ }
110
+ }
111
+
112
+ // ── AsyncStorage: async scan ──
113
+ try {
114
+ const AsyncStorage = require("@react-native-async-storage/async-storage").default;
115
+ if (AsyncStorage) {
116
+ const allKeys = await AsyncStorage.getAllKeys();
117
+ if (allKeys.length > 0) {
118
+ const pairs = await AsyncStorage.multiGet(allKeys);
119
+ for (const [key, rawValue] of pairs) {
120
+ this.addEvent({
121
+ action: "setItem",
122
+ timestamp: now,
123
+ data: {
124
+ key,
125
+ value: rawValue ?? undefined
126
+ },
127
+ storageType: "async",
128
+ id: nextStorageEventId()
129
+ });
130
+ }
131
+ }
132
+ }
133
+ } catch {
134
+ // AsyncStorage not available — skip silently
135
+ }
136
+ }
137
+
60
138
  /**
61
139
  * Start capturing storage events from both AsyncStorage and MMKV
62
140
  */
@@ -89,6 +167,9 @@ class StorageEventStore extends BaseEventStore {
89
167
  this.addEvent(storageEvent);
90
168
  });
91
169
  }
170
+
171
+ // Scan existing keys so getEvents() includes pre-existing state
172
+ await this.scanExistingState();
92
173
  }
93
174
 
94
175
  /**
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ import AsyncStorage from "@react-native-async-storage/async-storage";
4
+ import { storageEventStore } from "../stores/storageEventStore";
5
+ import { clearAllAppStorage } from "../utils/clearAllStorage";
6
+ /**
7
+ * Sync adapter for the storage tool, consumed by @buoy-gg/external-sync's
8
+ * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
9
+ * this package doesn't need a dependency on it).
10
+ *
11
+ * Subscribing starts the underlying capture lifecycle (including the initial
12
+ * key scan), so storage events are only recorded while a dashboard is
13
+ * watching.
14
+ *
15
+ * The `async.*` actions mirror the AsyncStorage API so the desktop dashboard
16
+ * can proxy its browse/edit mode against the device's real storage.
17
+ */
18
+ export const storageSyncAdapter = {
19
+ version: 1,
20
+ getSnapshot: () => storageEventStore.getEvents(),
21
+ subscribe: onChange => storageEventStore.subscribeToEvents(onChange),
22
+ actions: {
23
+ clearEvents: () => {
24
+ storageEventStore.clearEvents();
25
+ },
26
+ /** Clears all app storage keys, preserving dev tools settings. */
27
+ clearAppStorage: () => clearAllAppStorage(),
28
+ // ── Remote AsyncStorage proxy (desktop browse/edit mode) ──
29
+ "async.getAllKeys": () => AsyncStorage.getAllKeys(),
30
+ "async.multiGet": params => AsyncStorage.multiGet(params.keys),
31
+ "async.getItem": params => AsyncStorage.getItem(params.key),
32
+ "async.setItem": params => {
33
+ const {
34
+ key,
35
+ value
36
+ } = params;
37
+ return AsyncStorage.setItem(key, value);
38
+ },
39
+ "async.removeItem": params => AsyncStorage.removeItem(params.key),
40
+ "async.multiRemove": params => AsyncStorage.multiRemove(params.keys),
41
+ "async.multiSet": params => AsyncStorage.multiSet(params.pairs),
42
+ "async.clear": () => AsyncStorage.clear()
43
+ }
44
+ };
@@ -33,6 +33,7 @@ export type { AsyncStorageEvent, AsyncStorageEventListener, } from "./storage/ut
33
33
  export type { MMKVEvent, MMKVEventListener, } from "./storage/utils/MMKVListener";
34
34
  export type { MMKVInstanceInfo } from "./storage/utils/MMKVInstanceRegistry";
35
35
  export { registerMMKVInstance, unregisterMMKVInstance, } from "./storage/utils/MMKVInstanceRegistry";
36
+ export { storageSyncAdapter } from "./storage/sync/storageSyncAdapter";
36
37
  /** @internal */
37
38
  export { storageEventStore } from "./storage/stores/storageEventStore";
38
39
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKhE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAKnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,GAC5B,MAAM,kCAAkC,CAAC;AAK1C,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,oCAAoC,CAAC;AAK5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACL,eAAe,EACf,YAAY,EACZ,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,cAAc,EACd,eAAe,EACf,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,GACR,MAAM,wCAAwC,CAAC;AAChD,YAAY,EACV,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,SAAS,EACT,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAK7E,OAAO,EACL,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,sCAAsC,CAAC;AAK9C,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAKH,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAKhE,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,gBAAgB,EAChB,YAAY,EACZ,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,qBAAqB,EAAE,MAAM,4CAA4C,CAAC;AAKnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,qBAAqB,GACtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,gBAAgB,EAChB,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,GAC5B,MAAM,kCAAkC,CAAC;AAK1C,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,oCAAoC,CAAC;AAK5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAC;AACrE,OAAO,EACL,eAAe,EACf,YAAY,EACZ,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,cAAc,EACd,eAAe,EACf,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,GACR,MAAM,wCAAwC,CAAC;AAChD,YAAY,EACV,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,sCAAsC,CAAC;AAC9C,YAAY,EACV,SAAS,EACT,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAK7E,OAAO,EACL,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,sCAAsC,CAAC;AAK9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAKvE,gBAAgB;AAChB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"GameUIStorageBrowser.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/GameUIStorageBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,gBAAgB,EACjB,MAAM,OAAO,CAAC;AASf,OAAO,EAAkB,kBAAkB,EAAmB,MAAM,UAAU,CAAC;AAgD/E,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,UAAU,yBAAyB;IACjC,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,mBAAmB,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;CACxD;AAED,wBAAgB,oBAAoB,CAAC,EACnC,mBAAwB,EACxB,WAAmB,EACnB,eAA0C,EAC1C,eAAe,EACf,YAAY,EACZ,WAAgB,EAChB,cAAc,EACd,eAAe,EACf,aAAa,EACb,mBAAmB,GACpB,EAAE,yBAAyB,+BAqxB3B"}
1
+ {"version":3,"file":"GameUIStorageBrowser.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/GameUIStorageBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,gBAAgB,EACjB,MAAM,OAAO,CAAC;AAUf,OAAO,EAAkB,kBAAkB,EAAmB,MAAM,UAAU,CAAC;AAgD/E,uCAAuC;AACvC,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,UAAU,yBAAyB;IACjC,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,gBAAgB,CAAC,GAAG,EAAE,CAAC,CAAC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,mBAAmB,CAAC,EAAE,GAAG,CAAC,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC,CAAC;CACxD;AAED,wBAAgB,oBAAoB,CAAC,EACnC,mBAAwB,EACxB,WAAmB,EACnB,eAA0C,EAC1C,eAAe,EACf,YAAY,EACZ,WAAgB,EAChB,cAAc,EACd,eAAe,EACf,aAAa,EACb,mBAAmB,GACpB,EAAE,yBAAyB,+BAqxB3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"GameUIStorageStats.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/GameUIStorageStats.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,UAAU,uBAAuB;IAC/B,KAAK,EAAE,eAAe,CAAC;CACxB;AAgFD,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE,uBAAuB,+BAwVpE"}
1
+ {"version":3,"file":"GameUIStorageStats.d.ts","sourceRoot":"","sources":["../../../../src/storage/components/GameUIStorageStats.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,UAAU,uBAAuB;IAC/B,KAAK,EAAE,eAAe,CAAC;CACxB;AAgFD,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE,uBAAuB,+BAwVpE"}
@@ -1 +1 @@
1
- {"version":3,"file":"useAsyncStorageKeys.d.ts","sourceRoot":"","sources":["../../../../src/storage/hooks/useAsyncStorageKeys.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9D,UAAU,yBAAyB;IACjC,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,mBAAmB,CACjC,mBAAmB,GAAE,kBAAkB,EAAO,GAC7C,yBAAyB,CAgJ3B"}
1
+ {"version":3,"file":"useAsyncStorageKeys.d.ts","sourceRoot":"","sources":["../../../../src/storage/hooks/useAsyncStorageKeys.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE9D,UAAU,yBAAyB;IACjC,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,mBAAmB,CACjC,mBAAmB,GAAE,kBAAkB,EAAO,GAC7C,yBAAyB,CA2J3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"useMMKVKeys.d.ts","sourceRoot":"","sources":["../../../../src/storage/hooks/useMMKVKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAI9D,KAAK,IAAI,GAAG,GAAG,CAAC;AAYhB,UAAU,iBAAiB;IACzB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,EACjC,UAAU,EAAE,MAAM,EAClB,mBAAmB,GAAE,kBAAkB,EAAO,GAC7C,iBAAiB,CA0LnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,EAChD,mBAAmB,GAAE,kBAAkB,EAAO,GAC7C,iBAAiB,CAyInB"}
1
+ {"version":3,"file":"useMMKVKeys.d.ts","sourceRoot":"","sources":["../../../../src/storage/hooks/useMMKVKeys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAI9D,KAAK,IAAI,GAAG,GAAG,CAAC;AAYhB,UAAU,iBAAiB;IACzB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,SAAS,EACjC,UAAU,EAAE,MAAM,EAClB,mBAAmB,GAAE,kBAAkB,EAAO,GAC7C,iBAAiB,CAmMnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,EAChD,mBAAmB,GAAE,kBAAkB,EAAO,GAC7C,iBAAiB,CAkJnB"}
@@ -46,7 +46,15 @@ export type StorageEventCallback = (event: StorageEvent) => void;
46
46
  declare class StorageEventStore extends BaseEventStore<StorageEvent> {
47
47
  private asyncStorageUnsubscribe;
48
48
  private mmkvUnsubscribe;
49
+ private hasScannedInitialState;
49
50
  constructor();
51
+ /**
52
+ * Scan all registered MMKV instances and AsyncStorage for existing keys,
53
+ * creating synthetic events so that getEvents() includes the current state
54
+ * (not just changes made after capture started). Only runs once per store
55
+ * lifetime to avoid duplicates.
56
+ */
57
+ private scanExistingState;
50
58
  /**
51
59
  * Start capturing storage events from both AsyncStorage and MMKV
52
60
  */
@@ -1 +1 @@
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;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB,CAAC,iBAAiB,GAAG;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GAC1D,CAAC,SAAS,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAOtD;;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;IAiC/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"}
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;AAI/B;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GACpB,CAAC,iBAAiB,GAAG;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,GAC1D,CAAC,SAAS,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAOtD;;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;IACpD,OAAO,CAAC,sBAAsB,CAAS;;IASvC;;;;;OAKG;YACW,iBAAiB;IA4D/B;;OAEG;cACa,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAoC/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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Sync adapter for the storage tool, consumed by @buoy-gg/external-sync's
3
+ * `useExternalSync` (structurally matches its ToolSyncAdapter interface so
4
+ * this package doesn't need a dependency on it).
5
+ *
6
+ * Subscribing starts the underlying capture lifecycle (including the initial
7
+ * key scan), so storage events are only recorded while a dashboard is
8
+ * watching.
9
+ *
10
+ * The `async.*` actions mirror the AsyncStorage API so the desktop dashboard
11
+ * can proxy its browse/edit mode against the device's real storage.
12
+ */
13
+ export declare const storageSyncAdapter: {
14
+ version: number;
15
+ getSnapshot: () => import("..").StorageEvent[];
16
+ subscribe: (onChange: () => void) => () => void;
17
+ actions: {
18
+ clearEvents: () => void;
19
+ /** Clears all app storage keys, preserving dev tools settings. */
20
+ clearAppStorage: () => Promise<void>;
21
+ "async.getAllKeys": () => Promise<readonly string[]>;
22
+ "async.multiGet": (params: unknown) => Promise<readonly import("@react-native-async-storage/async-storage/lib/typescript/types").KeyValuePair[]>;
23
+ "async.getItem": (params: unknown) => Promise<string | null>;
24
+ "async.setItem": (params: unknown) => Promise<void>;
25
+ "async.removeItem": (params: unknown) => Promise<void>;
26
+ "async.multiRemove": (params: unknown) => Promise<void>;
27
+ "async.multiSet": (params: unknown) => Promise<void>;
28
+ "async.clear": () => Promise<void>;
29
+ };
30
+ };
31
+ //# sourceMappingURL=storageSyncAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storageSyncAdapter.d.ts","sourceRoot":"","sources":["../../../../src/storage/sync/storageSyncAdapter.ts"],"names":[],"mappings":"AAkBA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB;;;0BAGP,MAAM,IAAI;;;QAM9B,kEAAkE;;;mCAKvC,OAAO;kCAER,OAAO;kCAEP,OAAO;qCAIJ,OAAO;sCAEN,OAAO;mCAEV,OAAO;;;CAIrC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/storage",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "storage package",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -26,11 +26,11 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "dependencies": {
29
- "@buoy-gg/floating-tools-core": "3.0.1",
30
- "@buoy-gg/shared-ui": "3.0.1"
29
+ "@buoy-gg/shared-ui": "3.0.2",
30
+ "@buoy-gg/floating-tools-core": "3.0.2"
31
31
  },
32
32
  "peerDependencies": {
33
- "@buoy-gg/license": "3.0.1",
33
+ "@buoy-gg/license": "3.0.2",
34
34
  "@react-native-async-storage/async-storage": ">=1.0.0",
35
35
  "react": "*",
36
36
  "react-native": "*"
@@ -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": "3.0.1"
43
+ "@buoy-gg/license": "3.0.2"
44
44
  },
45
45
  "react-native-builder-bob": {
46
46
  "source": "src",