@rozenite/storage-plugin 2.0.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.
- package/CHANGELOG.md +18 -0
- package/dist/devtools/assets/panel-BlDRXgVW.js +32 -0
- package/dist/devtools/assets/panel-DBweZnW5.css +1 -0
- package/dist/devtools/panel.html +2 -2
- package/dist/react-native/chunks/index.require.js +1 -3
- package/dist/react-native/chunks/secure-storage.require.js +2 -6
- package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +1 -1
- package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +266 -295
- package/dist/react-native/index.d.ts +14 -2
- package/dist/react-native/index.js +2 -10
- package/dist/rozenite.json +1 -1
- package/package.json +29 -29
- package/react-native.ts +11 -36
- package/rozenite.config.ts +2 -6
- package/sdk.ts +1 -5
- package/src/react-native/__tests__/entry-preview-pagination.test.ts +12 -40
- package/src/react-native/__tests__/export-snapshot.test.ts +2 -6
- package/src/react-native/__tests__/full-entry-request.test.ts +5 -17
- package/src/react-native/__tests__/import.test.ts +9 -8
- package/src/react-native/__tests__/storage-discovery.test.ts +7 -18
- package/src/react-native/__tests__/stress-verification.test.ts +5 -18
- package/src/react-native/__tests__/use-storage-agent-tools.test.ts +25 -35
- package/src/react-native/adapters/__tests__/mmkv.test.ts +20 -30
- package/src/react-native/adapters/async-storage.ts +2 -5
- package/src/react-native/adapters/index.ts +2 -8
- package/src/react-native/adapters/mmkv.ts +7 -24
- package/src/react-native/adapters/secure-storage.ts +2 -6
- package/src/react-native/entry-preview-pagination.ts +11 -27
- package/src/react-native/export-snapshot.ts +3 -9
- package/src/react-native/full-entry-request.ts +3 -8
- package/src/react-native/import.ts +10 -21
- package/src/react-native/storage-discovery.ts +2 -6
- package/src/react-native/storage-view.ts +2 -6
- package/src/react-native/useRozeniteStoragePlugin.ts +107 -119
- package/src/react-native/useStorageAgentTools.ts +8 -24
- package/src/shared/__tests__/entry-preview.test.ts +9 -27
- package/src/shared/__tests__/snapshot.test.ts +10 -32
- package/src/shared/agent-tools.ts +9 -30
- package/src/shared/entry-preview.ts +3 -8
- package/src/shared/messaging.ts +15 -11
- package/src/shared/snapshot.ts +4 -17
- package/src/shared/types.ts +2 -4
- package/src/ui/__tests__/binary-value-editor-state.test.ts +6 -16
- package/src/ui/__tests__/binary.test.ts +13 -33
- package/src/ui/__tests__/large-value-viewer.test.tsx +4 -13
- package/src/ui/__tests__/panel.test.tsx +41 -68
- package/src/ui/__tests__/storage-groups.test.ts +1 -4
- package/src/ui/__tests__/type-conversion.test.ts +8 -24
- package/src/ui/__tests__/use-storage-entries.test.tsx +18 -34
- package/src/ui/add-entry-dialog.tsx +4 -15
- package/src/ui/binary-value-editor-state.ts +6 -24
- package/src/ui/binary-value-editor.tsx +5 -17
- package/src/ui/binary.ts +6 -19
- package/src/ui/edit-entry-dialog.tsx +3 -11
- package/src/ui/editor-switcher.tsx +2 -11
- package/src/ui/entry-detail-dialog.tsx +5 -14
- package/src/ui/format-value.tsx +2 -9
- package/src/ui/import-dialog.tsx +7 -19
- package/src/ui/large-value-viewer-state.ts +2 -9
- package/src/ui/large-value-viewer.tsx +3 -9
- package/src/ui/panel.tsx +65 -153
- package/src/ui/query-client.tsx +4 -15
- package/src/ui/type-conversion.ts +2 -5
- package/src/ui/typed-value-editor.tsx +1 -5
- package/src/ui/use-storage-entries.ts +6 -21
- package/src/ui/utils.ts +1 -4
- package/dist/devtools/assets/panel-B6Wp0eie.css +0 -1
- package/dist/devtools/assets/panel-D7MFc4HG.js +0 -32
|
@@ -17,35 +17,31 @@ const createViews = (
|
|
|
17
17
|
entries: Record<string, StorageEntry>;
|
|
18
18
|
}>,
|
|
19
19
|
) => {
|
|
20
|
-
const adapters = storages.map(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
clear: vi.fn(),
|
|
36
|
-
},
|
|
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(),
|
|
37
35
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
}));
|
|
41
39
|
|
|
42
40
|
return { adapters, views: createStorageViews(adapters) };
|
|
43
41
|
};
|
|
44
42
|
|
|
45
43
|
const entries = (...keys: string[]): Record<string, StorageEntry> =>
|
|
46
|
-
Object.fromEntries(
|
|
47
|
-
keys.map((key) => [key, { key, type: 'string', value: `value:${key}` }]),
|
|
48
|
-
);
|
|
44
|
+
Object.fromEntries(keys.map((key) => [key, { key, type: 'string', value: `value:${key}` }]));
|
|
49
45
|
|
|
50
46
|
describe('storage Agent tool handlers', () => {
|
|
51
47
|
it('lists storage descriptors without reading keys or values', async () => {
|
|
@@ -54,9 +50,7 @@ describe('storage Agent tool handlers', () => {
|
|
|
54
50
|
{ adapterId: 'two', storageId: 'second', entries: entries('b') },
|
|
55
51
|
]);
|
|
56
52
|
|
|
57
|
-
await expect(
|
|
58
|
-
createStorageAgentHandlers(views).listStorages(),
|
|
59
|
-
).resolves.toEqual({
|
|
53
|
+
await expect(createStorageAgentHandlers(views).listStorages()).resolves.toEqual({
|
|
60
54
|
storages: [
|
|
61
55
|
{
|
|
62
56
|
adapterId: 'one',
|
|
@@ -111,9 +105,7 @@ describe('storage Agent tool handlers', () => {
|
|
|
111
105
|
});
|
|
112
106
|
expect(first.items.every((item) => !('value' in item))).toBe(true);
|
|
113
107
|
|
|
114
|
-
await expect(
|
|
115
|
-
handlers.listEntries({ cursor: first.page.nextCursor }),
|
|
116
|
-
).resolves.toEqual({
|
|
108
|
+
await expect(handlers.listEntries({ cursor: first.page.nextCursor })).resolves.toEqual({
|
|
117
109
|
adapterId: 'adapter',
|
|
118
110
|
storageId: 'storage',
|
|
119
111
|
total: 3,
|
|
@@ -129,9 +121,7 @@ describe('storage Agent tool handlers', () => {
|
|
|
129
121
|
adapterId: 'adapter',
|
|
130
122
|
storageId: 'storage',
|
|
131
123
|
entries: entries(
|
|
132
|
-
...Array.from({ length: MAX_PAGE_LIMIT + 1 }, (_, index) =>
|
|
133
|
-
String(index),
|
|
134
|
-
),
|
|
124
|
+
...Array.from({ length: MAX_PAGE_LIMIT + 1 }, (_, index) => String(index)),
|
|
135
125
|
),
|
|
136
126
|
},
|
|
137
127
|
]);
|
|
@@ -198,9 +188,9 @@ describe('storage Agent tool handlers', () => {
|
|
|
198
188
|
offset: 0,
|
|
199
189
|
}),
|
|
200
190
|
).rejects.toThrow('either cursor or offset');
|
|
201
|
-
await expect(
|
|
202
|
-
|
|
203
|
-
)
|
|
191
|
+
await expect(handlers.listEntries({ adapterId: 'adapter' })).rejects.toThrow(
|
|
192
|
+
'Multiple storages matched',
|
|
193
|
+
);
|
|
204
194
|
await expect(handlers.listEntries({ storageId: 'one' })).rejects.toThrow(
|
|
205
195
|
'Multiple storages matched',
|
|
206
196
|
);
|
|
@@ -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
|
-
|
|
114
|
-
)
|
|
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
|
-
|
|
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
|
-
(
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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;
|
|
@@ -29,9 +29,7 @@ type MultiStorageOptions = {
|
|
|
29
29
|
adapterName?: string;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
export type CreateAsyncStorageAdapterOptions =
|
|
33
|
-
| SingleStorageOptions
|
|
34
|
-
| MultiStorageOptions;
|
|
32
|
+
export type CreateAsyncStorageAdapterOptions = SingleStorageOptions | MultiStorageOptions;
|
|
35
33
|
|
|
36
34
|
const toStorageNode = (
|
|
37
35
|
storageId: string,
|
|
@@ -113,5 +111,4 @@ export const createAsyncStorageAdapter = (
|
|
|
113
111
|
* @deprecated Use createAsyncStorageAdapter instead.
|
|
114
112
|
*/
|
|
115
113
|
export const createExpoAsyncStorageAdapter = createAsyncStorageAdapter;
|
|
116
|
-
export type CreateExpoAsyncStorageAdapterOptions =
|
|
117
|
-
CreateAsyncStorageAdapterOptions;
|
|
114
|
+
export type CreateExpoAsyncStorageAdapterOptions = CreateAsyncStorageAdapterOptions;
|
|
@@ -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;
|
|
@@ -44,9 +40,7 @@ const normalizeStorages = (storages: MMKV[] | Record<string, MMKV>) => {
|
|
|
44
40
|
);
|
|
45
41
|
}
|
|
46
42
|
|
|
47
|
-
return Object.fromEntries(
|
|
48
|
-
(storages as MMKVV3[]).map((storage) => [storage['id'], storage]),
|
|
49
|
-
);
|
|
43
|
+
return Object.fromEntries((storages as MMKVV3[]).map((storage) => [storage['id'], storage]));
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
return storages;
|
|
@@ -64,8 +58,7 @@ const getMMKVAdapter = (mmkv: MMKV): MMKVAdapter => {
|
|
|
64
58
|
delete: (key) => mmkv.remove(key),
|
|
65
59
|
getAllKeys: () => mmkv.getAllKeys(),
|
|
66
60
|
clear,
|
|
67
|
-
addOnValueChangedListener: (callback) =>
|
|
68
|
-
mmkv.addOnValueChangedListener(callback),
|
|
61
|
+
addOnValueChangedListener: (callback) => mmkv.addOnValueChangedListener(callback),
|
|
69
62
|
};
|
|
70
63
|
}
|
|
71
64
|
|
|
@@ -78,8 +71,7 @@ const getMMKVAdapter = (mmkv: MMKV): MMKVAdapter => {
|
|
|
78
71
|
delete: (key) => mmkv.delete(key),
|
|
79
72
|
getAllKeys: () => mmkv.getAllKeys(),
|
|
80
73
|
clear,
|
|
81
|
-
addOnValueChangedListener: (callback) =>
|
|
82
|
-
mmkv.addOnValueChangedListener(callback),
|
|
74
|
+
addOnValueChangedListener: (callback) => mmkv.addOnValueChangedListener(callback),
|
|
83
75
|
};
|
|
84
76
|
};
|
|
85
77
|
|
|
@@ -102,10 +94,7 @@ const getMMKVAdapter = (mmkv: MMKV): MMKVAdapter => {
|
|
|
102
94
|
// those keys, so the chain falls through.
|
|
103
95
|
// 4. Empty string — intentional `setString(key, "")` lands here once
|
|
104
96
|
// we've ruled out a non-empty buffer payload at the same key.
|
|
105
|
-
const getEntry = (
|
|
106
|
-
adapter: MMKVAdapter,
|
|
107
|
-
key: string,
|
|
108
|
-
): StorageEntry | undefined => {
|
|
97
|
+
const getEntry = (adapter: MMKVAdapter, key: string): StorageEntry | undefined => {
|
|
109
98
|
const stringValue = adapter.getString(key);
|
|
110
99
|
if (stringValue !== undefined && stringValue.length > 0) {
|
|
111
100
|
return { key, type: 'string', value: stringValue };
|
|
@@ -145,10 +134,7 @@ const setEntry = (adapter: MMKVAdapter, entry: StorageEntry) => {
|
|
|
145
134
|
}
|
|
146
135
|
};
|
|
147
136
|
|
|
148
|
-
const getStorageBlacklist = (
|
|
149
|
-
config: MMKVBlacklistConfig | undefined,
|
|
150
|
-
storageId: string,
|
|
151
|
-
) => {
|
|
137
|
+
const getStorageBlacklist = (config: MMKVBlacklistConfig | undefined, storageId: string) => {
|
|
152
138
|
if (!config) {
|
|
153
139
|
return undefined;
|
|
154
140
|
}
|
|
@@ -180,10 +166,7 @@ export const createMMKVStorageAdapter = ({
|
|
|
180
166
|
storages,
|
|
181
167
|
blacklist,
|
|
182
168
|
}: CreateMMKVStorageAdapterOptions): StorageAdapter => {
|
|
183
|
-
const normalizedStorages = normalizeStorages(storages) as Record<
|
|
184
|
-
string,
|
|
185
|
-
MMKV
|
|
186
|
-
>;
|
|
169
|
+
const normalizedStorages = normalizeStorages(storages) as Record<string, MMKV>;
|
|
187
170
|
|
|
188
171
|
const storageNodes: StorageNode[] = Object.entries(normalizedStorages).map(
|
|
189
172
|
([storageId, storage]) => {
|
|
@@ -59,9 +59,7 @@ export const createExpoSecureStorageAdapter = ({
|
|
|
59
59
|
},
|
|
60
60
|
set: async (entry) => {
|
|
61
61
|
if (entry.type !== 'string') {
|
|
62
|
-
throw new Error(
|
|
63
|
-
'Expo SecureStore adapter supports only string values.',
|
|
64
|
-
);
|
|
62
|
+
throw new Error('Expo SecureStore adapter supports only string values.');
|
|
65
63
|
}
|
|
66
64
|
|
|
67
65
|
await storage.setItemAsync(entry.key, entry.value);
|
|
@@ -69,9 +67,7 @@ export const createExpoSecureStorageAdapter = ({
|
|
|
69
67
|
delete: (key) => storage.deleteItemAsync(key),
|
|
70
68
|
clear: async () => {
|
|
71
69
|
const storageKeys = await resolveKeys(keys);
|
|
72
|
-
await Promise.all(
|
|
73
|
-
storageKeys.map((key) => storage.deleteItemAsync(key)),
|
|
74
|
-
);
|
|
70
|
+
await Promise.all(storageKeys.map((key) => storage.deleteItemAsync(key)));
|
|
75
71
|
},
|
|
76
72
|
},
|
|
77
73
|
};
|
|
@@ -10,9 +10,7 @@ import type { StorageView } from './storage-view';
|
|
|
10
10
|
export const MAX_ENTRY_PREVIEW_PAGE_SIZE = 100;
|
|
11
11
|
export const ENTRY_PREVIEW_READ_CONCURRENCY = 4;
|
|
12
12
|
|
|
13
|
-
type PaginationResult =
|
|
14
|
-
| StorageListEntryPreviewsResponseEvent
|
|
15
|
-
| StorageRequestErrorEvent;
|
|
13
|
+
type PaginationResult = StorageListEntryPreviewsResponseEvent | StorageRequestErrorEvent;
|
|
16
14
|
|
|
17
15
|
type Cursor = {
|
|
18
16
|
version: 1;
|
|
@@ -49,8 +47,7 @@ const isTarget = (value: unknown): value is StorageTarget =>
|
|
|
49
47
|
typeof (value as { storageId?: unknown }).storageId === 'string' &&
|
|
50
48
|
(value as { storageId: string }).storageId.trim().length > 0;
|
|
51
49
|
|
|
52
|
-
const normalizeSearch = (search: string | undefined) =>
|
|
53
|
-
(search ?? '').trim().toLowerCase();
|
|
50
|
+
const normalizeSearch = (search: string | undefined) => (search ?? '').trim().toLowerCase();
|
|
54
51
|
|
|
55
52
|
const validateRequest = (payload: unknown): ValidRequest | undefined => {
|
|
56
53
|
if (
|
|
@@ -72,8 +69,7 @@ const validateRequest = (payload: unknown): ValidRequest | undefined => {
|
|
|
72
69
|
return undefined;
|
|
73
70
|
}
|
|
74
71
|
|
|
75
|
-
const keySortDirection = (payload as { keySortDirection?: unknown })
|
|
76
|
-
.keySortDirection;
|
|
72
|
+
const keySortDirection = (payload as { keySortDirection?: unknown }).keySortDirection;
|
|
77
73
|
if (
|
|
78
74
|
keySortDirection !== undefined &&
|
|
79
75
|
keySortDirection !== 'ascending' &&
|
|
@@ -93,8 +89,7 @@ const validateRequest = (payload: unknown): ValidRequest | undefined => {
|
|
|
93
89
|
|
|
94
90
|
// Cursor contents are intentionally private to the device protocol. Callers only
|
|
95
91
|
// receive and return this opaque token; its representation may change freely.
|
|
96
|
-
const encodeCursor = (cursor: Cursor) =>
|
|
97
|
-
btoa(encodeURIComponent(JSON.stringify(cursor)));
|
|
92
|
+
const encodeCursor = (cursor: Cursor) => btoa(encodeURIComponent(JSON.stringify(cursor)));
|
|
98
93
|
|
|
99
94
|
const decodeCursor = (value: string): Cursor | undefined => {
|
|
100
95
|
try {
|
|
@@ -106,10 +101,8 @@ const decodeCursor = (value: string): Cursor | undefined => {
|
|
|
106
101
|
(parsed as { version?: unknown }).version !== 1 ||
|
|
107
102
|
!isTarget((parsed as { target?: unknown }).target) ||
|
|
108
103
|
typeof (parsed as { search?: unknown }).search !== 'string' ||
|
|
109
|
-
((parsed as { keySortDirection?: unknown }).keySortDirection !==
|
|
110
|
-
'
|
|
111
|
-
(parsed as { keySortDirection?: unknown }).keySortDirection !==
|
|
112
|
-
'descending') ||
|
|
104
|
+
((parsed as { keySortDirection?: unknown }).keySortDirection !== 'ascending' &&
|
|
105
|
+
(parsed as { keySortDirection?: unknown }).keySortDirection !== 'descending') ||
|
|
113
106
|
typeof (parsed as { boundaryKey?: unknown }).boundaryKey !== 'string' ||
|
|
114
107
|
((parsed as { direction?: unknown }).direction !== 'next' &&
|
|
115
108
|
(parsed as { direction?: unknown }).direction !== 'previous')
|
|
@@ -126,8 +119,7 @@ const decodeCursor = (value: string): Cursor | undefined => {
|
|
|
126
119
|
const isSameTarget = (left: StorageTarget, right: StorageTarget) =>
|
|
127
120
|
left.adapterId === right.adapterId && left.storageId === right.storageId;
|
|
128
121
|
|
|
129
|
-
const compareKeys = (left: string, right: string) =>
|
|
130
|
-
left < right ? -1 : left > right ? 1 : 0;
|
|
122
|
+
const compareKeys = (left: string, right: string) => (left < right ? -1 : left > right ? 1 : 0);
|
|
131
123
|
|
|
132
124
|
const readPreviews = async (
|
|
133
125
|
view: StorageView,
|
|
@@ -148,14 +140,9 @@ const readPreviews = async (
|
|
|
148
140
|
};
|
|
149
141
|
|
|
150
142
|
await Promise.all(
|
|
151
|
-
Array.from(
|
|
152
|
-
{ length: Math.min(ENTRY_PREVIEW_READ_CONCURRENCY, keys.length) },
|
|
153
|
-
worker,
|
|
154
|
-
),
|
|
155
|
-
);
|
|
156
|
-
return previews.filter(
|
|
157
|
-
(preview): preview is StorageEntryPreview => preview != null,
|
|
143
|
+
Array.from({ length: Math.min(ENTRY_PREVIEW_READ_CONCURRENCY, keys.length) }, worker),
|
|
158
144
|
);
|
|
145
|
+
return previews.filter((preview): preview is StorageEntryPreview => preview != null);
|
|
159
146
|
};
|
|
160
147
|
|
|
161
148
|
export const handleListEntryPreviewsRequest = async (
|
|
@@ -174,9 +161,7 @@ export const handleListEntryPreviewsRequest = async (
|
|
|
174
161
|
};
|
|
175
162
|
}
|
|
176
163
|
|
|
177
|
-
const view = views.find((candidate) =>
|
|
178
|
-
isSameTarget(candidate.target, request.target),
|
|
179
|
-
);
|
|
164
|
+
const view = views.find((candidate) => isSameTarget(candidate.target, request.target));
|
|
180
165
|
if (!view) {
|
|
181
166
|
return {
|
|
182
167
|
type: 'storage-request-error',
|
|
@@ -199,8 +184,7 @@ export const handleListEntryPreviewsRequest = async (
|
|
|
199
184
|
type: 'storage-request-error',
|
|
200
185
|
requestId,
|
|
201
186
|
code: 'INVALID_CURSOR',
|
|
202
|
-
message:
|
|
203
|
-
'The entry preview cursor is invalid or belongs to a different query.',
|
|
187
|
+
message: 'The entry preview cursor is invalid or belongs to a different query.',
|
|
204
188
|
resetPagination: true,
|
|
205
189
|
};
|
|
206
190
|
}
|
|
@@ -7,9 +7,7 @@ import type {
|
|
|
7
7
|
import type { StorageTarget } from '../shared/types';
|
|
8
8
|
import type { StorageView } from './storage-view';
|
|
9
9
|
|
|
10
|
-
type ExportSnapshotResult =
|
|
11
|
-
| StorageExportSnapshotResponseEvent
|
|
12
|
-
| StorageRequestErrorEvent;
|
|
10
|
+
type ExportSnapshotResult = StorageExportSnapshotResponseEvent | StorageRequestErrorEvent;
|
|
13
11
|
|
|
14
12
|
const getRequestId = (payload: unknown) =>
|
|
15
13
|
typeof payload === 'object' &&
|
|
@@ -31,9 +29,7 @@ const isTarget = (value: unknown): value is StorageTarget =>
|
|
|
31
29
|
const isSameTarget = (left: StorageTarget, right: StorageTarget) =>
|
|
32
30
|
left.adapterId === right.adapterId && left.storageId === right.storageId;
|
|
33
31
|
|
|
34
|
-
const validateRequest = (
|
|
35
|
-
payload: unknown,
|
|
36
|
-
): payload is StorageExportSnapshotRequestEvent =>
|
|
32
|
+
const validateRequest = (payload: unknown): payload is StorageExportSnapshotRequestEvent =>
|
|
37
33
|
typeof payload === 'object' &&
|
|
38
34
|
payload != null &&
|
|
39
35
|
!Array.isArray(payload) &&
|
|
@@ -56,9 +52,7 @@ export const handleExportSnapshotRequest = async (
|
|
|
56
52
|
};
|
|
57
53
|
}
|
|
58
54
|
|
|
59
|
-
const view = views.find((candidate) =>
|
|
60
|
-
isSameTarget(candidate.target, payload.target),
|
|
61
|
-
);
|
|
55
|
+
const view = views.find((candidate) => isSameTarget(candidate.target, payload.target));
|
|
62
56
|
if (!view) {
|
|
63
57
|
return {
|
|
64
58
|
type: 'storage-request-error',
|
|
@@ -28,9 +28,7 @@ const isTarget = (value: unknown): value is StorageTarget =>
|
|
|
28
28
|
const isSameTarget = (left: StorageTarget, right: StorageTarget) =>
|
|
29
29
|
left.adapterId === right.adapterId && left.storageId === right.storageId;
|
|
30
30
|
|
|
31
|
-
const validateRequest = (
|
|
32
|
-
payload: unknown,
|
|
33
|
-
): payload is StorageGetEntryRequestEvent =>
|
|
31
|
+
const validateRequest = (payload: unknown): payload is StorageGetEntryRequestEvent =>
|
|
34
32
|
typeof payload === 'object' &&
|
|
35
33
|
payload != null &&
|
|
36
34
|
!Array.isArray(payload) &&
|
|
@@ -50,14 +48,11 @@ export const handleFullEntryRequest = async (
|
|
|
50
48
|
type: 'storage-request-error',
|
|
51
49
|
requestId,
|
|
52
50
|
code: 'INVALID_REQUEST',
|
|
53
|
-
message:
|
|
54
|
-
'Full entry requests require a valid target, request ID, and string key.',
|
|
51
|
+
message: 'Full entry requests require a valid target, request ID, and string key.',
|
|
55
52
|
};
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
const view = views.find((candidate) =>
|
|
59
|
-
isSameTarget(candidate.target, payload.target),
|
|
60
|
-
);
|
|
55
|
+
const view = views.find((candidate) => isSameTarget(candidate.target, payload.target));
|
|
61
56
|
if (!view) {
|
|
62
57
|
return {
|
|
63
58
|
type: 'storage-request-error',
|
|
@@ -8,11 +8,7 @@ import type {
|
|
|
8
8
|
StorageRequestErrorEvent,
|
|
9
9
|
} from '../shared/messaging';
|
|
10
10
|
import { computePreview, parseSnapshot } from '../shared/snapshot';
|
|
11
|
-
import {
|
|
12
|
-
supportsType,
|
|
13
|
-
type StorageEntry,
|
|
14
|
-
type StorageTarget,
|
|
15
|
-
} from '../shared/types';
|
|
11
|
+
import { supportsType, type StorageEntry, type StorageTarget } from '../shared/types';
|
|
16
12
|
import type { StorageView } from './storage-view';
|
|
17
13
|
|
|
18
14
|
export type ImportEmittedEvent =
|
|
@@ -20,9 +16,7 @@ export type ImportEmittedEvent =
|
|
|
20
16
|
| StorageImportResultEvent
|
|
21
17
|
| StorageInvalidatedEvent;
|
|
22
18
|
|
|
23
|
-
type ImportPreviewResult =
|
|
24
|
-
| StorageImportPreviewResponseEvent
|
|
25
|
-
| StorageRequestErrorEvent;
|
|
19
|
+
type ImportPreviewResult = StorageImportPreviewResponseEvent | StorageRequestErrorEvent;
|
|
26
20
|
|
|
27
21
|
const isSameTarget = (left: StorageTarget, right: StorageTarget) =>
|
|
28
22
|
left.adapterId === right.adapterId && left.storageId === right.storageId;
|
|
@@ -58,11 +52,7 @@ const isEntry = (value: unknown): value is StorageEntry => {
|
|
|
58
52
|
entry.type === 'buffer' &&
|
|
59
53
|
Array.isArray(entry.value) &&
|
|
60
54
|
entry.value.every(
|
|
61
|
-
(byte) =>
|
|
62
|
-
typeof byte === 'number' &&
|
|
63
|
-
Number.isInteger(byte) &&
|
|
64
|
-
byte >= 0 &&
|
|
65
|
-
byte <= 255,
|
|
55
|
+
(byte) => typeof byte === 'number' && Number.isInteger(byte) && byte >= 0 && byte <= 255,
|
|
66
56
|
)
|
|
67
57
|
);
|
|
68
58
|
};
|
|
@@ -96,8 +86,7 @@ export const handleImportPreviewRequest = async (
|
|
|
96
86
|
type: 'storage-request-error',
|
|
97
87
|
requestId,
|
|
98
88
|
code: 'INVALID_REQUEST',
|
|
99
|
-
message:
|
|
100
|
-
'Import preview requests require a valid target, request ID, and snapshot.',
|
|
89
|
+
message: 'Import preview requests require a valid target, request ID, and snapshot.',
|
|
101
90
|
};
|
|
102
91
|
}
|
|
103
92
|
|
|
@@ -154,9 +143,7 @@ export const handleImportEntries = async (
|
|
|
154
143
|
emit({
|
|
155
144
|
type: 'import-result',
|
|
156
145
|
requestId,
|
|
157
|
-
target: isTarget(event.target)
|
|
158
|
-
? event.target
|
|
159
|
-
: { adapterId: '', storageId: '' },
|
|
146
|
+
target: isTarget(event.target) ? event.target : { adapterId: '', storageId: '' },
|
|
160
147
|
ok: false,
|
|
161
148
|
written: 0,
|
|
162
149
|
total: 0,
|
|
@@ -182,9 +169,7 @@ export const handleImportEntries = async (
|
|
|
182
169
|
|
|
183
170
|
// Recheck policy at apply time so a preview cannot bypass a changed target.
|
|
184
171
|
const entries = event.entries.filter(
|
|
185
|
-
(entry) =>
|
|
186
|
-
supportsType(view.capabilities, entry.type) &&
|
|
187
|
-
!isBlacklisted(view, entry.key),
|
|
172
|
+
(entry) => supportsType(view.capabilities, entry.type) && !isBlacklisted(view, entry.key),
|
|
188
173
|
);
|
|
189
174
|
|
|
190
175
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -194,10 +179,12 @@ export const handleImportEntries = async (
|
|
|
194
179
|
await view.set(entry);
|
|
195
180
|
} catch (error) {
|
|
196
181
|
if (i > 0) {
|
|
182
|
+
const entryCount = (await view.getAllKeys()).length;
|
|
197
183
|
emit({
|
|
198
184
|
type: 'storage-invalidated',
|
|
199
185
|
target: event.target,
|
|
200
186
|
operation: 'import',
|
|
187
|
+
entryCount,
|
|
201
188
|
});
|
|
202
189
|
}
|
|
203
190
|
emit({
|
|
@@ -223,10 +210,12 @@ export const handleImportEntries = async (
|
|
|
223
210
|
}
|
|
224
211
|
|
|
225
212
|
if (entries.length > 0) {
|
|
213
|
+
const entryCount = (await view.getAllKeys()).length;
|
|
226
214
|
emit({
|
|
227
215
|
type: 'storage-invalidated',
|
|
228
216
|
target: event.target,
|
|
229
217
|
operation: 'import',
|
|
218
|
+
entryCount,
|
|
230
219
|
});
|
|
231
220
|
}
|
|
232
221
|
|
|
@@ -6,9 +6,7 @@ import type {
|
|
|
6
6
|
import type { StorageDescriptor } from '../shared/types';
|
|
7
7
|
import type { StorageView } from './storage-view';
|
|
8
8
|
|
|
9
|
-
type DiscoveryResult =
|
|
10
|
-
| StorageDiscoverStoragesResponseEvent
|
|
11
|
-
| StorageRequestErrorEvent;
|
|
9
|
+
type DiscoveryResult = StorageDiscoverStoragesResponseEvent | StorageRequestErrorEvent;
|
|
12
10
|
|
|
13
11
|
const getRequestId = (payload: unknown) => {
|
|
14
12
|
if (
|
|
@@ -23,9 +21,7 @@ const getRequestId = (payload: unknown) => {
|
|
|
23
21
|
return '';
|
|
24
22
|
};
|
|
25
23
|
|
|
26
|
-
const validateRequest = (
|
|
27
|
-
payload: unknown,
|
|
28
|
-
): payload is StorageDiscoverStoragesRequestEvent =>
|
|
24
|
+
const validateRequest = (payload: unknown): payload is StorageDiscoverStoragesRequestEvent =>
|
|
29
25
|
typeof payload === 'object' &&
|
|
30
26
|
payload != null &&
|
|
31
27
|
!Array.isArray(payload) &&
|
|
@@ -123,9 +123,7 @@ export const createStorageView = (
|
|
|
123
123
|
const getAllEntries = async () => {
|
|
124
124
|
const keys = await getAllKeys(storage);
|
|
125
125
|
const visibleEntries = await Promise.all(
|
|
126
|
-
keys
|
|
127
|
-
.filter((key) => !shouldFilterKey(storageNode, key))
|
|
128
|
-
.map((key) => getEntry(storage, key)),
|
|
126
|
+
keys.filter((key) => !shouldFilterKey(storageNode, key)).map((key) => getEntry(storage, key)),
|
|
129
127
|
);
|
|
130
128
|
|
|
131
129
|
return visibleEntries.filter((entry): entry is StorageEntry => !!entry);
|
|
@@ -189,7 +187,5 @@ export const createStorageView = (
|
|
|
189
187
|
|
|
190
188
|
export const createStorageViews = (storages: StorageAdapter[]) =>
|
|
191
189
|
storages.flatMap((adapter) =>
|
|
192
|
-
adapter.storages.map((storageNode) =>
|
|
193
|
-
createStorageView(adapter, storageNode),
|
|
194
|
-
),
|
|
190
|
+
adapter.storages.map((storageNode) => createStorageView(adapter, storageNode)),
|
|
195
191
|
);
|