@rozenite/storage-plugin 1.13.0 → 2.1.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 (84) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +6 -1
  3. package/dist/devtools/assets/panel-BlDRXgVW.js +32 -0
  4. package/dist/devtools/assets/panel-DBweZnW5.css +1 -0
  5. package/dist/devtools/panel.html +2 -2
  6. package/dist/react-native/chunks/async-storage.require.cjs +1 -1
  7. package/dist/react-native/chunks/async-storage.require.js +8 -7
  8. package/dist/react-native/chunks/index.require.cjs +1 -1
  9. package/dist/react-native/chunks/index.require.js +70 -66
  10. package/dist/react-native/chunks/secure-storage.require.cjs +1 -1
  11. package/dist/react-native/chunks/secure-storage.require.js +17 -13
  12. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +1 -1
  13. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +863 -344
  14. package/dist/react-native/index.d.ts +166 -18
  15. package/dist/rozenite.json +1 -1
  16. package/dist/sdk/index.cjs +1 -1
  17. package/dist/sdk/index.d.ts +13 -5
  18. package/dist/sdk/index.js +21 -12
  19. package/package.json +35 -37
  20. package/react-native.ts +12 -22
  21. package/rozenite.config.ts +74 -0
  22. package/sdk.ts +1 -5
  23. package/src/react-native/__tests__/entry-preview-pagination.test.ts +234 -0
  24. package/src/react-native/__tests__/export-snapshot.test.ts +138 -0
  25. package/src/react-native/__tests__/full-entry-request.test.ts +143 -0
  26. package/src/react-native/__tests__/import.test.ts +174 -11
  27. package/src/react-native/__tests__/storage-discovery.test.ts +144 -0
  28. package/src/react-native/__tests__/storage-view.test.ts +172 -0
  29. package/src/react-native/__tests__/stress-verification.test.ts +140 -0
  30. package/src/react-native/__tests__/use-storage-agent-tools.test.ts +221 -0
  31. package/src/react-native/adapters/__tests__/mmkv.test.ts +20 -30
  32. package/src/react-native/adapters/async-storage.ts +8 -9
  33. package/src/react-native/adapters/index.ts +2 -8
  34. package/src/react-native/adapters/mmkv.ts +12 -24
  35. package/src/react-native/adapters/secure-storage.ts +4 -0
  36. package/src/react-native/entry-preview-pagination.ts +261 -0
  37. package/src/react-native/export-snapshot.ts +88 -0
  38. package/src/react-native/full-entry-request.ts +91 -0
  39. package/src/react-native/import.ts +179 -16
  40. package/src/react-native/storage-discovery.ts +92 -0
  41. package/src/react-native/storage-view.ts +47 -109
  42. package/src/react-native/useRozeniteStoragePlugin.ts +153 -129
  43. package/src/react-native/useStorageAgentTools.ts +196 -91
  44. package/src/shared/__tests__/entry-preview.test.ts +99 -0
  45. package/src/shared/__tests__/snapshot.test.ts +31 -32
  46. package/src/shared/agent-tools.ts +33 -38
  47. package/src/shared/entry-preview.ts +54 -0
  48. package/src/shared/messaging.ts +140 -21
  49. package/src/shared/snapshot.ts +12 -18
  50. package/src/shared/types.ts +21 -4
  51. package/src/ui/__tests__/binary-value-editor-state.test.ts +16 -13
  52. package/src/ui/__tests__/binary.test.ts +31 -33
  53. package/src/ui/__tests__/large-value-viewer-state.test.ts +21 -0
  54. package/src/ui/__tests__/large-value-viewer.test.tsx +54 -0
  55. package/src/ui/__tests__/panel.test.tsx +382 -0
  56. package/src/ui/__tests__/storage-groups.test.ts +88 -0
  57. package/src/ui/__tests__/type-conversion.test.ts +8 -24
  58. package/src/ui/__tests__/use-storage-entries.test.tsx +337 -0
  59. package/src/ui/add-entry-dialog.tsx +66 -117
  60. package/src/ui/binary-value-editor-state.ts +16 -25
  61. package/src/ui/binary-value-editor.tsx +59 -47
  62. package/src/ui/binary.ts +54 -15
  63. package/src/ui/edit-entry-dialog.tsx +68 -104
  64. package/src/ui/editor-switcher.tsx +9 -16
  65. package/src/ui/entry-detail-dialog.tsx +62 -181
  66. package/src/ui/format-value.tsx +25 -0
  67. package/src/ui/globals.css +1 -123
  68. package/src/ui/import-dialog.tsx +72 -109
  69. package/src/ui/large-value-viewer-state.ts +25 -0
  70. package/src/ui/large-value-viewer.tsx +65 -0
  71. package/src/ui/panel.tsx +649 -509
  72. package/src/ui/query-client.tsx +23 -0
  73. package/src/ui/storage-groups.ts +66 -0
  74. package/src/ui/type-conversion.ts +2 -5
  75. package/src/ui/typed-value-editor.tsx +15 -20
  76. package/src/ui/use-storage-entries.ts +192 -0
  77. package/src/ui/utils.ts +1 -4
  78. package/tsconfig.json +5 -1
  79. package/dist/devtools/assets/panel-Bm-SWF7d.js +0 -33
  80. package/dist/devtools/assets/panel-DIqI4WSp.css +0 -1
  81. package/postcss.config.js +0 -6
  82. package/src/ui/confirm-dialog.tsx +0 -100
  83. package/src/ui/editable-table.tsx +0 -300
  84. package/tailwind.config.ts +0 -94
@@ -0,0 +1,140 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { StorageAdapter, StorageEntry, StorageEntryType } from '../../shared/types';
3
+ import {
4
+ ENTRY_PREVIEW_READ_CONCURRENCY,
5
+ MAX_ENTRY_PREVIEW_PAGE_SIZE,
6
+ handleListEntryPreviewsRequest,
7
+ } from '../entry-preview-pagination';
8
+ import { handleStorageDiscoveryRequest } from '../storage-discovery';
9
+ import { createStorageViews } from '../storage-view';
10
+
11
+ const STORAGE_COUNT = 250;
12
+ const ENTRIES_PER_STORAGE = 1_000;
13
+ const MEGABYTE_VALUE = 'x'.repeat(1024 * 1024);
14
+ const selectedTarget = { adapterId: 'stress', storageId: 'storage-000' };
15
+
16
+ const keys = Array.from(
17
+ { length: ENTRIES_PER_STORAGE },
18
+ (_, index) => `entry-${String(index).padStart(4, '0')}`,
19
+ );
20
+
21
+ const previewRequest = (overrides: Record<string, unknown> = {}) => ({
22
+ type: 'list-entry-previews' as const,
23
+ requestId: 'stress-request',
24
+ target: selectedTarget,
25
+ limit: MAX_ENTRY_PREVIEW_PAGE_SIZE,
26
+ ...overrides,
27
+ });
28
+
29
+ const createStressAdapter = () => {
30
+ let activeReads = 0;
31
+ let maxActiveReads = 0;
32
+ const storageReads = Array.from({ length: STORAGE_COUNT }, (_, index) => {
33
+ const getAllKeys = vi.fn(async () => keys);
34
+ const get = vi.fn(async (key: string): Promise<StorageEntry> => {
35
+ activeReads += 1;
36
+ maxActiveReads = Math.max(maxActiveReads, activeReads);
37
+ // This fixture is intentionally asynchronous. The assertion below uses
38
+ // counts, rather than duration, so it stays deterministic under load.
39
+ await new Promise((resolve) => setTimeout(resolve, 1));
40
+ activeReads -= 1;
41
+ return { key, type: 'string', value: MEGABYTE_VALUE };
42
+ });
43
+
44
+ return {
45
+ getAllKeys,
46
+ get,
47
+ node: {
48
+ id: `storage-${String(index).padStart(3, '0')}`,
49
+ name: `Stress storage ${index}`,
50
+ capabilities: { supportedTypes: ['string'] as StorageEntryType[] },
51
+ storage: {
52
+ kind: 'async' as const,
53
+ getAllKeys,
54
+ get,
55
+ set: vi.fn(async () => undefined),
56
+ delete: vi.fn(async () => undefined),
57
+ clear: vi.fn(async () => undefined),
58
+ },
59
+ },
60
+ };
61
+ });
62
+
63
+ const adapter: StorageAdapter = {
64
+ id: 'stress',
65
+ name: 'Stress adapter',
66
+ storages: storageReads.map(({ node }) => node),
67
+ };
68
+
69
+ return {
70
+ maxActiveReads: () => maxActiveReads,
71
+ storageReads,
72
+ views: createStorageViews([adapter]),
73
+ };
74
+ };
75
+
76
+ describe('storage scalability stress fixture', () => {
77
+ it('discovers many storages without starting value work', async () => {
78
+ const fixture = createStressAdapter();
79
+
80
+ expect(
81
+ await handleStorageDiscoveryRequest(fixture.views, {
82
+ type: 'discover-storages',
83
+ requestId: 'discover-stress',
84
+ }),
85
+ ).toMatchObject({
86
+ type: 'storage-descriptors',
87
+ storages: expect.any(Array),
88
+ });
89
+ expect(fixture.views).toHaveLength(STORAGE_COUNT);
90
+ for (const { get, getAllKeys } of fixture.storageReads) {
91
+ expect(getAllKeys).toHaveBeenCalledTimes(1);
92
+ expect(get).not.toHaveBeenCalled();
93
+ }
94
+ });
95
+
96
+ it('reads only one bounded page from the selected storage when values are slow and large', async () => {
97
+ const fixture = createStressAdapter();
98
+
99
+ const result = await handleListEntryPreviewsRequest(fixture.views, previewRequest());
100
+
101
+ expect(result).toMatchObject({
102
+ type: 'entry-previews',
103
+ items: Array.from({ length: MAX_ENTRY_PREVIEW_PAGE_SIZE }, () =>
104
+ expect.objectContaining({
105
+ preview: 'x'.repeat(256),
106
+ valueSize: MEGABYTE_VALUE.length,
107
+ isTruncated: true,
108
+ }),
109
+ ),
110
+ });
111
+ if (result.type === 'entry-previews') {
112
+ expect(result.items.every((item) => !('value' in item))).toBe(true);
113
+ }
114
+ expect(fixture.storageReads[0]?.getAllKeys).toHaveBeenCalledTimes(1);
115
+ expect(fixture.storageReads[0]?.get).toHaveBeenCalledTimes(MAX_ENTRY_PREVIEW_PAGE_SIZE);
116
+ expect(fixture.maxActiveReads()).toBeLessThanOrEqual(ENTRY_PREVIEW_READ_CONCURRENCY);
117
+ for (const { get, getAllKeys } of fixture.storageReads.slice(1)) {
118
+ expect(getAllKeys).not.toHaveBeenCalled();
119
+ expect(get).not.toHaveBeenCalled();
120
+ }
121
+ });
122
+
123
+ it('searches keys first and reads only the resulting page', async () => {
124
+ const fixture = createStressAdapter();
125
+
126
+ const result = await handleListEntryPreviewsRequest(
127
+ fixture.views,
128
+ previewRequest({ search: ' 0999 ' }),
129
+ );
130
+
131
+ expect(result).toMatchObject({
132
+ type: 'entry-previews',
133
+ items: [expect.objectContaining({ key: 'entry-0999' })],
134
+ });
135
+ expect(fixture.storageReads[0]?.get).toHaveBeenCalledExactlyOnceWith('entry-0999');
136
+ for (const { get } of fixture.storageReads.slice(1)) {
137
+ expect(get).not.toHaveBeenCalled();
138
+ }
139
+ });
140
+ });
@@ -0,0 +1,221 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { MAX_PAGE_LIMIT } from '@rozenite/agent-shared';
3
+ import { storageToolDefinitions } from '../../shared/agent-tools';
4
+ import type { StorageAdapter, StorageEntry } from '../../shared/types';
5
+
6
+ vi.mock('@rozenite/agent-bridge', () => ({
7
+ useRozenitePluginAgentTool: vi.fn(),
8
+ }));
9
+
10
+ import { createStorageAgentHandlers } from '../useStorageAgentTools';
11
+ import { createStorageViews } from '../storage-view';
12
+
13
+ const createViews = (
14
+ storages: Array<{
15
+ adapterId: string;
16
+ storageId: string;
17
+ entries: Record<string, StorageEntry>;
18
+ }>,
19
+ ) => {
20
+ const adapters = storages.map(({ adapterId, storageId, entries }): StorageAdapter => ({
21
+ id: adapterId,
22
+ name: `Adapter ${adapterId}`,
23
+ storages: [
24
+ {
25
+ id: storageId,
26
+ name: `Storage ${storageId}`,
27
+ capabilities: { supportedTypes: ['string', 'buffer'] },
28
+ storage: {
29
+ kind: 'sync',
30
+ getAllKeys: vi.fn(() => Object.keys(entries)),
31
+ get: vi.fn((key: string) => entries[key]),
32
+ set: vi.fn(),
33
+ delete: vi.fn(),
34
+ clear: vi.fn(),
35
+ },
36
+ },
37
+ ],
38
+ }));
39
+
40
+ return { adapters, views: createStorageViews(adapters) };
41
+ };
42
+
43
+ const entries = (...keys: string[]): Record<string, StorageEntry> =>
44
+ Object.fromEntries(keys.map((key) => [key, { key, type: 'string', value: `value:${key}` }]));
45
+
46
+ describe('storage Agent tool handlers', () => {
47
+ it('lists storage descriptors without reading keys or values', async () => {
48
+ const { adapters, views } = createViews([
49
+ { adapterId: 'one', storageId: 'first', entries: entries('a') },
50
+ { adapterId: 'two', storageId: 'second', entries: entries('b') },
51
+ ]);
52
+
53
+ await expect(createStorageAgentHandlers(views).listStorages()).resolves.toEqual({
54
+ storages: [
55
+ {
56
+ adapterId: 'one',
57
+ storageId: 'first',
58
+ adapterName: 'Adapter one',
59
+ storageName: 'Storage first',
60
+ supportedTypes: ['string', 'buffer'],
61
+ },
62
+ {
63
+ adapterId: 'two',
64
+ storageId: 'second',
65
+ adapterName: 'Adapter two',
66
+ storageName: 'Storage second',
67
+ supportedTypes: ['string', 'buffer'],
68
+ },
69
+ ],
70
+ });
71
+ for (const adapter of adapters) {
72
+ const storage = adapter.storages[0].storage;
73
+ expect(storage.getAllKeys).not.toHaveBeenCalled();
74
+ expect(storage.get).not.toHaveBeenCalled();
75
+ }
76
+ });
77
+
78
+ it('uses the shared cursor pagination contract and returns keys without values', async () => {
79
+ const { adapters, views } = createViews([
80
+ {
81
+ adapterId: 'adapter',
82
+ storageId: 'storage',
83
+ entries: entries('first', 'second', 'third'),
84
+ },
85
+ ]);
86
+ const handlers = createStorageAgentHandlers(views);
87
+
88
+ expect(storageToolDefinitions.listEntries.pagination).toEqual({
89
+ kind: 'cursor',
90
+ fields: ['key'],
91
+ defaultFields: ['key'],
92
+ });
93
+
94
+ const first = await handlers.listEntries({ limit: 2 });
95
+ expect(first).toEqual({
96
+ adapterId: 'adapter',
97
+ storageId: 'storage',
98
+ total: 3,
99
+ items: [{ key: 'first' }, { key: 'second' }],
100
+ page: {
101
+ limit: 2,
102
+ hasMore: true,
103
+ nextCursor: expect.any(String),
104
+ },
105
+ });
106
+ expect(first.items.every((item) => !('value' in item))).toBe(true);
107
+
108
+ await expect(handlers.listEntries({ cursor: first.page.nextCursor })).resolves.toEqual({
109
+ adapterId: 'adapter',
110
+ storageId: 'storage',
111
+ total: 3,
112
+ items: [{ key: 'third' }],
113
+ page: { limit: 20, hasMore: false },
114
+ });
115
+ expect(adapters[0].storages[0].storage.get).not.toHaveBeenCalled();
116
+ });
117
+
118
+ it('defaults invalid limits and offsets and caps oversized finite limits', async () => {
119
+ const { views } = createViews([
120
+ {
121
+ adapterId: 'adapter',
122
+ storageId: 'storage',
123
+ entries: entries(
124
+ ...Array.from({ length: MAX_PAGE_LIMIT + 1 }, (_, index) => String(index)),
125
+ ),
126
+ },
127
+ ]);
128
+ const handlers = createStorageAgentHandlers(views);
129
+
130
+ for (const limit of [-1, 1.5, Number.NaN, Number.POSITIVE_INFINITY]) {
131
+ await expect(handlers.listEntries({ limit })).resolves.toMatchObject({
132
+ page: { limit: 20 },
133
+ });
134
+ }
135
+ for (const offset of [
136
+ -1,
137
+ 1.5,
138
+ Number.NaN,
139
+ Number.POSITIVE_INFINITY,
140
+ Number.MAX_SAFE_INTEGER + 1,
141
+ ]) {
142
+ await expect(handlers.listEntries({ offset })).resolves.toMatchObject({
143
+ items: expect.arrayContaining([{ key: '0' }]),
144
+ });
145
+ }
146
+ const capped = await handlers.listEntries({ limit: MAX_PAGE_LIMIT + 1 });
147
+ expect(capped.items).toHaveLength(MAX_PAGE_LIMIT);
148
+ expect(capped.page).toMatchObject({
149
+ limit: MAX_PAGE_LIMIT,
150
+ hasMore: true,
151
+ });
152
+ });
153
+
154
+ it('rejects malformed and target-mismatched cursors and ambiguous selections', async () => {
155
+ const { views } = createViews([
156
+ { adapterId: 'adapter', storageId: 'one', entries: entries('a', 'b') },
157
+ { adapterId: 'adapter', storageId: 'two', entries: entries('c') },
158
+ { adapterId: 'other', storageId: 'one', entries: entries('d') },
159
+ ]);
160
+ const handlers = createStorageAgentHandlers(views);
161
+ const cursor = (
162
+ await handlers.listEntries({
163
+ adapterId: 'adapter',
164
+ storageId: 'one',
165
+ limit: 1,
166
+ })
167
+ ).page.nextCursor!;
168
+
169
+ await expect(
170
+ handlers.listEntries({
171
+ adapterId: 'adapter',
172
+ storageId: 'one',
173
+ cursor: 'bad',
174
+ }),
175
+ ).rejects.toThrow('Invalid cursor');
176
+ await expect(
177
+ handlers.listEntries({
178
+ adapterId: 'other',
179
+ storageId: 'one',
180
+ cursor,
181
+ }),
182
+ ).rejects.toThrow('Cursor does not match the requested storage');
183
+ await expect(
184
+ handlers.listEntries({
185
+ adapterId: 'adapter',
186
+ storageId: 'one',
187
+ cursor,
188
+ offset: 0,
189
+ }),
190
+ ).rejects.toThrow('either cursor or offset');
191
+ await expect(handlers.listEntries({ adapterId: 'adapter' })).rejects.toThrow(
192
+ 'Multiple storages matched',
193
+ );
194
+ await expect(handlers.listEntries({ storageId: 'one' })).rejects.toThrow(
195
+ 'Multiple storages matched',
196
+ );
197
+ });
198
+
199
+ it('returns a complete large value only when read-entry explicitly requests it', async () => {
200
+ const value = 'x'.repeat(100_000);
201
+ const { adapters, views } = createViews([
202
+ {
203
+ adapterId: 'adapter',
204
+ storageId: 'storage',
205
+ entries: {
206
+ large: { key: 'large', type: 'string', value },
207
+ other: { key: 'other', type: 'string', value: 'not read' },
208
+ },
209
+ },
210
+ ]);
211
+ const handlers = createStorageAgentHandlers(views);
212
+
213
+ await expect(handlers.readEntry({ key: 'large' })).resolves.toEqual({
214
+ adapterId: 'adapter',
215
+ storageId: 'storage',
216
+ entry: { key: 'large', type: 'string', value },
217
+ });
218
+ expect(adapters[0].storages[0].storage.get).toHaveBeenCalledTimes(1);
219
+ expect(adapters[0].storages[0].storage.get).toHaveBeenCalledWith('large');
220
+ });
221
+ });
@@ -109,9 +109,9 @@ describe('createMMKVStorageAdapter', () => {
109
109
  it('rejects MMKV v4 storages passed as an array', () => {
110
110
  const storage = createFakeMMKVV4();
111
111
 
112
- expect(() =>
113
- createMMKVStorageAdapter({ storages: [storage as any] }),
114
- ).toThrow(/MMKV arrays are not supported for v4 storages/);
112
+ expect(() => createMMKVStorageAdapter({ storages: [storage as any] })).toThrow(
113
+ /MMKV arrays are not supported for v4 storages/,
114
+ );
115
115
  });
116
116
 
117
117
  it('applies global blacklist patterns against storageId:key', async () => {
@@ -129,12 +129,8 @@ describe('createMMKVStorageAdapter', () => {
129
129
  });
130
130
 
131
131
  const views = createStorageViews([adapter]);
132
- const userView = views.find(
133
- (view) => view.target.storageId === 'user-storage',
134
- );
135
- const cacheView = views.find(
136
- (view) => view.target.storageId === 'cache-storage',
137
- );
132
+ const userView = views.find((view) => view.target.storageId === 'user-storage');
133
+ const cacheView = views.find((view) => view.target.storageId === 'cache-storage');
138
134
 
139
135
  expect(userView).toBeDefined();
140
136
  expect(cacheView).toBeDefined();
@@ -173,22 +169,20 @@ const createAmbiguousFakeMMKVV4 = ({
173
169
  const values = new Map<string, Stored>();
174
170
 
175
171
  return {
176
- set: vi.fn(
177
- (key: string, value: string | number | boolean | ArrayBuffer) => {
178
- if (typeof value === 'string') {
179
- values.set(key, { kind: 'string', value });
180
- } else if (typeof value === 'number') {
181
- values.set(key, { kind: 'number', value });
182
- } else if (typeof value === 'boolean') {
183
- values.set(key, { kind: 'boolean', value });
184
- } else {
185
- values.set(key, {
186
- kind: 'buffer',
187
- bytes: new Uint8Array(value),
188
- });
189
- }
190
- },
191
- ),
172
+ set: vi.fn((key: string, value: string | number | boolean | ArrayBuffer) => {
173
+ if (typeof value === 'string') {
174
+ values.set(key, { kind: 'string', value });
175
+ } else if (typeof value === 'number') {
176
+ values.set(key, { kind: 'number', value });
177
+ } else if (typeof value === 'boolean') {
178
+ values.set(key, { kind: 'boolean', value });
179
+ } else {
180
+ values.set(key, {
181
+ kind: 'buffer',
182
+ bytes: new Uint8Array(value),
183
+ });
184
+ }
185
+ }),
192
186
  getString: vi.fn((key: string) => {
193
187
  const stored = values.get(key);
194
188
  if (!stored) return undefined;
@@ -205,11 +199,7 @@ const createAmbiguousFakeMMKVV4 = ({
205
199
  getNumber: vi.fn((key: string) => {
206
200
  const stored = values.get(key);
207
201
  if (stored?.kind === 'number') return stored.value;
208
- if (
209
- lenientGetNumber &&
210
- stored?.kind === 'buffer' &&
211
- stored.bytes.byteLength === 8
212
- ) {
202
+ if (lenientGetNumber && stored?.kind === 'buffer' && stored.bytes.byteLength === 8) {
213
203
  return new DataView(stored.bytes.buffer).getFloat64(0, true);
214
204
  }
215
205
  return undefined;
@@ -5,6 +5,7 @@ export type AsyncStorageLike = {
5
5
  getItem: (key: string) => Promise<string | null>;
6
6
  setItem: (key: string, value: string) => Promise<void>;
7
7
  removeItem: (key: string) => Promise<void>;
8
+ clear: () => Promise<void>;
8
9
  };
9
10
 
10
11
  export type AsyncStorageInstanceConfig = {
@@ -28,15 +29,13 @@ type MultiStorageOptions = {
28
29
  adapterName?: string;
29
30
  };
30
31
 
31
- export type CreateAsyncStorageAdapterOptions =
32
- | SingleStorageOptions
33
- | MultiStorageOptions;
32
+ export type CreateAsyncStorageAdapterOptions = SingleStorageOptions | MultiStorageOptions;
34
33
 
35
34
  const toStorageNode = (
36
35
  storageId: string,
37
36
  config: AsyncStorageLike | AsyncStorageInstanceConfig,
38
37
  fallbackName?: string,
39
- fallbackBlacklist?: RegExp
38
+ fallbackBlacklist?: RegExp,
40
39
  ): StorageNode => {
41
40
  const resolved =
42
41
  'storage' in config
@@ -77,27 +76,27 @@ const toStorageNode = (
77
76
  await storage.setItem(entry.key, entry.value);
78
77
  },
79
78
  delete: (key) => storage.removeItem(key),
79
+ clear: () => storage.clear(),
80
80
  },
81
81
  };
82
82
  };
83
83
 
84
84
  export const createAsyncStorageAdapter = (
85
- options: CreateAsyncStorageAdapterOptions
85
+ options: CreateAsyncStorageAdapterOptions,
86
86
  ): StorageAdapter => {
87
- const { adapterId = 'async-storage', adapterName = 'AsyncStorage' } =
88
- options;
87
+ const { adapterId = 'async-storage', adapterName = 'AsyncStorage' } = options;
89
88
 
90
89
  const storageNodes: StorageNode[] =
91
90
  'storages' in options
92
91
  ? Object.entries(options.storages).map(([storageId, config]) =>
93
- toStorageNode(storageId, config)
92
+ toStorageNode(storageId, config),
94
93
  )
95
94
  : [
96
95
  toStorageNode(
97
96
  options.storageId ?? 'default',
98
97
  options.storage,
99
98
  options.storageName ?? 'Default Storage',
100
- options.blacklist
99
+ options.blacklist,
101
100
  ),
102
101
  ];
103
102
 
@@ -1,10 +1,7 @@
1
1
  export { createMMKVStorageAdapter } from './mmkv';
2
2
  export type { CreateMMKVStorageAdapterOptions } from './mmkv';
3
3
 
4
- export {
5
- createAsyncStorageAdapter,
6
- createExpoAsyncStorageAdapter,
7
- } from './async-storage';
4
+ export { createAsyncStorageAdapter, createExpoAsyncStorageAdapter } from './async-storage';
8
5
  export type {
9
6
  AsyncStorageInstanceConfig,
10
7
  AsyncStorageLike,
@@ -13,7 +10,4 @@ export type {
13
10
  } from './async-storage';
14
11
 
15
12
  export { createExpoSecureStorageAdapter } from './secure-storage';
16
- export type {
17
- CreateExpoSecureStorageAdapterOptions,
18
- SecureStorageLike,
19
- } from './secure-storage';
13
+ export type { CreateExpoSecureStorageAdapterOptions, SecureStorageLike } from './secure-storage';
@@ -1,10 +1,6 @@
1
1
  import type { MMKV as MMKVV3 } from 'react-native-mmkv-v3';
2
2
  import type { MMKV as MMKVV4 } from 'react-native-mmkv-v4';
3
- import type {
4
- StorageAdapter,
5
- StorageEntry,
6
- StorageNode,
7
- } from '../../shared/types';
3
+ import type { StorageAdapter, StorageEntry, StorageNode } from '../../shared/types';
8
4
  import { DEFAULT_SUPPORTED_TYPES } from '../../shared/types';
9
5
 
10
6
  type MMKV = MMKVV3 | MMKVV4;
@@ -17,6 +13,7 @@ type MMKVAdapter = {
17
13
  getBuffer: (key: string) => ArrayBuffer | undefined;
18
14
  delete: (key: string) => void;
19
15
  getAllKeys: () => string[];
16
+ clear: () => void;
20
17
  addOnValueChangedListener: (callback: (key: string) => void) => {
21
18
  remove: () => void;
22
19
  };
@@ -43,15 +40,14 @@ const normalizeStorages = (storages: MMKV[] | Record<string, MMKV>) => {
43
40
  );
44
41
  }
45
42
 
46
- return Object.fromEntries(
47
- (storages as MMKVV3[]).map((storage) => [storage['id'], storage]),
48
- );
43
+ return Object.fromEntries((storages as MMKVV3[]).map((storage) => [storage['id'], storage]));
49
44
  }
50
45
 
51
46
  return storages;
52
47
  };
53
48
 
54
49
  const getMMKVAdapter = (mmkv: MMKV): MMKVAdapter => {
50
+ const clear = () => mmkv.clearAll();
55
51
  if (isMMKVV4(mmkv)) {
56
52
  return {
57
53
  set: (key, value) => mmkv.set(key, value),
@@ -61,8 +57,8 @@ const getMMKVAdapter = (mmkv: MMKV): MMKVAdapter => {
61
57
  getBuffer: (key) => mmkv.getBuffer(key),
62
58
  delete: (key) => mmkv.remove(key),
63
59
  getAllKeys: () => mmkv.getAllKeys(),
64
- addOnValueChangedListener: (callback) =>
65
- mmkv.addOnValueChangedListener(callback),
60
+ clear,
61
+ addOnValueChangedListener: (callback) => mmkv.addOnValueChangedListener(callback),
66
62
  };
67
63
  }
68
64
 
@@ -74,8 +70,8 @@ const getMMKVAdapter = (mmkv: MMKV): MMKVAdapter => {
74
70
  getBuffer: (key) => mmkv.getBuffer(key) as ArrayBuffer | undefined,
75
71
  delete: (key) => mmkv.delete(key),
76
72
  getAllKeys: () => mmkv.getAllKeys(),
77
- addOnValueChangedListener: (callback) =>
78
- mmkv.addOnValueChangedListener(callback),
73
+ clear,
74
+ addOnValueChangedListener: (callback) => mmkv.addOnValueChangedListener(callback),
79
75
  };
80
76
  };
81
77
 
@@ -98,10 +94,7 @@ const getMMKVAdapter = (mmkv: MMKV): MMKVAdapter => {
98
94
  // those keys, so the chain falls through.
99
95
  // 4. Empty string — intentional `setString(key, "")` lands here once
100
96
  // we've ruled out a non-empty buffer payload at the same key.
101
- const getEntry = (
102
- adapter: MMKVAdapter,
103
- key: string,
104
- ): StorageEntry | undefined => {
97
+ const getEntry = (adapter: MMKVAdapter, key: string): StorageEntry | undefined => {
105
98
  const stringValue = adapter.getString(key);
106
99
  if (stringValue !== undefined && stringValue.length > 0) {
107
100
  return { key, type: 'string', value: stringValue };
@@ -141,10 +134,7 @@ const setEntry = (adapter: MMKVAdapter, entry: StorageEntry) => {
141
134
  }
142
135
  };
143
136
 
144
- const getStorageBlacklist = (
145
- config: MMKVBlacklistConfig | undefined,
146
- storageId: string,
147
- ) => {
137
+ const getStorageBlacklist = (config: MMKVBlacklistConfig | undefined, storageId: string) => {
148
138
  if (!config) {
149
139
  return undefined;
150
140
  }
@@ -176,10 +166,7 @@ export const createMMKVStorageAdapter = ({
176
166
  storages,
177
167
  blacklist,
178
168
  }: CreateMMKVStorageAdapterOptions): StorageAdapter => {
179
- const normalizedStorages = normalizeStorages(storages) as Record<
180
- string,
181
- MMKV
182
- >;
169
+ const normalizedStorages = normalizeStorages(storages) as Record<string, MMKV>;
183
170
 
184
171
  const storageNodes: StorageNode[] = Object.entries(normalizedStorages).map(
185
172
  ([storageId, storage]) => {
@@ -199,6 +186,7 @@ export const createMMKVStorageAdapter = ({
199
186
  get: (key) => getEntry(mmkv, key),
200
187
  set: (entry) => setEntry(mmkv, entry),
201
188
  delete: (key) => mmkv.delete(key),
189
+ clear: mmkv.clear,
202
190
  subscribe: (callback) => mmkv.addOnValueChangedListener(callback),
203
191
  },
204
192
  };
@@ -65,6 +65,10 @@ export const createExpoSecureStorageAdapter = ({
65
65
  await storage.setItemAsync(entry.key, entry.value);
66
66
  },
67
67
  delete: (key) => storage.deleteItemAsync(key),
68
+ clear: async () => {
69
+ const storageKeys = await resolveKeys(keys);
70
+ await Promise.all(storageKeys.map((key) => storage.deleteItemAsync(key)));
71
+ },
68
72
  },
69
73
  };
70
74