@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
|
@@ -137,6 +137,16 @@ declare type StorageDescriptor = {
|
|
|
137
137
|
supportsSubscriptions: boolean;
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Announced by the device once it is listening for panel requests. The panel is
|
|
142
|
+
* recreated on every app reload and asks for storages immediately, which can
|
|
143
|
+
* land before the app's React tree has mounted the plugin; this lets the panel
|
|
144
|
+
* ask again instead of waiting forever on a dropped request.
|
|
145
|
+
*/
|
|
146
|
+
declare type StorageDeviceReadyEvent = {
|
|
147
|
+
type: 'device-ready';
|
|
148
|
+
};
|
|
149
|
+
|
|
140
150
|
declare type StorageDiscoverStoragesRequestEvent = {
|
|
141
151
|
type: 'discover-storages';
|
|
142
152
|
requestId: string;
|
|
@@ -178,7 +188,7 @@ export declare type StorageEntryType = StorageEntry['type'];
|
|
|
178
188
|
|
|
179
189
|
export declare type StorageEntryValue = StorageEntry['value'];
|
|
180
190
|
|
|
181
|
-
declare type StorageEvent_2 = StorageSetEntryEvent | StorageDeleteEntryEvent | StoragePurgeEvent | StorageInvalidatedEvent | StorageDiscoverStoragesRequestEvent | StorageDiscoverStoragesResponseEvent | StorageListEntryPreviewsRequestEvent | StorageListEntryPreviewsResponseEvent | StorageGetEntryRequestEvent | StorageGetEntryResponseEvent | StorageExportSnapshotRequestEvent | StorageExportSnapshotResponseEvent | StorageImportPreviewRequestEvent | StorageImportPreviewResponseEvent | StorageRequestErrorEvent | StorageImportEntriesEvent | StorageImportProgressEvent | StorageImportResultEvent;
|
|
191
|
+
declare type StorageEvent_2 = StorageSetEntryEvent | StorageDeleteEntryEvent | StoragePurgeEvent | StorageInvalidatedEvent | StorageDeviceReadyEvent | StorageDiscoverStoragesRequestEvent | StorageDiscoverStoragesResponseEvent | StorageListEntryPreviewsRequestEvent | StorageListEntryPreviewsResponseEvent | StorageGetEntryRequestEvent | StorageGetEntryResponseEvent | StorageExportSnapshotRequestEvent | StorageExportSnapshotResponseEvent | StorageImportPreviewRequestEvent | StorageImportPreviewResponseEvent | StorageRequestErrorEvent | StorageImportEntriesEvent | StorageImportProgressEvent | StorageImportResultEvent;
|
|
182
192
|
|
|
183
193
|
declare type StorageEventMap = {
|
|
184
194
|
[K in StorageEvent_2['type']]: Extract<StorageEvent_2, {
|
|
@@ -259,6 +269,8 @@ declare type StorageInvalidatedEvent = {
|
|
|
259
269
|
/** Native subscriptions only expose a key, not the operation. */
|
|
260
270
|
key?: string;
|
|
261
271
|
operation?: StorageInvalidationOperation;
|
|
272
|
+
/** Updated entry count for the storage after the invalidation. */
|
|
273
|
+
entryCount: number;
|
|
262
274
|
};
|
|
263
275
|
|
|
264
276
|
declare type StorageInvalidationOperation = 'set' | 'delete' | 'import' | 'purge';
|
|
@@ -348,6 +360,6 @@ export declare type SyncStorage = {
|
|
|
348
360
|
|
|
349
361
|
export declare let useRozeniteStoragePlugin: typeof useRozeniteStoragePlugin_2;
|
|
350
362
|
|
|
351
|
-
declare const useRozeniteStoragePlugin_2: ({ storages
|
|
363
|
+
declare const useRozeniteStoragePlugin_2: ({ storages }: RozeniteStoragePluginOptions) => RozeniteDevToolsRequestClient<StorageEventMap> | null;
|
|
352
364
|
|
|
353
365
|
export { }
|
|
@@ -13,18 +13,10 @@ if (!i || u)
|
|
|
13
13
|
e,
|
|
14
14
|
"expo-secure-store",
|
|
15
15
|
"Expo SecureStore"
|
|
16
|
-
)), s = ((e) => t(
|
|
17
|
-
e,
|
|
18
|
-
"mmkv",
|
|
19
|
-
"MMKV"
|
|
20
|
-
)), p = () => null;
|
|
16
|
+
)), s = ((e) => t(e, "mmkv", "MMKV")), p = () => null;
|
|
21
17
|
else if (d) {
|
|
22
18
|
const e = require("./chunks/async-storage.require.js"), a = require("./chunks/secure-storage.require.js");
|
|
23
|
-
r = e.createAsyncStorageAdapter, c = e.createExpoAsyncStorageAdapter, n = a.createExpoSecureStorageAdapter, s = ((o) => t(
|
|
24
|
-
o,
|
|
25
|
-
"mmkv",
|
|
26
|
-
"MMKV"
|
|
27
|
-
)), p = require("./chunks/useRozeniteStoragePlugin.require.js").useRozeniteStoragePlugin;
|
|
19
|
+
r = e.createAsyncStorageAdapter, c = e.createExpoAsyncStorageAdapter, n = a.createExpoSecureStorageAdapter, s = ((o) => t(o, "mmkv", "MMKV")), p = require("./chunks/useRozeniteStoragePlugin.require.js").useRozeniteStoragePlugin;
|
|
28
20
|
} else
|
|
29
21
|
r = require("./chunks/index.require.js").createAsyncStorageAdapter, c = require("./chunks/index.require.js").createExpoAsyncStorageAdapter, n = require("./chunks/index.require.js").createExpoSecureStorageAdapter, s = require("./chunks/index.require.js").createMMKVStorageAdapter, p = require("./chunks/useRozeniteStoragePlugin.require.js").useRozeniteStoragePlugin;
|
|
30
22
|
export {
|
package/dist/rozenite.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"@rozenite/storage-plugin","version":"2.
|
|
1
|
+
{"name":"@rozenite/storage-plugin","version":"2.1.0","description":"Generic Storage Inspector for Rozenite.","panels":[{"name":"Storage","source":"/devtools/panel.html"}]}
|
package/package.json
CHANGED
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rozenite/storage-plugin",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Generic Storage Inspector for Rozenite.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/react-native/index.cjs",
|
|
7
|
-
"module": "./dist/react-native/index.js",
|
|
8
|
-
"types": "./dist/react-native/index.d.ts",
|
|
9
5
|
"homepage": "https://github.com/callstackincubator/rozenite#readme",
|
|
10
6
|
"bugs": {
|
|
11
7
|
"url": "https://github.com/callstackincubator/rozenite/issues"
|
|
12
8
|
},
|
|
9
|
+
"license": "MIT",
|
|
13
10
|
"repository": {
|
|
14
11
|
"type": "git",
|
|
15
12
|
"url": "https://github.com/callstackincubator/rozenite.git"
|
|
16
13
|
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/react-native/index.cjs",
|
|
16
|
+
"module": "./dist/react-native/index.js",
|
|
17
|
+
"types": "./dist/react-native/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/react-native/index.d.ts",
|
|
21
|
+
"import": "./dist/react-native/index.js",
|
|
22
|
+
"require": "./dist/react-native/index.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./sdk": {
|
|
25
|
+
"development": "./sdk.ts",
|
|
26
|
+
"types": "./dist/sdk/index.d.ts",
|
|
27
|
+
"import": "./dist/sdk/index.js",
|
|
28
|
+
"require": "./dist/sdk/index.cjs"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
17
35
|
"dependencies": {
|
|
18
|
-
"@rozenite/agent-
|
|
19
|
-
"@rozenite/agent-
|
|
20
|
-
"@rozenite/plugin-bridge": "2.
|
|
21
|
-
"@rozenite/ui": "2.
|
|
36
|
+
"@rozenite/agent-bridge": "2.1.0",
|
|
37
|
+
"@rozenite/agent-shared": "2.1.0",
|
|
38
|
+
"@rozenite/plugin-bridge": "2.1.0",
|
|
39
|
+
"@rozenite/ui": "2.1.0"
|
|
22
40
|
},
|
|
23
41
|
"devDependencies": {
|
|
24
42
|
"@codemirror/commands": "^6.10.3",
|
|
@@ -39,8 +57,8 @@
|
|
|
39
57
|
"react-virtuoso": "^4.14.1",
|
|
40
58
|
"typescript": "~5.9.3",
|
|
41
59
|
"vite": "^7.3.1",
|
|
42
|
-
"@rozenite/vite-plugin": "2.
|
|
43
|
-
"rozenite": "2.
|
|
60
|
+
"@rozenite/vite-plugin": "2.1.0",
|
|
61
|
+
"rozenite": "2.1.0"
|
|
44
62
|
},
|
|
45
63
|
"peerDependencies": {
|
|
46
64
|
"@react-native-async-storage/async-storage": "*",
|
|
@@ -60,24 +78,6 @@
|
|
|
60
78
|
"optional": true
|
|
61
79
|
}
|
|
62
80
|
},
|
|
63
|
-
"license": "MIT",
|
|
64
|
-
"exports": {
|
|
65
|
-
".": {
|
|
66
|
-
"types": "./dist/react-native/index.d.ts",
|
|
67
|
-
"import": "./dist/react-native/index.js",
|
|
68
|
-
"require": "./dist/react-native/index.cjs"
|
|
69
|
-
},
|
|
70
|
-
"./sdk": {
|
|
71
|
-
"development": "./sdk.ts",
|
|
72
|
-
"types": "./dist/sdk/index.d.ts",
|
|
73
|
-
"import": "./dist/sdk/index.js",
|
|
74
|
-
"require": "./dist/sdk/index.cjs"
|
|
75
|
-
},
|
|
76
|
-
"./package.json": "./package.json"
|
|
77
|
-
},
|
|
78
|
-
"publishConfig": {
|
|
79
|
-
"access": "public"
|
|
80
|
-
},
|
|
81
81
|
"scripts": {
|
|
82
82
|
"build": "rozenite build",
|
|
83
83
|
"dev": "rozenite dev",
|
package/react-native.ts
CHANGED
|
@@ -25,8 +25,7 @@ export let createExpoSecureStorageAdapter: typeof import('./src/react-native/ada
|
|
|
25
25
|
export let createMMKVStorageAdapter: typeof import('./src/react-native/adapters').createMMKVStorageAdapter;
|
|
26
26
|
export let useRozeniteStoragePlugin: typeof import('./src/react-native/useRozeniteStoragePlugin').useRozeniteStoragePlugin;
|
|
27
27
|
|
|
28
|
-
const isWeb =
|
|
29
|
-
typeof window !== 'undefined' && window.navigator.product !== 'ReactNative';
|
|
28
|
+
const isWeb = typeof window !== 'undefined' && window.navigator.product !== 'ReactNative';
|
|
30
29
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
31
30
|
const isServer = typeof window === 'undefined';
|
|
32
31
|
|
|
@@ -41,64 +40,40 @@ const createNoopStorageAdapter = (
|
|
|
41
40
|
});
|
|
42
41
|
|
|
43
42
|
if (!isDev || isServer) {
|
|
44
|
-
createAsyncStorageAdapter = ((options: {
|
|
45
|
-
adapterId?: string;
|
|
46
|
-
adapterName?: string;
|
|
47
|
-
}) =>
|
|
43
|
+
createAsyncStorageAdapter = ((options: { adapterId?: string; adapterName?: string }) =>
|
|
48
44
|
createNoopStorageAdapter(
|
|
49
45
|
options,
|
|
50
46
|
'async-storage',
|
|
51
47
|
'AsyncStorage',
|
|
52
48
|
)) as typeof createAsyncStorageAdapter;
|
|
53
49
|
createExpoAsyncStorageAdapter = createAsyncStorageAdapter;
|
|
54
|
-
createExpoSecureStorageAdapter = ((options: {
|
|
55
|
-
adapterId?: string;
|
|
56
|
-
adapterName?: string;
|
|
57
|
-
}) =>
|
|
50
|
+
createExpoSecureStorageAdapter = ((options: { adapterId?: string; adapterName?: string }) =>
|
|
58
51
|
createNoopStorageAdapter(
|
|
59
52
|
options,
|
|
60
53
|
'expo-secure-store',
|
|
61
54
|
'Expo SecureStore',
|
|
62
55
|
)) as typeof createExpoSecureStorageAdapter;
|
|
63
|
-
createMMKVStorageAdapter = ((options: {
|
|
64
|
-
|
|
65
|
-
adapterName?: string;
|
|
66
|
-
}) =>
|
|
67
|
-
createNoopStorageAdapter(
|
|
68
|
-
options,
|
|
69
|
-
'mmkv',
|
|
70
|
-
'MMKV',
|
|
71
|
-
)) as typeof createMMKVStorageAdapter;
|
|
56
|
+
createMMKVStorageAdapter = ((options: { adapterId?: string; adapterName?: string }) =>
|
|
57
|
+
createNoopStorageAdapter(options, 'mmkv', 'MMKV')) as typeof createMMKVStorageAdapter;
|
|
72
58
|
useRozeniteStoragePlugin = () => null;
|
|
73
59
|
} else if (isWeb) {
|
|
74
60
|
const asyncStorageModule = require('./src/react-native/adapters/async-storage');
|
|
75
61
|
const secureStorageModule = require('./src/react-native/adapters/secure-storage');
|
|
76
62
|
|
|
77
63
|
createAsyncStorageAdapter = asyncStorageModule.createAsyncStorageAdapter;
|
|
78
|
-
createExpoAsyncStorageAdapter =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
createMMKVStorageAdapter = ((options: {
|
|
83
|
-
adapterId?: string;
|
|
84
|
-
adapterName?: string;
|
|
85
|
-
}) =>
|
|
86
|
-
createNoopStorageAdapter(
|
|
87
|
-
options,
|
|
88
|
-
'mmkv',
|
|
89
|
-
'MMKV',
|
|
90
|
-
)) as typeof createMMKVStorageAdapter;
|
|
64
|
+
createExpoAsyncStorageAdapter = asyncStorageModule.createExpoAsyncStorageAdapter;
|
|
65
|
+
createExpoSecureStorageAdapter = secureStorageModule.createExpoSecureStorageAdapter;
|
|
66
|
+
createMMKVStorageAdapter = ((options: { adapterId?: string; adapterName?: string }) =>
|
|
67
|
+
createNoopStorageAdapter(options, 'mmkv', 'MMKV')) as typeof createMMKVStorageAdapter;
|
|
91
68
|
useRozeniteStoragePlugin =
|
|
92
69
|
require('./src/react-native/useRozeniteStoragePlugin').useRozeniteStoragePlugin;
|
|
93
70
|
} else {
|
|
94
|
-
createAsyncStorageAdapter =
|
|
95
|
-
require('./src/react-native/adapters').createAsyncStorageAdapter;
|
|
71
|
+
createAsyncStorageAdapter = require('./src/react-native/adapters').createAsyncStorageAdapter;
|
|
96
72
|
createExpoAsyncStorageAdapter =
|
|
97
73
|
require('./src/react-native/adapters').createExpoAsyncStorageAdapter;
|
|
98
74
|
createExpoSecureStorageAdapter =
|
|
99
75
|
require('./src/react-native/adapters').createExpoSecureStorageAdapter;
|
|
100
|
-
createMMKVStorageAdapter =
|
|
101
|
-
require('./src/react-native/adapters').createMMKVStorageAdapter;
|
|
76
|
+
createMMKVStorageAdapter = require('./src/react-native/adapters').createMMKVStorageAdapter;
|
|
102
77
|
useRozeniteStoragePlugin =
|
|
103
78
|
require('./src/react-native/useRozeniteStoragePlugin').useRozeniteStoragePlugin;
|
|
104
79
|
}
|
package/rozenite.config.ts
CHANGED
|
@@ -63,16 +63,12 @@ export default {
|
|
|
63
63
|
name: 'Initialize storage fixtures',
|
|
64
64
|
autoRun: true,
|
|
65
65
|
run: async ({ send, waitForMessage }: DevFlowContext) => {
|
|
66
|
-
await waitForMessage(
|
|
67
|
-
{ type: 'get-snapshot', direction: 'out' },
|
|
68
|
-
{ timeoutMs: 10000 },
|
|
69
|
-
);
|
|
66
|
+
await waitForMessage({ type: 'get-snapshot', direction: 'out' }, { timeoutMs: 10000 });
|
|
70
67
|
|
|
71
68
|
storageFixtures.forEach((snapshot) => send('snapshot', snapshot));
|
|
72
69
|
|
|
73
70
|
return {
|
|
74
|
-
message:
|
|
75
|
-
'Initialized AsyncStorage, Expo SecureStore, and MMKV fixtures.',
|
|
71
|
+
message: 'Initialized AsyncStorage, Expo SecureStore, and MMKV fixtures.',
|
|
76
72
|
storages: storageFixtures.length,
|
|
77
73
|
entries: storageFixtures.reduce(
|
|
78
74
|
(total, snapshot) => total + snapshot.entries.length,
|
package/sdk.ts
CHANGED
|
@@ -5,11 +5,7 @@ import {
|
|
|
5
5
|
storageToolDefinitions,
|
|
6
6
|
} from './src/shared/agent-tools.js';
|
|
7
7
|
|
|
8
|
-
export {
|
|
9
|
-
STORAGE_AGENT_ENTRY_TYPES,
|
|
10
|
-
STORAGE_AGENT_PLUGIN_ID,
|
|
11
|
-
storageToolDefinitions,
|
|
12
|
-
};
|
|
8
|
+
export { STORAGE_AGENT_ENTRY_TYPES, STORAGE_AGENT_PLUGIN_ID, storageToolDefinitions };
|
|
13
9
|
|
|
14
10
|
export const storageTools = defineAgentToolDescriptors(
|
|
15
11
|
STORAGE_AGENT_PLUGIN_ID,
|
|
@@ -13,9 +13,7 @@ const createView = (
|
|
|
13
13
|
values: Record<string, StorageEntry>,
|
|
14
14
|
options?: {
|
|
15
15
|
blacklist?: RegExp;
|
|
16
|
-
get?: (
|
|
17
|
-
key: string,
|
|
18
|
-
) => StorageEntry | undefined | Promise<StorageEntry | undefined>;
|
|
16
|
+
get?: (key: string) => StorageEntry | undefined | Promise<StorageEntry | undefined>;
|
|
19
17
|
},
|
|
20
18
|
) => {
|
|
21
19
|
const get = vi.fn(options?.get ?? ((key: string) => values[key]));
|
|
@@ -45,9 +43,7 @@ const createView = (
|
|
|
45
43
|
};
|
|
46
44
|
|
|
47
45
|
const entries = (...keys: string[]): Record<string, StorageEntry> =>
|
|
48
|
-
Object.fromEntries(
|
|
49
|
-
keys.map((key) => [key, { key, type: 'string', value: `value:${key}` }]),
|
|
50
|
-
);
|
|
46
|
+
Object.fromEntries(keys.map((key) => [key, { key, type: 'string', value: `value:${key}` }]));
|
|
51
47
|
|
|
52
48
|
const request = (overrides: Record<string, unknown> = {}) => ({
|
|
53
49
|
type: 'list-entry-previews' as const,
|
|
@@ -59,9 +55,7 @@ const request = (overrides: Record<string, unknown> = {}) => ({
|
|
|
59
55
|
|
|
60
56
|
describe('handleListEntryPreviewsRequest', () => {
|
|
61
57
|
it('returns sorted first, next, and previous pages without reading outside each page', async () => {
|
|
62
|
-
const { view, get } = createView(
|
|
63
|
-
entries('delta', 'bravo', 'alpha', 'charlie', 'echo'),
|
|
64
|
-
);
|
|
58
|
+
const { view, get } = createView(entries('delta', 'bravo', 'alpha', 'charlie', 'echo'));
|
|
65
59
|
|
|
66
60
|
const first = await handleListEntryPreviewsRequest([view], request());
|
|
67
61
|
expect(first).toMatchObject({
|
|
@@ -85,8 +79,7 @@ describe('handleListEntryPreviewsRequest', () => {
|
|
|
85
79
|
const previous = await handleListEntryPreviewsRequest(
|
|
86
80
|
[view],
|
|
87
81
|
request({
|
|
88
|
-
cursor:
|
|
89
|
-
next.type === 'entry-previews' ? next.previousCursor : undefined,
|
|
82
|
+
cursor: next.type === 'entry-previews' ? next.previousCursor : undefined,
|
|
90
83
|
}),
|
|
91
84
|
);
|
|
92
85
|
expect(previous).toMatchObject({
|
|
@@ -97,9 +90,7 @@ describe('handleListEntryPreviewsRequest', () => {
|
|
|
97
90
|
});
|
|
98
91
|
|
|
99
92
|
it('filters normalized key search and sorts before pagination without inspecting non-page values', async () => {
|
|
100
|
-
const { view, get } = createView(
|
|
101
|
-
entries('Alpha', 'alphabet', 'beta', 'ALPINE'),
|
|
102
|
-
);
|
|
93
|
+
const { view, get } = createView(entries('Alpha', 'alphabet', 'beta', 'ALPINE'));
|
|
103
94
|
|
|
104
95
|
const result = await handleListEntryPreviewsRequest(
|
|
105
96
|
[view],
|
|
@@ -125,9 +116,7 @@ describe('handleListEntryPreviewsRequest', () => {
|
|
|
125
116
|
target,
|
|
126
117
|
items: [],
|
|
127
118
|
});
|
|
128
|
-
await expect(
|
|
129
|
-
handleListEntryPreviewsRequest([view], request()),
|
|
130
|
-
).resolves.toEqual({
|
|
119
|
+
await expect(handleListEntryPreviewsRequest([view], request())).resolves.toEqual({
|
|
131
120
|
type: 'entry-previews',
|
|
132
121
|
requestId: 'request-1',
|
|
133
122
|
target,
|
|
@@ -148,25 +137,16 @@ describe('handleListEntryPreviewsRequest', () => {
|
|
|
148
137
|
const { view } = createView(entries('alpha', 'bravo', 'charlie'));
|
|
149
138
|
|
|
150
139
|
await expect(
|
|
151
|
-
handleListEntryPreviewsRequest(
|
|
152
|
-
[view],
|
|
153
|
-
request({ cursor: 'not-a-cursor' }),
|
|
154
|
-
),
|
|
140
|
+
handleListEntryPreviewsRequest([view], request({ cursor: 'not-a-cursor' })),
|
|
155
141
|
).resolves.toMatchObject({ code: 'INVALID_CURSOR', resetPagination: true });
|
|
156
142
|
|
|
157
143
|
const first = await handleListEntryPreviewsRequest([view], request());
|
|
158
144
|
const cursor = first.type === 'entry-previews' ? first.nextCursor! : '';
|
|
159
145
|
await expect(
|
|
160
|
-
handleListEntryPreviewsRequest(
|
|
161
|
-
[view],
|
|
162
|
-
request({ search: 'alpha', cursor }),
|
|
163
|
-
),
|
|
146
|
+
handleListEntryPreviewsRequest([view], request({ search: 'alpha', cursor })),
|
|
164
147
|
).resolves.toMatchObject({ code: 'INVALID_CURSOR', resetPagination: true });
|
|
165
148
|
|
|
166
|
-
const stale = await handleListEntryPreviewsRequest(
|
|
167
|
-
[view],
|
|
168
|
-
request({ cursor }),
|
|
169
|
-
);
|
|
149
|
+
const stale = await handleListEntryPreviewsRequest([view], request({ cursor }));
|
|
170
150
|
expect(stale).toMatchObject({ type: 'entry-previews' });
|
|
171
151
|
const noBoundary = createView(entries('alpha', 'charlie')).view;
|
|
172
152
|
await expect(
|
|
@@ -177,10 +157,7 @@ describe('handleListEntryPreviewsRequest', () => {
|
|
|
177
157
|
it('applies blacklist rules before pagination and caps oversized page limits', async () => {
|
|
178
158
|
const { view, get } = createView(
|
|
179
159
|
entries(
|
|
180
|
-
...Array.from(
|
|
181
|
-
{ length: MAX_ENTRY_PREVIEW_PAGE_SIZE + 2 },
|
|
182
|
-
(_, index) => `key-${index}`,
|
|
183
|
-
),
|
|
160
|
+
...Array.from({ length: MAX_ENTRY_PREVIEW_PAGE_SIZE + 2 }, (_, index) => `key-${index}`),
|
|
184
161
|
'secret',
|
|
185
162
|
),
|
|
186
163
|
{ blacklist: /^secret$/ },
|
|
@@ -209,10 +186,7 @@ describe('handleListEntryPreviewsRequest', () => {
|
|
|
209
186
|
},
|
|
210
187
|
});
|
|
211
188
|
|
|
212
|
-
const result = await handleListEntryPreviewsRequest(
|
|
213
|
-
[view],
|
|
214
|
-
request({ limit: 5 }),
|
|
215
|
-
);
|
|
189
|
+
const result = await handleListEntryPreviewsRequest([view], request({ limit: 5 }));
|
|
216
190
|
|
|
217
191
|
expect(maxActiveReads).toBeLessThanOrEqual(ENTRY_PREVIEW_READ_CONCURRENCY);
|
|
218
192
|
expect(result).toMatchObject({
|
|
@@ -239,9 +213,7 @@ describe('handleListEntryPreviewsRequest', () => {
|
|
|
239
213
|
},
|
|
240
214
|
});
|
|
241
215
|
|
|
242
|
-
await expect(
|
|
243
|
-
handleListEntryPreviewsRequest([view], request()),
|
|
244
|
-
).resolves.toEqual({
|
|
216
|
+
await expect(handleListEntryPreviewsRequest([view], request())).resolves.toEqual({
|
|
245
217
|
type: 'storage-request-error',
|
|
246
218
|
requestId: 'request-1',
|
|
247
219
|
code: 'READ_FAILED',
|
|
@@ -89,9 +89,7 @@ describe('handleExportSnapshotRequest', () => {
|
|
|
89
89
|
it('exports an empty storage as a valid empty snapshot', async () => {
|
|
90
90
|
const { view } = createView({});
|
|
91
91
|
|
|
92
|
-
await expect(
|
|
93
|
-
handleExportSnapshotRequest([view], request()),
|
|
94
|
-
).resolves.toMatchObject({
|
|
92
|
+
await expect(handleExportSnapshotRequest([view], request())).resolves.toMatchObject({
|
|
95
93
|
type: 'export-snapshot-result',
|
|
96
94
|
snapshot: { entries: [] },
|
|
97
95
|
});
|
|
@@ -130,9 +128,7 @@ describe('handleExportSnapshotRequest', () => {
|
|
|
130
128
|
},
|
|
131
129
|
);
|
|
132
130
|
|
|
133
|
-
await expect(
|
|
134
|
-
handleExportSnapshotRequest([view], request()),
|
|
135
|
-
).resolves.toEqual({
|
|
131
|
+
await expect(handleExportSnapshotRequest([view], request())).resolves.toEqual({
|
|
136
132
|
type: 'storage-request-error',
|
|
137
133
|
requestId: 'request-1',
|
|
138
134
|
code: 'READ_FAILED',
|
|
@@ -17,9 +17,7 @@ const createView = (
|
|
|
17
17
|
values: Record<string, StorageEntry>,
|
|
18
18
|
options?: {
|
|
19
19
|
blacklist?: RegExp;
|
|
20
|
-
get?: (
|
|
21
|
-
key: string,
|
|
22
|
-
) => StorageEntry | undefined | Promise<StorageEntry | undefined>;
|
|
20
|
+
get?: (key: string) => StorageEntry | undefined | Promise<StorageEntry | undefined>;
|
|
23
21
|
},
|
|
24
22
|
) => {
|
|
25
23
|
const get = vi.fn(options?.get ?? ((key: string) => values[key]));
|
|
@@ -69,9 +67,7 @@ describe('handleFullEntryRequest', () => {
|
|
|
69
67
|
|
|
70
68
|
it('returns structured errors for missing, blacklisted, invalid, and unknown targets', async () => {
|
|
71
69
|
const missing = createView({});
|
|
72
|
-
await expect(
|
|
73
|
-
handleFullEntryRequest([missing.view], request()),
|
|
74
|
-
).resolves.toMatchObject({
|
|
70
|
+
await expect(handleFullEntryRequest([missing.view], request())).resolves.toMatchObject({
|
|
75
71
|
type: 'storage-request-error',
|
|
76
72
|
requestId: 'request-1',
|
|
77
73
|
code: 'ENTRY_NOT_FOUND',
|
|
@@ -81,9 +77,7 @@ describe('handleFullEntryRequest', () => {
|
|
|
81
77
|
{ key: { key: 'key', type: 'string', value: 'secret' } },
|
|
82
78
|
{ blacklist: /^key$/ },
|
|
83
79
|
);
|
|
84
|
-
await expect(
|
|
85
|
-
handleFullEntryRequest([blacklisted.view], request()),
|
|
86
|
-
).resolves.toMatchObject({
|
|
80
|
+
await expect(handleFullEntryRequest([blacklisted.view], request())).resolves.toMatchObject({
|
|
87
81
|
code: 'ENTRY_NOT_FOUND',
|
|
88
82
|
});
|
|
89
83
|
expect(blacklisted.get).not.toHaveBeenCalled();
|
|
@@ -128,14 +122,8 @@ describe('handleFullEntryRequest', () => {
|
|
|
128
122
|
}),
|
|
129
123
|
},
|
|
130
124
|
);
|
|
131
|
-
const first = handleFullEntryRequest(
|
|
132
|
-
|
|
133
|
-
request({ requestId: 'first', key: 'first' }),
|
|
134
|
-
);
|
|
135
|
-
const second = handleFullEntryRequest(
|
|
136
|
-
[view],
|
|
137
|
-
request({ requestId: 'second', key: 'second' }),
|
|
138
|
-
);
|
|
125
|
+
const first = handleFullEntryRequest([view], request({ requestId: 'first', key: 'first' }));
|
|
126
|
+
const second = handleFullEntryRequest([view], request({ requestId: 'second', key: 'second' }));
|
|
139
127
|
|
|
140
128
|
await vi.waitFor(() => expect(get).toHaveBeenCalledTimes(2));
|
|
141
129
|
resolvers.get('second')?.({ key: 'second', type: 'string', value: 'two' });
|
|
@@ -29,7 +29,7 @@ const buildView = (overrides?: Partial<StorageView>): StorageView => {
|
|
|
29
29
|
set: vi.fn(async () => {}),
|
|
30
30
|
delete: unexpected('delete'),
|
|
31
31
|
purge: unexpected('purge'),
|
|
32
|
-
getAllKeys:
|
|
32
|
+
getAllKeys: vi.fn(async () => []),
|
|
33
33
|
getAllEntries: unexpected('getAllEntries'),
|
|
34
34
|
watch: unexpected('watch'),
|
|
35
35
|
...overrides,
|
|
@@ -74,6 +74,7 @@ describe('handleImportEntries', () => {
|
|
|
74
74
|
type: 'storage-invalidated',
|
|
75
75
|
target,
|
|
76
76
|
operation: 'import',
|
|
77
|
+
entryCount: 0,
|
|
77
78
|
});
|
|
78
79
|
expect(emitted[6]).toEqual({
|
|
79
80
|
type: 'import-result',
|
|
@@ -122,6 +123,7 @@ describe('handleImportEntries', () => {
|
|
|
122
123
|
type: 'storage-invalidated',
|
|
123
124
|
target,
|
|
124
125
|
operation: 'import',
|
|
126
|
+
entryCount: 0,
|
|
125
127
|
});
|
|
126
128
|
expect(emitted[3]).toEqual({
|
|
127
129
|
type: 'import-result',
|
|
@@ -294,6 +296,7 @@ describe('handleImportEntries', () => {
|
|
|
294
296
|
type: 'storage-invalidated',
|
|
295
297
|
target,
|
|
296
298
|
operation: 'import',
|
|
299
|
+
entryCount: 0,
|
|
297
300
|
},
|
|
298
301
|
{
|
|
299
302
|
type: 'import-result',
|
|
@@ -309,13 +312,11 @@ describe('handleImportEntries', () => {
|
|
|
309
312
|
it('returns predictable errors for malformed and unknown preview targets', async () => {
|
|
310
313
|
const view = buildView();
|
|
311
314
|
|
|
312
|
-
await expect(handleImportPreviewRequest([view], {})).resolves.toMatchObject(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
},
|
|
318
|
-
);
|
|
315
|
+
await expect(handleImportPreviewRequest([view], {})).resolves.toMatchObject({
|
|
316
|
+
type: 'storage-request-error',
|
|
317
|
+
requestId: '',
|
|
318
|
+
code: 'INVALID_REQUEST',
|
|
319
|
+
});
|
|
319
320
|
await expect(
|
|
320
321
|
handleImportPreviewRequest([view], {
|
|
321
322
|
type: 'preview-import',
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import type {
|
|
3
|
-
StorageAdapter,
|
|
4
|
-
StorageCapabilities,
|
|
5
|
-
StorageTarget,
|
|
6
|
-
} from '../../shared/types';
|
|
2
|
+
import type { StorageAdapter, StorageCapabilities, StorageTarget } from '../../shared/types';
|
|
7
3
|
import { handleStorageDiscoveryRequest } from '../storage-discovery';
|
|
8
4
|
import { createStorageViews, type StorageView } from '../storage-view';
|
|
9
5
|
|
|
@@ -92,13 +88,10 @@ describe('handleStorageDiscoveryRequest', () => {
|
|
|
92
88
|
|
|
93
89
|
it('returns a correlated structured error for invalid requests', async () => {
|
|
94
90
|
expect(
|
|
95
|
-
await handleStorageDiscoveryRequest(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
requestId: ' ',
|
|
100
|
-
},
|
|
101
|
-
),
|
|
91
|
+
await handleStorageDiscoveryRequest(createStorageViews([createAdapter()]), {
|
|
92
|
+
type: 'discover-storages',
|
|
93
|
+
requestId: ' ',
|
|
94
|
+
}),
|
|
102
95
|
).toEqual({
|
|
103
96
|
type: 'storage-request-error',
|
|
104
97
|
requestId: ' ',
|
|
@@ -125,9 +118,7 @@ describe('handleStorageDiscoveryRequest', () => {
|
|
|
125
118
|
watch: vi.fn(),
|
|
126
119
|
});
|
|
127
120
|
|
|
128
|
-
expect(
|
|
129
|
-
await handleStorageDiscoveryRequest([view('One'), view('Two')], request),
|
|
130
|
-
).toEqual({
|
|
121
|
+
expect(await handleStorageDiscoveryRequest([view('One'), view('Two')], request)).toEqual({
|
|
131
122
|
type: 'storage-request-error',
|
|
132
123
|
requestId: 'request-1',
|
|
133
124
|
code: 'INVALID_REQUEST',
|
|
@@ -143,9 +134,7 @@ describe('handleStorageDiscoveryRequest', () => {
|
|
|
143
134
|
},
|
|
144
135
|
} as unknown as StorageView;
|
|
145
136
|
|
|
146
|
-
await expect(
|
|
147
|
-
handleStorageDiscoveryRequest([view], request),
|
|
148
|
-
).resolves.toEqual({
|
|
137
|
+
await expect(handleStorageDiscoveryRequest([view], request)).resolves.toEqual({
|
|
149
138
|
type: 'storage-request-error',
|
|
150
139
|
requestId: 'request-1',
|
|
151
140
|
code: 'READ_FAILED',
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import type {
|
|
3
|
-
StorageAdapter,
|
|
4
|
-
StorageEntry,
|
|
5
|
-
StorageEntryType,
|
|
6
|
-
} from '../../shared/types';
|
|
2
|
+
import type { StorageAdapter, StorageEntry, StorageEntryType } from '../../shared/types';
|
|
7
3
|
import {
|
|
8
4
|
ENTRY_PREVIEW_READ_CONCURRENCY,
|
|
9
5
|
MAX_ENTRY_PREVIEW_PAGE_SIZE,
|
|
@@ -100,10 +96,7 @@ describe('storage scalability stress fixture', () => {
|
|
|
100
96
|
it('reads only one bounded page from the selected storage when values are slow and large', async () => {
|
|
101
97
|
const fixture = createStressAdapter();
|
|
102
98
|
|
|
103
|
-
const result = await handleListEntryPreviewsRequest(
|
|
104
|
-
fixture.views,
|
|
105
|
-
previewRequest(),
|
|
106
|
-
);
|
|
99
|
+
const result = await handleListEntryPreviewsRequest(fixture.views, previewRequest());
|
|
107
100
|
|
|
108
101
|
expect(result).toMatchObject({
|
|
109
102
|
type: 'entry-previews',
|
|
@@ -119,12 +112,8 @@ describe('storage scalability stress fixture', () => {
|
|
|
119
112
|
expect(result.items.every((item) => !('value' in item))).toBe(true);
|
|
120
113
|
}
|
|
121
114
|
expect(fixture.storageReads[0]?.getAllKeys).toHaveBeenCalledTimes(1);
|
|
122
|
-
expect(fixture.storageReads[0]?.get).toHaveBeenCalledTimes(
|
|
123
|
-
|
|
124
|
-
);
|
|
125
|
-
expect(fixture.maxActiveReads()).toBeLessThanOrEqual(
|
|
126
|
-
ENTRY_PREVIEW_READ_CONCURRENCY,
|
|
127
|
-
);
|
|
115
|
+
expect(fixture.storageReads[0]?.get).toHaveBeenCalledTimes(MAX_ENTRY_PREVIEW_PAGE_SIZE);
|
|
116
|
+
expect(fixture.maxActiveReads()).toBeLessThanOrEqual(ENTRY_PREVIEW_READ_CONCURRENCY);
|
|
128
117
|
for (const { get, getAllKeys } of fixture.storageReads.slice(1)) {
|
|
129
118
|
expect(getAllKeys).not.toHaveBeenCalled();
|
|
130
119
|
expect(get).not.toHaveBeenCalled();
|
|
@@ -143,9 +132,7 @@ describe('storage scalability stress fixture', () => {
|
|
|
143
132
|
type: 'entry-previews',
|
|
144
133
|
items: [expect.objectContaining({ key: 'entry-0999' })],
|
|
145
134
|
});
|
|
146
|
-
expect(fixture.storageReads[0]?.get).toHaveBeenCalledExactlyOnceWith(
|
|
147
|
-
'entry-0999',
|
|
148
|
-
);
|
|
135
|
+
expect(fixture.storageReads[0]?.get).toHaveBeenCalledExactlyOnceWith('entry-0999');
|
|
149
136
|
for (const { get } of fixture.storageReads.slice(1)) {
|
|
150
137
|
expect(get).not.toHaveBeenCalled();
|
|
151
138
|
}
|