@rozenite/storage-plugin 1.4.0
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/CHANGELOG.md +28 -0
- package/LICENSE +20 -0
- package/README.md +77 -0
- package/dist/assets/panel-C7usUotG.js +22 -0
- package/dist/assets/panel-DMhXYHH4.css +1 -0
- package/dist/panel.html +31 -0
- package/dist/react-native.cjs +1 -0
- package/dist/react-native.d.ts +4 -0
- package/dist/react-native.js +200 -0
- package/dist/rozenite.config.d.ts +7 -0
- package/dist/rozenite.json +1 -0
- package/dist/src/react-native/adapters/async-storage.d.ts +33 -0
- package/dist/src/react-native/adapters/index.d.ts +6 -0
- package/dist/src/react-native/adapters/mmkv.d.ts +13 -0
- package/dist/src/react-native/adapters/secure-storage.d.ts +18 -0
- package/dist/src/react-native/is-garbled.d.ts +1 -0
- package/dist/src/react-native/storage-view.d.ts +19 -0
- package/dist/src/react-native/useRozeniteStoragePlugin.d.ts +6 -0
- package/dist/src/shared/messaging.d.ts +29 -0
- package/dist/src/shared/types.d.ts +60 -0
- package/dist/src/ui/add-entry-dialog.d.ts +9 -0
- package/dist/src/ui/confirm-dialog.d.ts +11 -0
- package/dist/src/ui/edit-entry-dialog.d.ts +9 -0
- package/dist/src/ui/editable-table.d.ts +10 -0
- package/dist/src/ui/entry-detail-dialog.d.ts +8 -0
- package/dist/src/ui/panel.d.ts +1 -0
- package/dist/types.cjs +1 -0
- package/dist/types.js +11 -0
- package/dist/useRozeniteStoragePlugin.cjs +1 -0
- package/dist/useRozeniteStoragePlugin.js +212 -0
- package/package.json +59 -0
- package/postcss.config.js +6 -0
- package/react-native.ts +40 -0
- package/rozenite.config.ts +8 -0
- package/src/css-modules.d.ts +4 -0
- package/src/react-native/adapters/async-storage.ts +115 -0
- package/src/react-native/adapters/index.ts +19 -0
- package/src/react-native/adapters/mmkv.ts +187 -0
- package/src/react-native/adapters/secure-storage.ts +76 -0
- package/src/react-native/is-garbled.ts +17 -0
- package/src/react-native/storage-view.ts +245 -0
- package/src/react-native/useRozeniteStoragePlugin.ts +162 -0
- package/src/shared/messaging.ts +37 -0
- package/src/shared/types.ts +66 -0
- package/src/ui/add-entry-dialog.tsx +310 -0
- package/src/ui/confirm-dialog.tsx +100 -0
- package/src/ui/edit-entry-dialog.tsx +280 -0
- package/src/ui/editable-table.tsx +298 -0
- package/src/ui/entry-detail-dialog.tsx +220 -0
- package/src/ui/globals.css +123 -0
- package/src/ui/panel.tsx +442 -0
- package/tailwind.config.ts +94 -0
- package/tsconfig.json +36 -0
- package/vite.config.ts +20 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { useRozeniteDevToolsClient as I } from "@rozenite/plugin-bridge";
|
|
2
|
+
import { useMemo as h, useEffect as S } from "react";
|
|
3
|
+
import { g as m, s as b } from "./types.js";
|
|
4
|
+
const $ = 1500, d = (t) => t.kind === "async", g = (t) => t.type === "buffer" ? `${t.type}:${t.value.join(",")}` : `${t.type}:${String(t.value)}`, v = (t) => new Map(t.map((e) => [e.key, e])), u = (t, e) => t.blacklist ? (t.blacklist.lastIndex = 0, t.blacklist.test(e)) : !1, E = (t, e, r) => {
|
|
5
|
+
if (!b(t, e))
|
|
6
|
+
throw new Error(
|
|
7
|
+
`Type "${e}" is not supported by storage "${r.storageId}" in adapter "${r.adapterId}".`
|
|
8
|
+
);
|
|
9
|
+
}, f = async (t) => (d(t), t.getAllKeys()), y = async (t, e) => (d(t), t.get(e)), P = async (t, e) => {
|
|
10
|
+
if (d(t)) {
|
|
11
|
+
await t.set(e);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
t.set(e);
|
|
15
|
+
}, z = async (t, e) => {
|
|
16
|
+
if (d(t)) {
|
|
17
|
+
await t.delete(e);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
t.delete(e);
|
|
21
|
+
}, w = async (t) => {
|
|
22
|
+
const e = await t();
|
|
23
|
+
return v(e);
|
|
24
|
+
}, M = (t, e, r) => {
|
|
25
|
+
e.forEach((i, c) => {
|
|
26
|
+
const p = t.get(c);
|
|
27
|
+
if (!p) {
|
|
28
|
+
r.onSet(i);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
g(p) !== g(i) && r.onSet(i);
|
|
32
|
+
}), t.forEach((i, c) => {
|
|
33
|
+
e.has(c) || r.onDelete(c);
|
|
34
|
+
});
|
|
35
|
+
}, R = async (t, e) => {
|
|
36
|
+
let r = await w(t);
|
|
37
|
+
const i = setInterval(async () => {
|
|
38
|
+
try {
|
|
39
|
+
const c = await w(t);
|
|
40
|
+
M(r, c, e), r = c;
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
}, $);
|
|
44
|
+
return {
|
|
45
|
+
remove: () => {
|
|
46
|
+
clearInterval(i);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}, A = (t, e) => {
|
|
50
|
+
const r = e.storage, i = {
|
|
51
|
+
adapterId: t.id,
|
|
52
|
+
storageId: e.id
|
|
53
|
+
}, c = async (o) => {
|
|
54
|
+
if (!u(e, o))
|
|
55
|
+
return y(r, o);
|
|
56
|
+
}, p = async () => {
|
|
57
|
+
const o = await f(r);
|
|
58
|
+
return (await Promise.all(
|
|
59
|
+
o.filter((s) => !u(e, s)).map((s) => y(r, s))
|
|
60
|
+
)).filter((s) => !!s);
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
id: m(i),
|
|
64
|
+
target: i,
|
|
65
|
+
adapterName: t.name,
|
|
66
|
+
storageName: e.name,
|
|
67
|
+
capabilities: e.capabilities,
|
|
68
|
+
get: c,
|
|
69
|
+
set: async (o) => {
|
|
70
|
+
E(e.capabilities, o.type, i), await P(r, o);
|
|
71
|
+
},
|
|
72
|
+
delete: async (o) => {
|
|
73
|
+
await z(r, o);
|
|
74
|
+
},
|
|
75
|
+
getAllKeys: async () => (await f(r)).filter((a) => !u(e, a)),
|
|
76
|
+
getAllEntries: p,
|
|
77
|
+
watch: async ({ onSet: o, onDelete: a }) => r.subscribe ? r.subscribe(async (s) => {
|
|
78
|
+
try {
|
|
79
|
+
const n = await c(s);
|
|
80
|
+
if (!n) {
|
|
81
|
+
a(s);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
o(n);
|
|
85
|
+
} catch {
|
|
86
|
+
}
|
|
87
|
+
}) : R(p, { onSet: o, onDelete: a })
|
|
88
|
+
};
|
|
89
|
+
}, F = (t) => t.flatMap(
|
|
90
|
+
(e) => e.storages.map((r) => A(e, r))
|
|
91
|
+
), k = ({
|
|
92
|
+
storages: t
|
|
93
|
+
}) => {
|
|
94
|
+
const e = h(() => F(t), [t]), r = I({
|
|
95
|
+
pluginId: "@rozenite/storage-plugin"
|
|
96
|
+
});
|
|
97
|
+
return S(() => {
|
|
98
|
+
if (!r)
|
|
99
|
+
return;
|
|
100
|
+
const i = async (a) => {
|
|
101
|
+
const s = a ? e.filter((n) => n.id === a) : e;
|
|
102
|
+
for (const n of s)
|
|
103
|
+
try {
|
|
104
|
+
const l = await n.getAllEntries();
|
|
105
|
+
r.send("snapshot", {
|
|
106
|
+
type: "snapshot",
|
|
107
|
+
target: n.target,
|
|
108
|
+
adapterName: n.adapterName,
|
|
109
|
+
storageName: n.storageName,
|
|
110
|
+
capabilities: n.capabilities,
|
|
111
|
+
entries: l
|
|
112
|
+
});
|
|
113
|
+
} catch (l) {
|
|
114
|
+
console.warn(
|
|
115
|
+
`[Rozenite] Storage Plugin: Failed to snapshot ${n.target.adapterId}/${n.target.storageId}.`,
|
|
116
|
+
l
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
i();
|
|
121
|
+
const c = [];
|
|
122
|
+
let p = !1;
|
|
123
|
+
Promise.all(
|
|
124
|
+
e.map(async (a) => {
|
|
125
|
+
try {
|
|
126
|
+
const s = await a.watch({
|
|
127
|
+
onSet: (n) => {
|
|
128
|
+
r.send("set-entry", {
|
|
129
|
+
type: "set-entry",
|
|
130
|
+
target: a.target,
|
|
131
|
+
entry: n
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
onDelete: (n) => {
|
|
135
|
+
r.send("delete-entry", {
|
|
136
|
+
type: "delete-entry",
|
|
137
|
+
target: a.target,
|
|
138
|
+
key: n
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
if (p) {
|
|
143
|
+
s.remove();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
c.push(s);
|
|
147
|
+
} catch (s) {
|
|
148
|
+
console.warn(
|
|
149
|
+
`[Rozenite] Storage Plugin: Failed to attach watcher for ${a.target.adapterId}/${a.target.storageId}.`,
|
|
150
|
+
s
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
const o = [
|
|
156
|
+
r.onMessage("set-entry", async ({ target: a, entry: s }) => {
|
|
157
|
+
const n = e.find(
|
|
158
|
+
(l) => l.target.adapterId === a.adapterId && l.target.storageId === a.storageId
|
|
159
|
+
);
|
|
160
|
+
if (!n) {
|
|
161
|
+
console.warn(
|
|
162
|
+
`[Rozenite] Storage Plugin: Storage target not found for ${a.adapterId}/${a.storageId}`
|
|
163
|
+
);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
await n.set(s);
|
|
168
|
+
} catch (l) {
|
|
169
|
+
console.warn(
|
|
170
|
+
`[Rozenite] Storage Plugin: Failed to set entry in ${a.adapterId}/${a.storageId}.`,
|
|
171
|
+
l
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}),
|
|
175
|
+
r.onMessage(
|
|
176
|
+
"delete-entry",
|
|
177
|
+
async ({ target: a, key: s }) => {
|
|
178
|
+
const n = e.find(
|
|
179
|
+
(l) => l.target.adapterId === a.adapterId && l.target.storageId === a.storageId
|
|
180
|
+
);
|
|
181
|
+
if (!n) {
|
|
182
|
+
console.warn(
|
|
183
|
+
`[Rozenite] Storage Plugin: Storage target not found for ${a.adapterId}/${a.storageId}`
|
|
184
|
+
);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
await n.delete(s);
|
|
189
|
+
} catch (l) {
|
|
190
|
+
console.warn(
|
|
191
|
+
`[Rozenite] Storage Plugin: Failed to delete entry in ${a.adapterId}/${a.storageId}.`,
|
|
192
|
+
l
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
),
|
|
197
|
+
r.onMessage("get-snapshot", async ({ target: a }) => {
|
|
198
|
+
if (a === "all") {
|
|
199
|
+
await i();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
await i(`${a.adapterId}:${a.storageId}`);
|
|
203
|
+
})
|
|
204
|
+
];
|
|
205
|
+
return () => {
|
|
206
|
+
p = !0, c.forEach((a) => a.remove()), o.forEach((a) => a.remove());
|
|
207
|
+
};
|
|
208
|
+
}, [r, e]), r;
|
|
209
|
+
};
|
|
210
|
+
export {
|
|
211
|
+
k as useRozeniteStoragePlugin
|
|
212
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rozenite/storage-plugin",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Generic Storage Inspector for Rozenite.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/react-native.cjs",
|
|
7
|
+
"module": "./dist/react-native.js",
|
|
8
|
+
"types": "./dist/react-native.d.ts",
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@rozenite/plugin-bridge": "1.4.0"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@tanstack/react-table": "^8.21.3",
|
|
14
|
+
"@types/react": "~19.2.2",
|
|
15
|
+
"autoprefixer": "^10.4.21",
|
|
16
|
+
"lucide-react": "^0.263.1",
|
|
17
|
+
"postcss": "^8.5.6",
|
|
18
|
+
"react": "19.2.0",
|
|
19
|
+
"react-dom": "19.2.0",
|
|
20
|
+
"react-json-tree": "^0.20.0",
|
|
21
|
+
"react-native": "0.83.1",
|
|
22
|
+
"react-native-mmkv": "^3.3.0",
|
|
23
|
+
"react-native-mmkv-v3": "npm:react-native-mmkv@^3.0.0",
|
|
24
|
+
"react-native-mmkv-v4": "npm:react-native-mmkv@^4.0.0",
|
|
25
|
+
"react-native-nitro-modules": "*",
|
|
26
|
+
"react-native-web": "^0.21.2",
|
|
27
|
+
"tailwindcss": "^3.4.17",
|
|
28
|
+
"tailwindcss-animate": "^1.0.7",
|
|
29
|
+
"typescript": "~5.9.3",
|
|
30
|
+
"vite": "^7.3.1",
|
|
31
|
+
"@rozenite/vite-plugin": "1.4.0",
|
|
32
|
+
"rozenite": "1.4.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@react-native-async-storage/async-storage": "*",
|
|
36
|
+
"expo-secure-store": "*",
|
|
37
|
+
"react": "*",
|
|
38
|
+
"react-native": "*",
|
|
39
|
+
"react-native-mmkv": "*"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@react-native-async-storage/async-storage": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"expo-secure-store": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"react-native-mmkv": {
|
|
49
|
+
"optional": true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "rozenite build",
|
|
55
|
+
"dev": "rozenite dev",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
57
|
+
"lint": "eslint ."
|
|
58
|
+
}
|
|
59
|
+
}
|
package/react-native.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export {
|
|
2
|
+
createAsyncStorageAdapter,
|
|
3
|
+
createExpoAsyncStorageAdapter,
|
|
4
|
+
createExpoSecureStorageAdapter,
|
|
5
|
+
createMMKVStorageAdapter,
|
|
6
|
+
} from './src/react-native/adapters';
|
|
7
|
+
export type {
|
|
8
|
+
AsyncStorageInstanceConfig,
|
|
9
|
+
AsyncStorageLike,
|
|
10
|
+
CreateAsyncStorageAdapterOptions,
|
|
11
|
+
CreateExpoAsyncStorageAdapterOptions,
|
|
12
|
+
CreateExpoSecureStorageAdapterOptions,
|
|
13
|
+
CreateMMKVStorageAdapterOptions,
|
|
14
|
+
SecureStorageLike,
|
|
15
|
+
} from './src/react-native/adapters';
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
AsyncStorage,
|
|
19
|
+
StorageAdapter,
|
|
20
|
+
StorageCapabilities,
|
|
21
|
+
StorageEntry,
|
|
22
|
+
StorageEntryType,
|
|
23
|
+
StorageEntryValue,
|
|
24
|
+
StorageNode,
|
|
25
|
+
SyncStorage,
|
|
26
|
+
} from './src/shared/types';
|
|
27
|
+
|
|
28
|
+
export let useRozeniteStoragePlugin: typeof import('./src/react-native/useRozeniteStoragePlugin').useRozeniteStoragePlugin;
|
|
29
|
+
|
|
30
|
+
const isWeb =
|
|
31
|
+
typeof window !== 'undefined' && window.navigator.product !== 'ReactNative';
|
|
32
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
33
|
+
const isServer = typeof window === 'undefined';
|
|
34
|
+
|
|
35
|
+
if (isDev && !isWeb && !isServer) {
|
|
36
|
+
useRozeniteStoragePlugin =
|
|
37
|
+
require('./src/react-native/useRozeniteStoragePlugin').useRozeniteStoragePlugin;
|
|
38
|
+
} else {
|
|
39
|
+
useRozeniteStoragePlugin = () => null;
|
|
40
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { StorageAdapter, StorageNode } from '../../shared/types';
|
|
2
|
+
|
|
3
|
+
export type AsyncStorageLike = {
|
|
4
|
+
getAllKeys: () => Promise<string[]>;
|
|
5
|
+
getItem: (key: string) => Promise<string | null>;
|
|
6
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
7
|
+
removeItem: (key: string) => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type AsyncStorageInstanceConfig = {
|
|
11
|
+
storage: AsyncStorageLike;
|
|
12
|
+
name?: string;
|
|
13
|
+
blacklist?: RegExp;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type SingleStorageOptions = {
|
|
17
|
+
storage: AsyncStorageLike;
|
|
18
|
+
adapterId?: string;
|
|
19
|
+
adapterName?: string;
|
|
20
|
+
storageId?: string;
|
|
21
|
+
storageName?: string;
|
|
22
|
+
blacklist?: RegExp;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type MultiStorageOptions = {
|
|
26
|
+
storages: Record<string, AsyncStorageLike | AsyncStorageInstanceConfig>;
|
|
27
|
+
adapterId?: string;
|
|
28
|
+
adapterName?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type CreateAsyncStorageAdapterOptions =
|
|
32
|
+
| SingleStorageOptions
|
|
33
|
+
| MultiStorageOptions;
|
|
34
|
+
|
|
35
|
+
const toStorageNode = (
|
|
36
|
+
storageId: string,
|
|
37
|
+
config: AsyncStorageLike | AsyncStorageInstanceConfig,
|
|
38
|
+
fallbackName?: string,
|
|
39
|
+
fallbackBlacklist?: RegExp
|
|
40
|
+
): StorageNode => {
|
|
41
|
+
const resolved =
|
|
42
|
+
'storage' in config
|
|
43
|
+
? config
|
|
44
|
+
: {
|
|
45
|
+
storage: config,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const { storage, name, blacklist } = resolved;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
id: storageId,
|
|
52
|
+
name: name ?? fallbackName ?? storageId,
|
|
53
|
+
blacklist: blacklist ?? fallbackBlacklist,
|
|
54
|
+
capabilities: {
|
|
55
|
+
supportedTypes: ['string'],
|
|
56
|
+
},
|
|
57
|
+
storage: {
|
|
58
|
+
kind: 'async',
|
|
59
|
+
getAllKeys: () => storage.getAllKeys(),
|
|
60
|
+
get: async (key) => {
|
|
61
|
+
const value = await storage.getItem(key);
|
|
62
|
+
if (value === null) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
key,
|
|
68
|
+
type: 'string',
|
|
69
|
+
value,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
set: async (entry) => {
|
|
73
|
+
if (entry.type !== 'string') {
|
|
74
|
+
throw new Error('AsyncStorage adapter supports only string values.');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await storage.setItem(entry.key, entry.value);
|
|
78
|
+
},
|
|
79
|
+
delete: (key) => storage.removeItem(key),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const createAsyncStorageAdapter = (
|
|
85
|
+
options: CreateAsyncStorageAdapterOptions
|
|
86
|
+
): StorageAdapter => {
|
|
87
|
+
const { adapterId = 'async-storage', adapterName = 'AsyncStorage' } =
|
|
88
|
+
options;
|
|
89
|
+
|
|
90
|
+
const storageNodes: StorageNode[] =
|
|
91
|
+
'storages' in options
|
|
92
|
+
? Object.entries(options.storages).map(([storageId, config]) =>
|
|
93
|
+
toStorageNode(storageId, config)
|
|
94
|
+
)
|
|
95
|
+
: [
|
|
96
|
+
toStorageNode(
|
|
97
|
+
options.storageId ?? 'default',
|
|
98
|
+
options.storage,
|
|
99
|
+
options.storageName ?? 'Default Storage',
|
|
100
|
+
options.blacklist
|
|
101
|
+
),
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
id: adapterId,
|
|
106
|
+
name: adapterName,
|
|
107
|
+
storages: storageNodes,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @deprecated Use createAsyncStorageAdapter instead.
|
|
113
|
+
*/
|
|
114
|
+
export const createExpoAsyncStorageAdapter = createAsyncStorageAdapter;
|
|
115
|
+
export type CreateExpoAsyncStorageAdapterOptions = CreateAsyncStorageAdapterOptions;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { createMMKVStorageAdapter } from './mmkv';
|
|
2
|
+
export type { CreateMMKVStorageAdapterOptions } from './mmkv';
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
createAsyncStorageAdapter,
|
|
6
|
+
createExpoAsyncStorageAdapter,
|
|
7
|
+
} from './async-storage';
|
|
8
|
+
export type {
|
|
9
|
+
AsyncStorageInstanceConfig,
|
|
10
|
+
AsyncStorageLike,
|
|
11
|
+
CreateAsyncStorageAdapterOptions,
|
|
12
|
+
CreateExpoAsyncStorageAdapterOptions,
|
|
13
|
+
} from './async-storage';
|
|
14
|
+
|
|
15
|
+
export { createExpoSecureStorageAdapter } from './secure-storage';
|
|
16
|
+
export type {
|
|
17
|
+
CreateExpoSecureStorageAdapterOptions,
|
|
18
|
+
SecureStorageLike,
|
|
19
|
+
} from './secure-storage';
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { MMKV as MMKVV3 } from 'react-native-mmkv-v3';
|
|
2
|
+
import type { MMKV as MMKVV4 } from 'react-native-mmkv-v4';
|
|
3
|
+
import type { StorageAdapter, StorageEntry, StorageNode } from '../../shared/types';
|
|
4
|
+
import { DEFAULT_SUPPORTED_TYPES } from '../../shared/types';
|
|
5
|
+
import { looksLikeGarbled } from '../is-garbled';
|
|
6
|
+
|
|
7
|
+
type MMKV = MMKVV3 | MMKVV4;
|
|
8
|
+
|
|
9
|
+
type MMKVAdapter = {
|
|
10
|
+
set: (key: string, value: boolean | string | number | ArrayBuffer) => void;
|
|
11
|
+
getBoolean: (key: string) => boolean | undefined;
|
|
12
|
+
getString: (key: string) => string | undefined;
|
|
13
|
+
getNumber: (key: string) => number | undefined;
|
|
14
|
+
getBuffer: (key: string) => ArrayBuffer | undefined;
|
|
15
|
+
delete: (key: string) => void;
|
|
16
|
+
getAllKeys: () => string[];
|
|
17
|
+
addOnValueChangedListener: (callback: (key: string) => void) => {
|
|
18
|
+
remove: () => void;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type MMKVBlacklistConfig = RegExp | Record<string, RegExp | undefined>;
|
|
23
|
+
|
|
24
|
+
export type CreateMMKVStorageAdapterOptions = {
|
|
25
|
+
adapterId?: string;
|
|
26
|
+
adapterName?: string;
|
|
27
|
+
storages: MMKV[] | Record<string, MMKV>;
|
|
28
|
+
blacklist?: MMKVBlacklistConfig;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const isMMKVV4 = (mmkv: MMKV): mmkv is MMKVV4 => 'remove' in mmkv;
|
|
32
|
+
|
|
33
|
+
const normalizeStorages = (storages: MMKV[] | Record<string, MMKV>) => {
|
|
34
|
+
if (Array.isArray(storages)) {
|
|
35
|
+
const isAnyStorageV4 = storages.some(isMMKVV4);
|
|
36
|
+
|
|
37
|
+
if (isAnyStorageV4) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
'[Rozenite] Storage Plugin: MMKV arrays are not supported for v4 storages. Pass a record of storage IDs and MMKV instances.'
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return Object.fromEntries(
|
|
44
|
+
(storages as MMKVV3[]).map((storage) => [storage['id'], storage])
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return storages;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getMMKVAdapter = (mmkv: MMKV): MMKVAdapter => {
|
|
52
|
+
if (isMMKVV4(mmkv)) {
|
|
53
|
+
return {
|
|
54
|
+
set: (key, value) => mmkv.set(key, value),
|
|
55
|
+
getBoolean: (key) => mmkv.getBoolean(key),
|
|
56
|
+
getString: (key) => mmkv.getString(key),
|
|
57
|
+
getNumber: (key) => mmkv.getNumber(key),
|
|
58
|
+
getBuffer: (key) => mmkv.getBuffer(key),
|
|
59
|
+
delete: (key) => mmkv.remove(key),
|
|
60
|
+
getAllKeys: () => mmkv.getAllKeys(),
|
|
61
|
+
addOnValueChangedListener: (callback) =>
|
|
62
|
+
mmkv.addOnValueChangedListener(callback),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
set: (key, value) => mmkv.set(key, value),
|
|
68
|
+
getBoolean: (key) => mmkv.getBoolean(key),
|
|
69
|
+
getString: (key) => mmkv.getString(key),
|
|
70
|
+
getNumber: (key) => mmkv.getNumber(key),
|
|
71
|
+
getBuffer: (key) => mmkv.getBuffer(key) as ArrayBuffer | undefined,
|
|
72
|
+
delete: (key) => mmkv.delete(key),
|
|
73
|
+
getAllKeys: () => mmkv.getAllKeys(),
|
|
74
|
+
addOnValueChangedListener: (callback) => mmkv.addOnValueChangedListener(callback),
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getEntry = (adapter: MMKVAdapter, key: string): StorageEntry | undefined => {
|
|
79
|
+
const stringValue = adapter.getString(key);
|
|
80
|
+
|
|
81
|
+
if (stringValue !== undefined && stringValue.length > 0) {
|
|
82
|
+
if (looksLikeGarbled(stringValue)) {
|
|
83
|
+
return {
|
|
84
|
+
key,
|
|
85
|
+
type: 'buffer',
|
|
86
|
+
value: Array.from(new TextEncoder().encode(stringValue)),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
key,
|
|
92
|
+
type: 'string',
|
|
93
|
+
value: stringValue,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const numberValue = adapter.getNumber(key);
|
|
98
|
+
if (numberValue !== undefined) {
|
|
99
|
+
return {
|
|
100
|
+
key,
|
|
101
|
+
type: 'number',
|
|
102
|
+
value: numberValue,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const booleanValue = adapter.getBoolean(key);
|
|
107
|
+
if (booleanValue !== undefined) {
|
|
108
|
+
return {
|
|
109
|
+
key,
|
|
110
|
+
type: 'boolean',
|
|
111
|
+
value: booleanValue,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const bufferValue = adapter.getBuffer(key);
|
|
116
|
+
if (bufferValue !== undefined) {
|
|
117
|
+
return {
|
|
118
|
+
key,
|
|
119
|
+
type: 'buffer',
|
|
120
|
+
value: Array.from(new Uint8Array(bufferValue)),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return undefined;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const setEntry = (adapter: MMKVAdapter, entry: StorageEntry) => {
|
|
128
|
+
if (entry.type === 'buffer') {
|
|
129
|
+
adapter.set(entry.key, new Uint8Array(entry.value).buffer);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
adapter.set(entry.key, entry.value);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const getStorageBlacklist = (
|
|
137
|
+
config: MMKVBlacklistConfig | undefined,
|
|
138
|
+
storageId: string
|
|
139
|
+
) => {
|
|
140
|
+
if (!config) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (config instanceof RegExp) {
|
|
145
|
+
return config;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return config[storageId];
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const createMMKVStorageAdapter = ({
|
|
152
|
+
adapterId = 'mmkv',
|
|
153
|
+
adapterName = 'MMKV',
|
|
154
|
+
storages,
|
|
155
|
+
blacklist,
|
|
156
|
+
}: CreateMMKVStorageAdapterOptions): StorageAdapter => {
|
|
157
|
+
const normalizedStorages = normalizeStorages(storages) as Record<string, MMKV>;
|
|
158
|
+
|
|
159
|
+
const storageNodes: StorageNode[] = Object.entries(normalizedStorages).map(
|
|
160
|
+
([storageId, storage]) => {
|
|
161
|
+
const mmkv = getMMKVAdapter(storage);
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
id: storageId,
|
|
165
|
+
name: storageId,
|
|
166
|
+
blacklist: getStorageBlacklist(blacklist, storageId),
|
|
167
|
+
capabilities: {
|
|
168
|
+
supportedTypes: DEFAULT_SUPPORTED_TYPES,
|
|
169
|
+
},
|
|
170
|
+
storage: {
|
|
171
|
+
kind: 'sync',
|
|
172
|
+
getAllKeys: () => mmkv.getAllKeys(),
|
|
173
|
+
get: (key) => getEntry(mmkv, key),
|
|
174
|
+
set: (entry) => setEntry(mmkv, entry),
|
|
175
|
+
delete: (key) => mmkv.delete(key),
|
|
176
|
+
subscribe: (callback) => mmkv.addOnValueChangedListener(callback),
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
id: adapterId,
|
|
184
|
+
name: adapterName,
|
|
185
|
+
storages: storageNodes,
|
|
186
|
+
};
|
|
187
|
+
};
|