@buoy-gg/storage 7.0.3 → 7.0.5
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 +77 -1
- package/lib/commonjs/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
- package/lib/commonjs/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
- package/lib/commonjs/storage/components/GameUIStorageBrowser.js +263 -115
- package/lib/commonjs/storage/components/GameUIStorageStats.js +7 -7
- package/lib/commonjs/storage/components/MMKVInstanceInfoPanel.js +1 -1
- package/lib/commonjs/storage/components/MMKVInstanceSelector.js +1 -1
- package/lib/commonjs/storage/components/SelectionActionBar.js +2 -36
- package/lib/commonjs/storage/components/StorageActions.js +6 -6
- package/lib/commonjs/storage/components/StorageBrowserMode.js +16 -2
- package/lib/commonjs/storage/components/StorageEventDetailContent.js +106 -149
- package/lib/commonjs/storage/components/StorageEventListener.js +13 -13
- package/lib/commonjs/storage/components/StorageFilterCards.js +13 -135
- package/lib/commonjs/storage/components/StorageKeyCard.js +1 -1
- package/lib/commonjs/storage/components/StorageKeyEditorScreen.js +392 -0
- package/lib/commonjs/storage/components/StorageKeyRow.js +53 -15
- package/lib/commonjs/storage/components/StorageKeySection.js +10 -4
- package/lib/commonjs/storage/components/StorageKeyStats.js +10 -10
- package/lib/commonjs/storage/components/StorageModalWithTabs.js +272 -129
- package/lib/commonjs/storage/components/StorageScalarKeyEditor.js +46 -0
- package/lib/commonjs/storage/components/StorageTypeFilterBar.js +141 -0
- package/lib/commonjs/storage/components/StorageValueEditModal.js +361 -0
- package/lib/commonjs/storage/hooks/useKeyEditor.js +104 -0
- package/lib/commonjs/storage/hooks/useMeasuredWidth.js +74 -0
- package/lib/commonjs/storage/hooks/useSecureStoreKeys.js +70 -15
- package/lib/commonjs/storage/hooks/useTreeDraft.js +107 -0
- package/lib/commonjs/storage/index.js +29 -0
- package/lib/commonjs/storage/stores/storageEventStore.js +4 -2
- package/lib/commonjs/storage/sync/storageSyncAdapter.js +125 -3
- package/lib/commonjs/storage/utils/AsyncStorageListener.js +76 -12
- package/lib/commonjs/storage/utils/MMKVInstanceRegistry.js +10 -9
- package/lib/commonjs/storage/utils/MMKVListener.js +40 -4
- package/lib/commonjs/storage/utils/SecureStoreRegistry.js +52 -1
- package/lib/commonjs/storage/utils/deleteStorageKeys.js +53 -0
- package/lib/commonjs/storage/utils/index.js +13 -0
- package/lib/commonjs/storage/utils/lineDiff.js +21 -354
- package/lib/commonjs/storage/utils/setStorageValue.js +218 -0
- package/lib/commonjs/storage/utils/storageBrowserRefresh.js +49 -0
- package/lib/commonjs/storage/utils/storageOps.js +53 -0
- package/lib/commonjs/storage/utils/storageTimeTravelUtils.js +60 -38
- package/lib/commonjs/storage/utils/valueType.js +24 -0
- package/lib/module/index.js +18 -1
- package/lib/module/storage/components/DiffViewer/TreeDiffViewer.js +10 -3
- package/lib/module/storage/components/DiffViewer/modes/ThemedSplitView.js +26 -8
- package/lib/module/storage/components/GameUIStorageBrowser.js +266 -118
- package/lib/module/storage/components/GameUIStorageStats.js +7 -7
- package/lib/module/storage/components/MMKVInstanceInfoPanel.js +1 -1
- package/lib/module/storage/components/MMKVInstanceSelector.js +1 -1
- package/lib/module/storage/components/SelectionActionBar.js +2 -36
- package/lib/module/storage/components/StorageActions.js +7 -7
- package/lib/module/storage/components/StorageBrowserMode.js +16 -2
- package/lib/module/storage/components/StorageEventDetailContent.js +110 -151
- package/lib/module/storage/components/StorageEventListener.js +14 -14
- package/lib/module/storage/components/StorageFilterCards.js +13 -135
- package/lib/module/storage/components/StorageKeyCard.js +1 -1
- package/lib/module/storage/components/StorageKeyEditorScreen.js +388 -0
- package/lib/module/storage/components/StorageKeyRow.js +55 -17
- package/lib/module/storage/components/StorageKeySection.js +11 -5
- package/lib/module/storage/components/StorageKeyStats.js +11 -10
- package/lib/module/storage/components/StorageModalWithTabs.js +272 -130
- package/lib/module/storage/components/StorageScalarKeyEditor.js +42 -0
- package/lib/module/storage/components/StorageTypeFilterBar.js +136 -0
- package/lib/module/storage/components/StorageValueEditModal.js +357 -0
- package/lib/module/storage/hooks/useKeyEditor.js +100 -0
- package/lib/module/storage/hooks/useMeasuredWidth.js +71 -0
- package/lib/module/storage/hooks/useSecureStoreKeys.js +71 -16
- package/lib/module/storage/hooks/useTreeDraft.js +103 -0
- package/lib/module/storage/index.js +3 -0
- package/lib/module/storage/stores/storageEventStore.js +4 -2
- package/lib/module/storage/sync/storageSyncAdapter.js +125 -3
- package/lib/module/storage/utils/AsyncStorageListener.js +76 -12
- package/lib/module/storage/utils/MMKVInstanceRegistry.js +8 -7
- package/lib/module/storage/utils/MMKVListener.js +40 -4
- package/lib/module/storage/utils/SecureStoreRegistry.js +52 -1
- package/lib/module/storage/utils/deleteStorageKeys.js +49 -0
- package/lib/module/storage/utils/index.js +3 -0
- package/lib/module/storage/utils/lineDiff.js +7 -354
- package/lib/module/storage/utils/setStorageValue.js +209 -0
- package/lib/module/storage/utils/storageBrowserRefresh.js +44 -0
- package/lib/module/storage/utils/storageOps.js +46 -0
- package/lib/module/storage/utils/storageTimeTravelUtils.js +61 -40
- package/lib/module/storage/utils/valueType.js +23 -0
- package/lib/typescript/index.d.ts +12 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts +4 -1
- package/lib/typescript/storage/components/DiffViewer/TreeDiffViewer.d.ts.map +1 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts +7 -1
- package/lib/typescript/storage/components/DiffViewer/modes/ThemedSplitView.d.ts.map +1 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts +27 -1
- package/lib/typescript/storage/components/GameUIStorageBrowser.d.ts.map +1 -1
- package/lib/typescript/storage/components/SelectionActionBar.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts +17 -1
- package/lib/typescript/storage/components/StorageBrowserMode.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts +4 -1
- package/lib/typescript/storage/components/StorageEventDetailContent.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageFilterCards.d.ts +9 -3
- package/lib/typescript/storage/components/StorageFilterCards.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts +42 -0
- package/lib/typescript/storage/components/StorageKeyEditorScreen.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts +17 -0
- package/lib/typescript/storage/components/StorageKeyRow.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts +7 -1
- package/lib/typescript/storage/components/StorageKeySection.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageKeyStats.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts +28 -1
- package/lib/typescript/storage/components/StorageModalWithTabs.d.ts.map +1 -1
- package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts +24 -0
- package/lib/typescript/storage/components/StorageScalarKeyEditor.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts +29 -0
- package/lib/typescript/storage/components/StorageTypeFilterBar.d.ts.map +1 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts +36 -0
- package/lib/typescript/storage/components/StorageValueEditModal.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts +51 -0
- package/lib/typescript/storage/hooks/useKeyEditor.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts +37 -0
- package/lib/typescript/storage/hooks/useMeasuredWidth.d.ts.map +1 -0
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts +8 -1
- package/lib/typescript/storage/hooks/useSecureStoreKeys.d.ts.map +1 -1
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts +67 -0
- package/lib/typescript/storage/hooks/useTreeDraft.d.ts.map +1 -0
- package/lib/typescript/storage/index.d.ts +2 -0
- package/lib/typescript/storage/index.d.ts.map +1 -1
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts +41 -0
- package/lib/typescript/storage/sync/storageSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts +15 -0
- package/lib/typescript/storage/utils/AsyncStorageListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/MMKVInstanceRegistry.d.ts.map +1 -1
- package/lib/typescript/storage/utils/MMKVListener.d.ts +7 -0
- package/lib/typescript/storage/utils/MMKVListener.d.ts.map +1 -1
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts +20 -0
- package/lib/typescript/storage/utils/SecureStoreRegistry.d.ts.map +1 -1
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts +27 -0
- package/lib/typescript/storage/utils/deleteStorageKeys.d.ts.map +1 -0
- package/lib/typescript/storage/utils/index.d.ts +1 -0
- package/lib/typescript/storage/utils/index.d.ts.map +1 -1
- package/lib/typescript/storage/utils/lineDiff.d.ts +7 -31
- package/lib/typescript/storage/utils/lineDiff.d.ts.map +1 -1
- package/lib/typescript/storage/utils/setStorageValue.d.ts +64 -0
- package/lib/typescript/storage/utils/setStorageValue.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts +28 -0
- package/lib/typescript/storage/utils/storageBrowserRefresh.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageOps.d.ts +25 -0
- package/lib/typescript/storage/utils/storageOps.d.ts.map +1 -0
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts +15 -5
- package/lib/typescript/storage/utils/storageTimeTravelUtils.d.ts.map +1 -1
- package/lib/typescript/storage/utils/valueType.d.ts +13 -0
- package/lib/typescript/storage/utils/valueType.d.ts.map +1 -1
- package/package.json +5 -5
|
@@ -126,6 +126,20 @@ class MMKVListener {
|
|
|
126
126
|
// The native listener (addOnValueChangedListener) is sufficient for monitoring writes
|
|
127
127
|
|
|
128
128
|
let nativeListenerUnsubscribe;
|
|
129
|
+
|
|
130
|
+
// Seed the shadow copy so the FIRST observed write already knows what it
|
|
131
|
+
// replaced. Without this, every key's first event would claim the key never
|
|
132
|
+
// existed even when it was carried over from a previous launch.
|
|
133
|
+
const lastKnownValues = new Map();
|
|
134
|
+
try {
|
|
135
|
+
for (const key of instance.getAllKeys() ?? []) {
|
|
136
|
+
if (this.shouldIgnoreKey(key)) continue;
|
|
137
|
+
lastKnownValues.set(key, detectMMKVType(instance, key).value);
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// Key scan unavailable — prevValue is simply absent until each key is
|
|
141
|
+
// written once, which is strictly better than reporting a wrong value.
|
|
142
|
+
}
|
|
129
143
|
if (typeof instance.addOnValueChangedListener === "function") {
|
|
130
144
|
try {
|
|
131
145
|
nativeListenerUnsubscribe = instance.addOnValueChangedListener(key => {
|
|
@@ -138,9 +152,15 @@ class MMKVListener {
|
|
|
138
152
|
type
|
|
139
153
|
} = detectMMKVType(instance, key);
|
|
140
154
|
|
|
155
|
+
// Read the shadow copy BEFORE overwriting it — this is the value the
|
|
156
|
+
// write replaced. `undefined` means the key did not exist.
|
|
157
|
+
const prevValue = lastKnownValues.get(key);
|
|
158
|
+
|
|
141
159
|
// Determine action: if value is undefined/unknown, it was deleted
|
|
142
160
|
const isDelete = type === "unknown" || value === undefined;
|
|
143
161
|
if (isDelete) {
|
|
162
|
+
lastKnownValues.delete(key);
|
|
163
|
+
|
|
144
164
|
// Key was deleted
|
|
145
165
|
const event = {
|
|
146
166
|
action: "delete",
|
|
@@ -148,11 +168,14 @@ class MMKVListener {
|
|
|
148
168
|
instanceId,
|
|
149
169
|
data: {
|
|
150
170
|
key,
|
|
171
|
+
prevValue,
|
|
151
172
|
success: true
|
|
152
173
|
}
|
|
153
174
|
};
|
|
154
175
|
this.emit(event);
|
|
155
176
|
} else {
|
|
177
|
+
lastKnownValues.set(key, value);
|
|
178
|
+
|
|
156
179
|
// Key was set/updated
|
|
157
180
|
const action = type === "string" ? "set.string" : type === "number" ? "set.number" : type === "boolean" ? "set.boolean" : type === "buffer" ? "set.buffer" : "set.string"; // fallback
|
|
158
181
|
|
|
@@ -163,6 +186,7 @@ class MMKVListener {
|
|
|
163
186
|
data: {
|
|
164
187
|
key,
|
|
165
188
|
value,
|
|
189
|
+
prevValue,
|
|
166
190
|
valueType: type,
|
|
167
191
|
success: true
|
|
168
192
|
}
|
|
@@ -181,7 +205,8 @@ class MMKVListener {
|
|
|
181
205
|
instanceId,
|
|
182
206
|
originalMethods: {},
|
|
183
207
|
// Not used in v4
|
|
184
|
-
nativeListenerUnsubscribe
|
|
208
|
+
nativeListenerUnsubscribe,
|
|
209
|
+
lastKnownValues
|
|
185
210
|
});
|
|
186
211
|
}
|
|
187
212
|
|
|
@@ -201,9 +226,20 @@ class MMKVListener {
|
|
|
201
226
|
nativeListenerUnsubscribe
|
|
202
227
|
} = tracked;
|
|
203
228
|
|
|
204
|
-
// Remove native listener
|
|
205
|
-
|
|
206
|
-
|
|
229
|
+
// Remove native listener. The return of addOnValueChangedListener varies by
|
|
230
|
+
// implementation: a bare unsubscribe function, or a Listener object with a
|
|
231
|
+
// `.remove()` method (react-native-mmkv v4 and the desktop RemoteMMKV
|
|
232
|
+
// back-end). Handle both, and never let teardown throw — this runs during
|
|
233
|
+
// React unmount, where an exception escapes every error boundary and blanks
|
|
234
|
+
// the whole app.
|
|
235
|
+
try {
|
|
236
|
+
if (typeof nativeListenerUnsubscribe === "function") {
|
|
237
|
+
nativeListenerUnsubscribe();
|
|
238
|
+
} else if (typeof nativeListenerUnsubscribe?.remove === "function") {
|
|
239
|
+
nativeListenerUnsubscribe.remove();
|
|
240
|
+
}
|
|
241
|
+
} catch {
|
|
242
|
+
/* listener already gone / back-end offline — nothing to clean up */
|
|
207
243
|
}
|
|
208
244
|
|
|
209
245
|
// Remove from tracking
|
|
@@ -49,12 +49,14 @@
|
|
|
49
49
|
class SecureStoreRegistry {
|
|
50
50
|
module = null;
|
|
51
51
|
keys = new Map();
|
|
52
|
+
listeners = new Set();
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
55
|
* Register the SecureStore module and the keys to expose. Callable multiple
|
|
55
56
|
* times — keys accumulate, the latest module wins.
|
|
56
57
|
*/
|
|
57
58
|
register(module, descriptors) {
|
|
59
|
+
const before = this.signature();
|
|
58
60
|
this.module = module;
|
|
59
61
|
for (const d of descriptors) {
|
|
60
62
|
const entry = typeof d === "string" ? {
|
|
@@ -71,11 +73,41 @@ class SecureStoreRegistry {
|
|
|
71
73
|
};
|
|
72
74
|
this.keys.set(entry.key, entry);
|
|
73
75
|
}
|
|
76
|
+
// Only announce a real change. The desktop dashboard re-registers the same
|
|
77
|
+
// key set on every device snapshot, and subscribers re-read the keychain on
|
|
78
|
+
// notify — signature-comparing keeps that from turning into a read storm.
|
|
79
|
+
if (this.signature() !== before) this.notify();
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
/** Remove a key from the registry (does not delete the stored value). */
|
|
77
83
|
unregister(key) {
|
|
78
|
-
this.keys.delete(key);
|
|
84
|
+
if (this.keys.delete(key)) this.notify();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Subscribe to registry changes (keys registered/unregistered). Lets the
|
|
89
|
+
* browser pick up keys that register after the tool is already open.
|
|
90
|
+
* Returns an unsubscribe function.
|
|
91
|
+
*/
|
|
92
|
+
subscribe(listener) {
|
|
93
|
+
this.listeners.add(listener);
|
|
94
|
+
return () => {
|
|
95
|
+
this.listeners.delete(listener);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Content signature of the registered entries (module identity excluded). */
|
|
100
|
+
signature() {
|
|
101
|
+
return JSON.stringify(Array.from(this.keys.values()));
|
|
102
|
+
}
|
|
103
|
+
notify() {
|
|
104
|
+
this.listeners.forEach(listener => {
|
|
105
|
+
try {
|
|
106
|
+
listener();
|
|
107
|
+
} catch {
|
|
108
|
+
// Listener errors are not ours
|
|
109
|
+
}
|
|
110
|
+
});
|
|
79
111
|
}
|
|
80
112
|
|
|
81
113
|
/** The registered SecureStore module, or null if none registered yet. */
|
|
@@ -102,6 +134,25 @@ class SecureStoreRegistry {
|
|
|
102
134
|
if (!this.module || !entry) return;
|
|
103
135
|
await this.module.deleteItemAsync(key, entry.options);
|
|
104
136
|
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Write a stored value via the registered module, using the options the key
|
|
140
|
+
* was registered with — writing with different `keychainService` options than
|
|
141
|
+
* the value was created with silently creates a SECOND entry the app can't
|
|
142
|
+
* read, so the registered options are the only safe ones to use.
|
|
143
|
+
*
|
|
144
|
+
* Throws rather than no-ops: unlike a delete, a write that quietly did
|
|
145
|
+
* nothing would leave the UI showing a value that was never stored.
|
|
146
|
+
*/
|
|
147
|
+
async setValue(key, value) {
|
|
148
|
+
const entry = this.keys.get(key);
|
|
149
|
+
if (!this.module) throw new Error("No SecureStore module is registered");
|
|
150
|
+
if (!entry) throw new Error(`SecureStore key "${key}" is not registered`);
|
|
151
|
+
if (entry.options.requireAuthentication) {
|
|
152
|
+
throw new Error(`SecureStore key "${key}" is biometric-protected — DevTools never writes it`);
|
|
153
|
+
}
|
|
154
|
+
await this.module.setItemAsync(key, value, entry.options);
|
|
155
|
+
}
|
|
105
156
|
}
|
|
106
157
|
|
|
107
158
|
/**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Deleting storage keys, in one place.
|
|
5
|
+
*
|
|
6
|
+
* Each backend removes keys differently — AsyncStorage takes a batch,
|
|
7
|
+
* SecureStore has to go through the registry (only it knows each key's
|
|
8
|
+
* `keychainService` options), and MMKV deletes per registered instance. Two call
|
|
9
|
+
* sites need this (the selection action bar and a single row's "Delete key"),
|
|
10
|
+
* and a second copy of that fan-out is exactly how one backend quietly stops
|
|
11
|
+
* being cleaned up.
|
|
12
|
+
*
|
|
13
|
+
* Pro gating and confirmation are deliberately NOT here: they belong to the UI
|
|
14
|
+
* that has the user's attention. This function assumes the caller already asked.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { removeMany } from "./asyncStorageCompat";
|
|
18
|
+
import { secureStoreRegistry } from "./SecureStoreRegistry";
|
|
19
|
+
/**
|
|
20
|
+
* Removes every key from whichever backend holds it. Rejects on the first
|
|
21
|
+
* failure — callers surface that, rather than reporting a partial delete as
|
|
22
|
+
* success.
|
|
23
|
+
*/
|
|
24
|
+
export async function deleteStorageKeys(keys, mmkvInstances) {
|
|
25
|
+
if (keys.length === 0) return;
|
|
26
|
+
const asyncKeys = keys.filter(k => k.storageType === "async");
|
|
27
|
+
const mmkvKeys = keys.filter(k => k.storageType === "mmkv");
|
|
28
|
+
const secureKeys = keys.filter(k => k.storageType === "secure");
|
|
29
|
+
if (asyncKeys.length > 0) {
|
|
30
|
+
await removeMany(asyncKeys.map(k => k.key));
|
|
31
|
+
}
|
|
32
|
+
for (const k of secureKeys) {
|
|
33
|
+
await secureStoreRegistry.deleteValue(k.key);
|
|
34
|
+
}
|
|
35
|
+
if (mmkvKeys.length > 0) {
|
|
36
|
+
const byInstance = {};
|
|
37
|
+
mmkvKeys.forEach(k => {
|
|
38
|
+
const instanceId = k.instanceId || "default";
|
|
39
|
+
if (!byInstance[instanceId]) byInstance[instanceId] = [];
|
|
40
|
+
byInstance[instanceId].push(k.key);
|
|
41
|
+
});
|
|
42
|
+
Object.entries(byInstance).forEach(([instanceId, instanceKeys]) => {
|
|
43
|
+
const inst = mmkvInstances.find(i => i.id === instanceId);
|
|
44
|
+
if (inst) {
|
|
45
|
+
instanceKeys.forEach(key => inst.instance.delete(key));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -21,5 +21,8 @@ export { registerMMKVInstance, unregisterMMKVInstance, mmkvInstanceRegistry } fr
|
|
|
21
21
|
// MMKV Listener
|
|
22
22
|
export { addMMKVInstance, removeMMKVInstance, removeAllMMKVInstances, addMMKVListener, removeAllMMKVListeners, isMMKVInstanceMonitored, getMonitoredMMKVInstances, getMMKVInstanceCount, getMMKVListenerCount, isMMKVListening, mmkvListener } from "./MMKVListener";
|
|
23
23
|
|
|
24
|
+
// Storage browser refresh signal (remote-mirror mode)
|
|
25
|
+
export { onStorageBrowserRefresh, requestStorageBrowserRefresh } from "./storageBrowserRefresh";
|
|
26
|
+
|
|
24
27
|
// MMKV Type Detection
|
|
25
28
|
export { detectMMKVType, formatMMKVValue, isTypeMatch } from "./mmkvTypeDetection";
|
|
@@ -1,359 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Line-by-line diff computation
|
|
5
|
-
*
|
|
4
|
+
* Line-by-line diff computation.
|
|
5
|
+
*
|
|
6
|
+
* This used to be a verbatim copy of the shared implementation, which meant a
|
|
7
|
+
* diff fix had to be made twice. It now re-exports the canonical one so the
|
|
8
|
+
* storage split view, the tree view, and every other tool's diff (redux, jotai,
|
|
9
|
+
* highlight-updates) share a single algorithm.
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
|
-
export
|
|
9
|
-
DiffType["DEFAULT"] = "unchanged";
|
|
10
|
-
DiffType["ADDED"] = "added";
|
|
11
|
-
DiffType["REMOVED"] = "removed";
|
|
12
|
-
DiffType["MODIFIED"] = "modified";
|
|
13
|
-
return DiffType;
|
|
14
|
-
}({});
|
|
15
|
-
/**
|
|
16
|
-
* Compute diff based on method - for word-level diff within lines
|
|
17
|
-
*/
|
|
18
|
-
function computeDiffByMethod(oldStr, newStr, method) {
|
|
19
|
-
let oldParts;
|
|
20
|
-
let newParts;
|
|
21
|
-
switch (method) {
|
|
22
|
-
case "chars":
|
|
23
|
-
// Split into individual characters
|
|
24
|
-
oldParts = oldStr.split("");
|
|
25
|
-
newParts = newStr.split("");
|
|
26
|
-
break;
|
|
27
|
-
case "words":
|
|
28
|
-
// Split by word boundaries, keeping whitespace
|
|
29
|
-
oldParts = oldStr.match(/\S+|\s+/g) || [];
|
|
30
|
-
newParts = newStr.match(/\S+|\s+/g) || [];
|
|
31
|
-
break;
|
|
32
|
-
case "trimmedLines":
|
|
33
|
-
{
|
|
34
|
-
// For trimmedLines, compare without leading/trailing whitespace
|
|
35
|
-
const oldTrimmed = oldStr.trim();
|
|
36
|
-
const newTrimmed = newStr.trim();
|
|
37
|
-
// But still do word-level diff on the trimmed content
|
|
38
|
-
oldParts = oldTrimmed.match(/\S+|\s+/g) || [];
|
|
39
|
-
newParts = newTrimmed.match(/\S+|\s+/g) || [];
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
case "lines":
|
|
43
|
-
default:
|
|
44
|
-
// For lines mode, don't do word diff - just show the whole line
|
|
45
|
-
return {
|
|
46
|
-
left: [{
|
|
47
|
-
value: oldStr,
|
|
48
|
-
type: DiffType.REMOVED
|
|
49
|
-
}],
|
|
50
|
-
right: [{
|
|
51
|
-
value: newStr,
|
|
52
|
-
type: DiffType.ADDED
|
|
53
|
-
}]
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Simple LCS-like algorithm for better diff
|
|
58
|
-
const left = [];
|
|
59
|
-
const right = [];
|
|
60
|
-
let i = 0,
|
|
61
|
-
j = 0;
|
|
62
|
-
|
|
63
|
-
// Find matching parts
|
|
64
|
-
while (i < oldParts.length && j < newParts.length) {
|
|
65
|
-
if (oldParts[i] === newParts[j]) {
|
|
66
|
-
// Parts match
|
|
67
|
-
left.push({
|
|
68
|
-
value: oldParts[i],
|
|
69
|
-
type: DiffType.DEFAULT
|
|
70
|
-
});
|
|
71
|
-
right.push({
|
|
72
|
-
value: newParts[j],
|
|
73
|
-
type: DiffType.DEFAULT
|
|
74
|
-
});
|
|
75
|
-
i++;
|
|
76
|
-
j++;
|
|
77
|
-
} else {
|
|
78
|
-
// Look ahead for matches
|
|
79
|
-
let foundMatch = false;
|
|
80
|
-
|
|
81
|
-
// Check if we can find newParts[j] in upcoming oldParts
|
|
82
|
-
for (let k = i + 1; k < Math.min(i + 5, oldParts.length); k++) {
|
|
83
|
-
if (oldParts[k] === newParts[j]) {
|
|
84
|
-
// Mark everything from i to k-1 as removed
|
|
85
|
-
for (let m = i; m < k; m++) {
|
|
86
|
-
left.push({
|
|
87
|
-
value: oldParts[m],
|
|
88
|
-
type: DiffType.REMOVED
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
i = k;
|
|
92
|
-
foundMatch = true;
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (!foundMatch) {
|
|
97
|
-
// Check if we can find oldParts[i] in upcoming newParts
|
|
98
|
-
for (let k = j + 1; k < Math.min(j + 5, newParts.length); k++) {
|
|
99
|
-
if (newParts[k] === oldParts[i]) {
|
|
100
|
-
// Mark everything from j to k-1 as added
|
|
101
|
-
for (let m = j; m < k; m++) {
|
|
102
|
-
right.push({
|
|
103
|
-
value: newParts[m],
|
|
104
|
-
type: DiffType.ADDED
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
j = k;
|
|
108
|
-
foundMatch = true;
|
|
109
|
-
break;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (!foundMatch) {
|
|
114
|
-
// No match found nearby, mark as changed
|
|
115
|
-
left.push({
|
|
116
|
-
value: oldParts[i],
|
|
117
|
-
type: DiffType.REMOVED
|
|
118
|
-
});
|
|
119
|
-
right.push({
|
|
120
|
-
value: newParts[j],
|
|
121
|
-
type: DiffType.ADDED
|
|
122
|
-
});
|
|
123
|
-
i++;
|
|
124
|
-
j++;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Handle remaining parts
|
|
130
|
-
while (i < oldParts.length) {
|
|
131
|
-
left.push({
|
|
132
|
-
value: oldParts[i],
|
|
133
|
-
type: DiffType.REMOVED
|
|
134
|
-
});
|
|
135
|
-
i++;
|
|
136
|
-
}
|
|
137
|
-
while (j < newParts.length) {
|
|
138
|
-
right.push({
|
|
139
|
-
value: newParts[j],
|
|
140
|
-
type: DiffType.ADDED
|
|
141
|
-
});
|
|
142
|
-
j++;
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
left,
|
|
146
|
-
right
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Convert object to formatted JSON lines
|
|
152
|
-
*/
|
|
153
|
-
function objectToLines(obj) {
|
|
154
|
-
if (obj === null || obj === undefined) {
|
|
155
|
-
return [String(obj)];
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
// Pretty print JSON with 2 space indentation
|
|
159
|
-
const jsonStr = JSON.stringify(obj, null, 2);
|
|
160
|
-
// Split into lines and filter out empty lines that appear between array elements
|
|
161
|
-
const lines = jsonStr.split("\n");
|
|
162
|
-
|
|
163
|
-
// Remove empty lines but keep structure
|
|
164
|
-
const filteredLines = [];
|
|
165
|
-
for (let i = 0; i < lines.length; i++) {
|
|
166
|
-
const line = lines[i];
|
|
167
|
-
const trimmed = line.trim();
|
|
168
|
-
|
|
169
|
-
// Keep the line if it's not empty or if it's meaningful whitespace
|
|
170
|
-
if (trimmed !== "") {
|
|
171
|
-
filteredLines.push(line);
|
|
172
|
-
} else if (i === 0 || i === lines.length - 1) {
|
|
173
|
-
// Keep first and last empty lines if they exist (though they shouldn't)
|
|
174
|
-
filteredLines.push(line);
|
|
175
|
-
}
|
|
176
|
-
// Skip other empty lines
|
|
177
|
-
}
|
|
178
|
-
return filteredLines;
|
|
179
|
-
} catch {
|
|
180
|
-
return [String(obj)];
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Apply showDiffOnly filter with context lines
|
|
185
|
-
*/
|
|
186
|
-
function filterDiffWithContext(diffs, contextLines) {
|
|
187
|
-
if (contextLines < 0) return diffs;
|
|
188
|
-
const result = [];
|
|
189
|
-
const changedIndices = [];
|
|
190
|
-
|
|
191
|
-
// Find all changed lines
|
|
192
|
-
diffs.forEach((diff, idx) => {
|
|
193
|
-
if (diff.type !== DiffType.DEFAULT) {
|
|
194
|
-
changedIndices.push(idx);
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// If no changes, return empty
|
|
199
|
-
if (changedIndices.length === 0) {
|
|
200
|
-
return [];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Build ranges to include
|
|
204
|
-
const ranges = [];
|
|
205
|
-
let currentStart = Math.max(0, changedIndices[0] - contextLines);
|
|
206
|
-
let currentEnd = Math.min(diffs.length - 1, changedIndices[0] + contextLines);
|
|
207
|
-
for (let i = 1; i < changedIndices.length; i++) {
|
|
208
|
-
const idx = changedIndices[i];
|
|
209
|
-
const rangeStart = Math.max(0, idx - contextLines);
|
|
210
|
-
const rangeEnd = Math.min(diffs.length - 1, idx + contextLines);
|
|
211
|
-
|
|
212
|
-
// If ranges overlap or are adjacent, merge them
|
|
213
|
-
if (rangeStart <= currentEnd + 1) {
|
|
214
|
-
currentEnd = Math.max(currentEnd, rangeEnd);
|
|
215
|
-
} else {
|
|
216
|
-
// Save current range and start a new one
|
|
217
|
-
ranges.push([currentStart, currentEnd]);
|
|
218
|
-
currentStart = rangeStart;
|
|
219
|
-
currentEnd = rangeEnd;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Don't forget the last range
|
|
224
|
-
ranges.push([currentStart, currentEnd]);
|
|
225
|
-
|
|
226
|
-
// Build result from ranges
|
|
227
|
-
ranges.forEach(([start, end]) => {
|
|
228
|
-
for (let i = start; i <= end; i++) {
|
|
229
|
-
result.push(diffs[i]);
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
return result;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Compare lines based on method
|
|
237
|
-
*/
|
|
238
|
-
function compareLinesWithMethod(line1, line2, method) {
|
|
239
|
-
switch (method) {
|
|
240
|
-
case "trimmedLines":
|
|
241
|
-
return line1.trim() === line2.trim();
|
|
242
|
-
case "chars":
|
|
243
|
-
case "words":
|
|
244
|
-
case "lines":
|
|
245
|
-
default:
|
|
246
|
-
return line1 === line2;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Compute line-by-line diff between two objects
|
|
252
|
-
*/
|
|
253
|
-
export function computeLineDiff(oldValue, newValue, options = {}) {
|
|
254
|
-
const {
|
|
255
|
-
compareMethod = "words",
|
|
256
|
-
disableWordDiff = false,
|
|
257
|
-
showDiffOnly = false,
|
|
258
|
-
contextLines = 3
|
|
259
|
-
} = options;
|
|
260
|
-
const oldLines = objectToLines(oldValue);
|
|
261
|
-
const newLines = objectToLines(newValue);
|
|
262
|
-
const result = [];
|
|
263
|
-
let leftLineNum = 1;
|
|
264
|
-
let rightLineNum = 1;
|
|
265
|
-
|
|
266
|
-
// Simple line diff algorithm (can be improved with LCS)
|
|
267
|
-
let i = 0,
|
|
268
|
-
j = 0;
|
|
269
|
-
while (i < oldLines.length || j < newLines.length) {
|
|
270
|
-
if (i >= oldLines.length) {
|
|
271
|
-
// Rest are additions
|
|
272
|
-
result.push({
|
|
273
|
-
rightLineNumber: rightLineNum++,
|
|
274
|
-
type: DiffType.ADDED,
|
|
275
|
-
rightContent: newLines[j],
|
|
276
|
-
rightRaw: newLines[j]
|
|
277
|
-
});
|
|
278
|
-
j++;
|
|
279
|
-
} else if (j >= newLines.length) {
|
|
280
|
-
// Rest are removals
|
|
281
|
-
result.push({
|
|
282
|
-
leftLineNumber: leftLineNum++,
|
|
283
|
-
type: DiffType.REMOVED,
|
|
284
|
-
leftContent: oldLines[i],
|
|
285
|
-
leftRaw: oldLines[i]
|
|
286
|
-
});
|
|
287
|
-
i++;
|
|
288
|
-
} else if (compareLinesWithMethod(oldLines[i], newLines[j], compareMethod === "trimmedLines" ? "trimmedLines" : "lines")) {
|
|
289
|
-
// Lines match (possibly after trimming if trimmedLines)
|
|
290
|
-
result.push({
|
|
291
|
-
leftLineNumber: leftLineNum++,
|
|
292
|
-
rightLineNumber: rightLineNum++,
|
|
293
|
-
type: DiffType.DEFAULT,
|
|
294
|
-
leftContent: oldLines[i],
|
|
295
|
-
rightContent: newLines[j],
|
|
296
|
-
leftRaw: oldLines[i],
|
|
297
|
-
rightRaw: newLines[j]
|
|
298
|
-
});
|
|
299
|
-
i++;
|
|
300
|
-
j++;
|
|
301
|
-
} else {
|
|
302
|
-
// Lines differ - check if it's a modification or separate add/remove
|
|
303
|
-
const oldTrimmed = oldLines[i].trim();
|
|
304
|
-
const newTrimmed = newLines[j].trim();
|
|
305
|
-
|
|
306
|
-
// Simple heuristic: if lines start similarly, treat as modification
|
|
307
|
-
if (oldTrimmed && newTrimmed && (oldTrimmed.startsWith(newTrimmed.substring(0, 3)) || newTrimmed.startsWith(oldTrimmed.substring(0, 3)))) {
|
|
308
|
-
// Treat as modification - compute word diff if enabled
|
|
309
|
-
if (!disableWordDiff && compareMethod !== "lines") {
|
|
310
|
-
const wordDiff = computeDiffByMethod(oldLines[i], newLines[j], compareMethod);
|
|
311
|
-
result.push({
|
|
312
|
-
leftLineNumber: leftLineNum++,
|
|
313
|
-
rightLineNumber: rightLineNum++,
|
|
314
|
-
type: DiffType.MODIFIED,
|
|
315
|
-
leftContent: wordDiff.left,
|
|
316
|
-
rightContent: wordDiff.right,
|
|
317
|
-
leftRaw: oldLines[i],
|
|
318
|
-
rightRaw: newLines[j]
|
|
319
|
-
});
|
|
320
|
-
} else {
|
|
321
|
-
// No word diff - just mark lines as different
|
|
322
|
-
result.push({
|
|
323
|
-
leftLineNumber: leftLineNum++,
|
|
324
|
-
rightLineNumber: rightLineNum++,
|
|
325
|
-
type: DiffType.MODIFIED,
|
|
326
|
-
leftContent: oldLines[i],
|
|
327
|
-
rightContent: newLines[j],
|
|
328
|
-
leftRaw: oldLines[i],
|
|
329
|
-
rightRaw: newLines[j]
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
i++;
|
|
333
|
-
j++;
|
|
334
|
-
} else {
|
|
335
|
-
// Treat as separate remove and add
|
|
336
|
-
result.push({
|
|
337
|
-
leftLineNumber: leftLineNum++,
|
|
338
|
-
type: DiffType.REMOVED,
|
|
339
|
-
leftContent: oldLines[i],
|
|
340
|
-
leftRaw: oldLines[i]
|
|
341
|
-
});
|
|
342
|
-
result.push({
|
|
343
|
-
rightLineNumber: rightLineNum++,
|
|
344
|
-
type: DiffType.ADDED,
|
|
345
|
-
rightContent: newLines[j],
|
|
346
|
-
rightRaw: newLines[j]
|
|
347
|
-
});
|
|
348
|
-
i++;
|
|
349
|
-
j++;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// Apply showDiffOnly filter if enabled
|
|
355
|
-
if (showDiffOnly) {
|
|
356
|
-
return filterDiffWithContext(result, contextLines);
|
|
357
|
-
}
|
|
358
|
-
return result;
|
|
359
|
-
}
|
|
12
|
+
export { computeLineDiff, DiffType, ABSENT_VALUE, isAbsent } from "@buoy-gg/shared-ui/dataViewer";
|