@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.
- package/lib/commonjs/index.js +7 -0
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +1 -1
- package/lib/commonjs/storage/components/GameUIStorageStats.js +2 -2
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +2 -2
- package/lib/commonjs/storage/components/StorageKeyCard.js +5 -5
- package/lib/commonjs/storage/components/StorageKeyRow.js +2 -2
- package/lib/commonjs/storage/components/StorageKeySection.js +2 -2
- package/lib/commonjs/storage/hooks/useAsyncStorageKeys.js +12 -1
- package/lib/commonjs/storage/hooks/useMMKVKeys.js +20 -2
- package/lib/commonjs/storage/stores/storageEventStore.js +81 -0
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +49 -0
- package/lib/module/index.js +5 -0
- package/lib/module/storage/components/GameUIStorageBrowser.js +2 -1
- package/lib/module/storage/components/GameUIStorageStats.js +3 -2
- package/lib/module/storage/components/StorageEventDetailContent.js +2 -2
- package/lib/module/storage/components/StorageKeyCard.js +5 -5
- package/lib/module/storage/components/StorageKeyRow.js +2 -2
- package/lib/module/storage/components/StorageKeySection.js +2 -2
- package/lib/module/storage/hooks/useAsyncStorageKeys.js +13 -2
- package/lib/module/storage/hooks/useMMKVKeys.js +21 -3
- package/lib/module/storage/stores/storageEventStore.js +81 -0
- package/lib/module/storage/sync/storageSyncAdapter.js +44 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageStats.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useAsyncStorageKeys.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useMMKVKeys.d.ts.map +1 -1
- package/lib/typescript/storage/stores/storageEventStore.d.ts +8 -0
- package/lib/typescript/storage/stores/storageEventStore.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +31 -0
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -0
- package/package.json +5 -5
package/lib/commonjs/index.js
CHANGED
|
@@ -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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/lib/module/index.js
CHANGED
|
@@ -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
|
-
...
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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":"
|
|
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,
|
|
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,
|
|
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;
|
|
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.
|
|
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/
|
|
30
|
-
"@buoy-gg/
|
|
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.
|
|
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.
|
|
43
|
+
"@buoy-gg/license": "3.0.2"
|
|
44
44
|
},
|
|
45
45
|
"react-native-builder-bob": {
|
|
46
46
|
"source": "src",
|