@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +20 -0
  3. package/README.md +77 -0
  4. package/dist/assets/panel-C7usUotG.js +22 -0
  5. package/dist/assets/panel-DMhXYHH4.css +1 -0
  6. package/dist/panel.html +31 -0
  7. package/dist/react-native.cjs +1 -0
  8. package/dist/react-native.d.ts +4 -0
  9. package/dist/react-native.js +200 -0
  10. package/dist/rozenite.config.d.ts +7 -0
  11. package/dist/rozenite.json +1 -0
  12. package/dist/src/react-native/adapters/async-storage.d.ts +33 -0
  13. package/dist/src/react-native/adapters/index.d.ts +6 -0
  14. package/dist/src/react-native/adapters/mmkv.d.ts +13 -0
  15. package/dist/src/react-native/adapters/secure-storage.d.ts +18 -0
  16. package/dist/src/react-native/is-garbled.d.ts +1 -0
  17. package/dist/src/react-native/storage-view.d.ts +19 -0
  18. package/dist/src/react-native/useRozeniteStoragePlugin.d.ts +6 -0
  19. package/dist/src/shared/messaging.d.ts +29 -0
  20. package/dist/src/shared/types.d.ts +60 -0
  21. package/dist/src/ui/add-entry-dialog.d.ts +9 -0
  22. package/dist/src/ui/confirm-dialog.d.ts +11 -0
  23. package/dist/src/ui/edit-entry-dialog.d.ts +9 -0
  24. package/dist/src/ui/editable-table.d.ts +10 -0
  25. package/dist/src/ui/entry-detail-dialog.d.ts +8 -0
  26. package/dist/src/ui/panel.d.ts +1 -0
  27. package/dist/types.cjs +1 -0
  28. package/dist/types.js +11 -0
  29. package/dist/useRozeniteStoragePlugin.cjs +1 -0
  30. package/dist/useRozeniteStoragePlugin.js +212 -0
  31. package/package.json +59 -0
  32. package/postcss.config.js +6 -0
  33. package/react-native.ts +40 -0
  34. package/rozenite.config.ts +8 -0
  35. package/src/css-modules.d.ts +4 -0
  36. package/src/react-native/adapters/async-storage.ts +115 -0
  37. package/src/react-native/adapters/index.ts +19 -0
  38. package/src/react-native/adapters/mmkv.ts +187 -0
  39. package/src/react-native/adapters/secure-storage.ts +76 -0
  40. package/src/react-native/is-garbled.ts +17 -0
  41. package/src/react-native/storage-view.ts +245 -0
  42. package/src/react-native/useRozeniteStoragePlugin.ts +162 -0
  43. package/src/shared/messaging.ts +37 -0
  44. package/src/shared/types.ts +66 -0
  45. package/src/ui/add-entry-dialog.tsx +310 -0
  46. package/src/ui/confirm-dialog.tsx +100 -0
  47. package/src/ui/edit-entry-dialog.tsx +280 -0
  48. package/src/ui/editable-table.tsx +298 -0
  49. package/src/ui/entry-detail-dialog.tsx +220 -0
  50. package/src/ui/globals.css +123 -0
  51. package/src/ui/panel.tsx +442 -0
  52. package/tailwind.config.ts +94 -0
  53. package/tsconfig.json +36 -0
  54. 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
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -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,8 @@
1
+ export default {
2
+ panels: [
3
+ {
4
+ name: 'Storage',
5
+ source: './src/ui/panel.tsx',
6
+ },
7
+ ],
8
+ };
@@ -0,0 +1,4 @@
1
+ declare module '*.module.css' {
2
+ const classes: { [key: string]: string };
3
+ export default classes;
4
+ }
@@ -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
+ };