@rozenite/storage-plugin 1.8.1 → 1.10.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 (42) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +23 -0
  3. package/dist/devtools/assets/panel-Bm-SWF7d.js +33 -0
  4. package/dist/devtools/assets/panel-DIqI4WSp.css +1 -0
  5. package/dist/devtools/panel.html +2 -2
  6. package/dist/react-native/chunks/index.require.cjs +1 -1
  7. package/dist/react-native/chunks/index.require.js +43 -57
  8. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +1 -1
  9. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +253 -191
  10. package/dist/react-native/index.d.ts +29 -6
  11. package/dist/rozenite.json +1 -1
  12. package/dist/sdk/index.d.ts +1 -0
  13. package/package.json +9 -6
  14. package/src/react-native/__tests__/import.test.ts +182 -0
  15. package/src/react-native/adapters/__tests__/mmkv.test.ts +436 -0
  16. package/src/react-native/adapters/mmkv.ts +65 -39
  17. package/src/react-native/import.ts +67 -0
  18. package/src/react-native/storage-view.ts +16 -8
  19. package/src/react-native/useRozeniteStoragePlugin.ts +61 -36
  20. package/src/shared/__tests__/snapshot.test.ts +361 -0
  21. package/src/shared/messaging.ts +25 -1
  22. package/src/shared/snapshot.ts +276 -0
  23. package/src/shared/types.ts +1 -0
  24. package/src/ui/__tests__/binary-value-editor-state.test.ts +199 -0
  25. package/src/ui/__tests__/binary.test.ts +251 -0
  26. package/src/ui/__tests__/type-conversion.test.ts +123 -0
  27. package/src/ui/add-entry-dialog.tsx +83 -173
  28. package/src/ui/binary-value-editor-state.ts +125 -0
  29. package/src/ui/binary-value-editor.tsx +168 -0
  30. package/src/ui/binary.ts +123 -0
  31. package/src/ui/edit-entry-dialog.tsx +64 -161
  32. package/src/ui/editable-table.tsx +12 -10
  33. package/src/ui/editor-switcher.tsx +62 -0
  34. package/src/ui/entry-detail-dialog.tsx +14 -6
  35. package/src/ui/import-dialog.tsx +261 -0
  36. package/src/ui/panel.tsx +257 -57
  37. package/src/ui/type-conversion.ts +105 -0
  38. package/src/ui/typed-value-editor.tsx +96 -0
  39. package/src/ui/utils.ts +30 -0
  40. package/dist/devtools/assets/panel-DMhXYHH4.css +0 -1
  41. package/dist/devtools/assets/panel-eGOuOVos.js +0 -22
  42. package/src/react-native/is-garbled.ts +0 -17
@@ -24,13 +24,13 @@ export declare type AsyncStorageLike = {
24
24
  removeItem: (key: string) => Promise<void>;
25
25
  };
26
26
 
27
- export declare let createAsyncStorageAdapter: createAsyncStorageAdapter_2;
27
+ export declare let createAsyncStorageAdapter: typeof createAsyncStorageAdapter_2;
28
28
 
29
29
  declare const createAsyncStorageAdapter_2: (options: CreateAsyncStorageAdapterOptions) => StorageAdapter;
30
30
 
31
31
  export declare type CreateAsyncStorageAdapterOptions = SingleStorageOptions | MultiStorageOptions;
32
32
 
33
- export declare let createExpoAsyncStorageAdapter: createExpoAsyncStorageAdapter_2;
33
+ export declare let createExpoAsyncStorageAdapter: typeof createExpoAsyncStorageAdapter_2;
34
34
 
35
35
  /**
36
36
  * @deprecated Use createAsyncStorageAdapter instead.
@@ -39,7 +39,7 @@ declare const createExpoAsyncStorageAdapter_2: (options: CreateAsyncStorageAdapt
39
39
 
40
40
  export declare type CreateExpoAsyncStorageAdapterOptions = CreateAsyncStorageAdapterOptions;
41
41
 
42
- export declare let createExpoSecureStorageAdapter: createExpoSecureStorageAdapter_2;
42
+ export declare let createExpoSecureStorageAdapter: typeof createExpoSecureStorageAdapter_2;
43
43
 
44
44
  declare const createExpoSecureStorageAdapter_2: ({ storage, keys, adapterId, adapterName, storageId, storageName, blacklist, }: CreateExpoSecureStorageAdapterOptions) => StorageAdapter;
45
45
 
@@ -53,7 +53,7 @@ export declare type CreateExpoSecureStorageAdapterOptions = {
53
53
  blacklist?: RegExp;
54
54
  };
55
55
 
56
- export declare let createMMKVStorageAdapter: createMMKVStorageAdapter_2;
56
+ export declare let createMMKVStorageAdapter: typeof createMMKVStorageAdapter_2;
57
57
 
58
58
  declare const createMMKVStorageAdapter_2: ({ adapterId, adapterName, storages, blacklist, }: CreateMMKVStorageAdapterOptions) => StorageAdapter;
59
59
 
@@ -86,6 +86,11 @@ export declare type SecureStorageLike = {
86
86
  deleteItemAsync: (key: string) => Promise<void>;
87
87
  };
88
88
 
89
+ declare type SerializedBlacklist = {
90
+ source: string;
91
+ flags: string;
92
+ };
93
+
89
94
  declare type SingleStorageOptions = {
90
95
  storage: AsyncStorageLike;
91
96
  adapterId?: string;
@@ -133,7 +138,7 @@ export declare type StorageEntryType = StorageEntry['type'];
133
138
 
134
139
  export declare type StorageEntryValue = StorageEntry['value'];
135
140
 
136
- declare type StorageEvent_2 = StorageSnapshotEvent | StorageSetEntryEvent | StorageDeleteEntryEvent | StorageGetSnapshotEvent;
141
+ declare type StorageEvent_2 = StorageSnapshotEvent | StorageSetEntryEvent | StorageDeleteEntryEvent | StorageGetSnapshotEvent | StorageImportEntriesEvent | StorageImportResultEvent;
137
142
 
138
143
  declare type StorageEventMap = {
139
144
  [K in StorageEvent_2['type']]: Extract<StorageEvent_2, {
@@ -146,12 +151,29 @@ declare type StorageGetSnapshotEvent = {
146
151
  target: StorageTarget | 'all';
147
152
  };
148
153
 
154
+ declare type StorageImportEntriesEvent = {
155
+ type: 'import-entries';
156
+ target: StorageTarget;
157
+ entries: StorageEntry[];
158
+ };
159
+
160
+ declare type StorageImportResultEvent = {
161
+ type: 'import-result';
162
+ target: StorageTarget;
163
+ ok: boolean;
164
+ written: number;
165
+ total: number;
166
+ failedKey?: string;
167
+ error?: string;
168
+ };
169
+
149
170
  export declare type StorageNode = {
150
171
  id: string;
151
172
  name: string;
152
173
  storage: SyncStorage | AsyncStorage;
153
174
  capabilities: StorageCapabilities;
154
175
  blacklist?: RegExp;
176
+ shouldFilterKey?: (key: string) => boolean;
155
177
  };
156
178
 
157
179
  declare type StorageSetEntryEvent = {
@@ -166,6 +188,7 @@ declare type StorageSnapshotEvent = {
166
188
  adapterName: string;
167
189
  storageName: string;
168
190
  capabilities: StorageCapabilities;
191
+ blacklist?: SerializedBlacklist;
169
192
  entries: StorageEntry[];
170
193
  };
171
194
 
@@ -187,7 +210,7 @@ export declare type SyncStorage = {
187
210
  subscribe?: (callback: (key: string) => void) => StorageSubscription;
188
211
  };
189
212
 
190
- export declare let useRozeniteStoragePlugin: useRozeniteStoragePlugin_2;
213
+ export declare let useRozeniteStoragePlugin: typeof useRozeniteStoragePlugin_2;
191
214
 
192
215
  declare const useRozeniteStoragePlugin_2: ({ storages, }: RozeniteStoragePluginOptions) => RozeniteDevToolsClient<StorageEventMap> | null;
193
216
 
@@ -1 +1 @@
1
- {"name":"@rozenite/storage-plugin","version":"1.8.1","description":"Generic Storage Inspector for Rozenite.","panels":[{"name":"Storage","source":"/devtools/panel.html"}]}
1
+ {"name":"@rozenite/storage-plugin","version":"1.10.0","description":"Generic Storage Inspector for Rozenite.","panels":[{"name":"Storage","source":"/devtools/panel.html"}]}
@@ -99,6 +99,7 @@ export declare type StorageNode = {
99
99
  storage: SyncStorage | AsyncStorage;
100
100
  capabilities: StorageCapabilities;
101
101
  blacklist?: RegExp;
102
+ shouldFilterKey?: (key: string) => boolean;
102
103
  };
103
104
 
104
105
  export declare type StorageReadEntryArgs = StorageSelection & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rozenite/storage-plugin",
3
- "version": "1.8.1",
3
+ "version": "1.10.0",
4
4
  "description": "Generic Storage Inspector for Rozenite.",
5
5
  "type": "module",
6
6
  "main": "./dist/react-native/index.cjs",
@@ -15,11 +15,14 @@
15
15
  "url": "https://github.com/callstackincubator/rozenite.git"
16
16
  },
17
17
  "dependencies": {
18
- "@rozenite/agent-shared": "1.8.1",
19
- "@rozenite/agent-bridge": "1.8.1",
20
- "@rozenite/plugin-bridge": "1.8.1"
18
+ "@rozenite/agent-shared": "1.10.0",
19
+ "@rozenite/agent-bridge": "1.10.0",
20
+ "@rozenite/plugin-bridge": "1.10.0"
21
21
  },
22
22
  "devDependencies": {
23
+ "@codemirror/commands": "^6.10.3",
24
+ "@codemirror/state": "^6.6.0",
25
+ "@codemirror/view": "^6.40.0",
23
26
  "@tanstack/react-table": "^8.21.3",
24
27
  "@types/react": "~19.2.2",
25
28
  "autoprefixer": "^10.4.21",
@@ -38,8 +41,8 @@
38
41
  "tailwindcss-animate": "^1.0.7",
39
42
  "typescript": "~5.9.3",
40
43
  "vite": "^7.3.1",
41
- "@rozenite/vite-plugin": "1.8.1",
42
- "rozenite": "1.8.1"
44
+ "@rozenite/vite-plugin": "1.10.0",
45
+ "rozenite": "1.10.0"
43
46
  },
44
47
  "peerDependencies": {
45
48
  "@react-native-async-storage/async-storage": "*",
@@ -0,0 +1,182 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { handleImportEntries, type ImportEmittedEvent } from '../import';
3
+ import type { StorageView } from '../storage-view';
4
+ import { getStorageViewId } from '../../shared/types';
5
+ import type { StorageEntry, StorageTarget } from '../../shared/types';
6
+ import type { StorageImportEntriesEvent } from '../../shared/messaging';
7
+
8
+ const target: StorageTarget = { adapterId: 'mmkv', storageId: 'user' };
9
+
10
+ const buildView = (overrides?: Partial<StorageView>): StorageView => {
11
+ const unexpected = (method: string) =>
12
+ vi.fn(() => {
13
+ throw new Error(`Unexpected call to ${method}`);
14
+ });
15
+
16
+ return {
17
+ id: getStorageViewId(target),
18
+ target,
19
+ adapterName: 'MMKV',
20
+ storageName: 'user',
21
+ capabilities: {
22
+ supportedTypes: ['string', 'number', 'boolean', 'buffer'],
23
+ },
24
+ get: unexpected('get'),
25
+ set: vi.fn(async () => {}),
26
+ delete: unexpected('delete'),
27
+ getAllKeys: unexpected('getAllKeys'),
28
+ getAllEntries: unexpected('getAllEntries'),
29
+ watch: unexpected('watch'),
30
+ ...overrides,
31
+ };
32
+ };
33
+
34
+ const buildEntries = (count: number): StorageEntry[] =>
35
+ Array.from({ length: count }, (_, index) => ({
36
+ key: `k${index}`,
37
+ type: 'string' as const,
38
+ value: `v${index}`,
39
+ }));
40
+
41
+ describe('handleImportEntries', () => {
42
+ it('emits a set-entry echo per write followed by a success import-result', async () => {
43
+ const view = buildView();
44
+ const entries = buildEntries(5);
45
+ const event: StorageImportEntriesEvent = {
46
+ type: 'import-entries',
47
+ target,
48
+ entries,
49
+ };
50
+ const emitted: ImportEmittedEvent[] = [];
51
+
52
+ await handleImportEntries([view], event, (out) => emitted.push(out));
53
+
54
+ expect(view.set).toHaveBeenCalledTimes(5);
55
+
56
+ expect(emitted).toHaveLength(6);
57
+ expect(emitted.slice(0, 5)).toEqual(
58
+ entries.map((entry) => ({
59
+ type: 'set-entry',
60
+ target,
61
+ entry,
62
+ })),
63
+ );
64
+ expect(emitted[5]).toEqual({
65
+ type: 'import-result',
66
+ target,
67
+ ok: true,
68
+ written: 5,
69
+ total: 5,
70
+ });
71
+ });
72
+
73
+ it('stops at the first write that throws and reports the failed key', async () => {
74
+ const failingKey = 'k2';
75
+ const view = buildView({
76
+ set: vi.fn(async (entry: StorageEntry) => {
77
+ if (entry.key === failingKey) {
78
+ throw new Error('boom');
79
+ }
80
+ }),
81
+ });
82
+ const entries = buildEntries(5);
83
+ const event: StorageImportEntriesEvent = {
84
+ type: 'import-entries',
85
+ target,
86
+ entries,
87
+ };
88
+ const emitted: ImportEmittedEvent[] = [];
89
+
90
+ await handleImportEntries([view], event, (out) => emitted.push(out));
91
+
92
+ // 2 set-entry echoes (k0, k1), then the failure result. No echoes for k2+.
93
+ expect(view.set).toHaveBeenCalledTimes(3);
94
+ expect(emitted).toHaveLength(3);
95
+ expect(emitted[0]).toMatchObject({ type: 'set-entry', entry: entries[0] });
96
+ expect(emitted[1]).toMatchObject({ type: 'set-entry', entry: entries[1] });
97
+ expect(emitted[2]).toEqual({
98
+ type: 'import-result',
99
+ target,
100
+ ok: false,
101
+ written: 2,
102
+ total: 5,
103
+ failedKey: failingKey,
104
+ error: 'boom',
105
+ });
106
+ });
107
+
108
+ it('coerces non-Error throws to a string error message', async () => {
109
+ const view = buildView({
110
+ set: vi.fn(async () => {
111
+ throw 'plain string failure';
112
+ }),
113
+ });
114
+ const event: StorageImportEntriesEvent = {
115
+ type: 'import-entries',
116
+ target,
117
+ entries: buildEntries(1),
118
+ };
119
+ const emitted: ImportEmittedEvent[] = [];
120
+
121
+ await handleImportEntries([view], event, (out) => emitted.push(out));
122
+
123
+ expect(emitted).toHaveLength(1);
124
+ expect(emitted[0]).toMatchObject({
125
+ type: 'import-result',
126
+ ok: false,
127
+ error: 'plain string failure',
128
+ });
129
+ });
130
+
131
+ it('emits an unknown-target import-result without calling set', async () => {
132
+ const view = buildView();
133
+ const unknownTarget: StorageTarget = {
134
+ adapterId: 'async-storage',
135
+ storageId: 'default',
136
+ };
137
+ const entries = buildEntries(3);
138
+ const event: StorageImportEntriesEvent = {
139
+ type: 'import-entries',
140
+ target: unknownTarget,
141
+ entries,
142
+ };
143
+ const emitted: ImportEmittedEvent[] = [];
144
+
145
+ await handleImportEntries([view], event, (out) => emitted.push(out));
146
+
147
+ expect(view.set).not.toHaveBeenCalled();
148
+ expect(emitted).toEqual([
149
+ {
150
+ type: 'import-result',
151
+ target: unknownTarget,
152
+ ok: false,
153
+ written: 0,
154
+ total: 3,
155
+ error: 'Target storage not found',
156
+ },
157
+ ]);
158
+ });
159
+
160
+ it('handles empty entries as a successful no-op', async () => {
161
+ const view = buildView();
162
+ const event: StorageImportEntriesEvent = {
163
+ type: 'import-entries',
164
+ target,
165
+ entries: [],
166
+ };
167
+ const emitted: ImportEmittedEvent[] = [];
168
+
169
+ await handleImportEntries([view], event, (out) => emitted.push(out));
170
+
171
+ expect(view.set).not.toHaveBeenCalled();
172
+ expect(emitted).toEqual([
173
+ {
174
+ type: 'import-result',
175
+ target,
176
+ ok: true,
177
+ written: 0,
178
+ total: 0,
179
+ },
180
+ ]);
181
+ });
182
+ });