@rozenite/storage-plugin 1.13.0 → 2.0.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 +28 -0
- package/README.md +6 -1
- package/dist/devtools/assets/panel-B6Wp0eie.css +1 -0
- package/dist/devtools/assets/panel-D7MFc4HG.js +32 -0
- package/dist/devtools/panel.html +2 -2
- package/dist/react-native/chunks/async-storage.require.cjs +1 -1
- package/dist/react-native/chunks/async-storage.require.js +8 -7
- package/dist/react-native/chunks/index.require.cjs +1 -1
- package/dist/react-native/chunks/index.require.js +70 -64
- package/dist/react-native/chunks/secure-storage.require.cjs +1 -1
- package/dist/react-native/chunks/secure-storage.require.js +22 -14
- package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +1 -1
- package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +863 -315
- package/dist/react-native/index.d.ts +154 -18
- package/dist/react-native/index.js +10 -2
- package/dist/rozenite.json +1 -1
- package/dist/sdk/index.cjs +1 -1
- package/dist/sdk/index.d.ts +13 -5
- package/dist/sdk/index.js +21 -12
- package/package.json +13 -15
- package/react-native.ts +29 -14
- package/rozenite.config.ts +78 -0
- package/src/react-native/__tests__/entry-preview-pagination.test.ts +262 -0
- package/src/react-native/__tests__/export-snapshot.test.ts +142 -0
- package/src/react-native/__tests__/full-entry-request.test.ts +155 -0
- package/src/react-native/__tests__/import.test.ts +172 -10
- package/src/react-native/__tests__/storage-discovery.test.ts +155 -0
- package/src/react-native/__tests__/storage-view.test.ts +172 -0
- package/src/react-native/__tests__/stress-verification.test.ts +153 -0
- package/src/react-native/__tests__/use-storage-agent-tools.test.ts +231 -0
- package/src/react-native/adapters/async-storage.ts +9 -7
- package/src/react-native/adapters/mmkv.ts +5 -0
- package/src/react-native/adapters/secure-storage.ts +9 -1
- package/src/react-native/entry-preview-pagination.ts +277 -0
- package/src/react-native/export-snapshot.ts +94 -0
- package/src/react-native/full-entry-request.ts +96 -0
- package/src/react-native/import.ts +190 -16
- package/src/react-native/storage-discovery.ts +96 -0
- package/src/react-native/storage-view.ts +45 -103
- package/src/react-native/useRozeniteStoragePlugin.ts +108 -72
- package/src/react-native/useStorageAgentTools.ts +207 -86
- package/src/shared/__tests__/entry-preview.test.ts +117 -0
- package/src/shared/__tests__/snapshot.test.ts +21 -0
- package/src/shared/agent-tools.ts +27 -11
- package/src/shared/entry-preview.ts +59 -0
- package/src/shared/messaging.ts +136 -21
- package/src/shared/snapshot.ts +8 -1
- package/src/shared/types.ts +20 -1
- package/src/ui/__tests__/binary-value-editor-state.test.ts +13 -0
- package/src/ui/__tests__/binary.test.ts +18 -0
- package/src/ui/__tests__/large-value-viewer-state.test.ts +21 -0
- package/src/ui/__tests__/large-value-viewer.test.tsx +63 -0
- package/src/ui/__tests__/panel.test.tsx +409 -0
- package/src/ui/__tests__/storage-groups.test.ts +91 -0
- package/src/ui/__tests__/use-storage-entries.test.tsx +353 -0
- package/src/ui/add-entry-dialog.tsx +65 -105
- package/src/ui/binary-value-editor-state.ts +10 -1
- package/src/ui/binary-value-editor.tsx +57 -33
- package/src/ui/binary.ts +56 -4
- package/src/ui/edit-entry-dialog.tsx +71 -99
- package/src/ui/editor-switcher.tsx +7 -5
- package/src/ui/entry-detail-dialog.tsx +69 -179
- package/src/ui/format-value.tsx +32 -0
- package/src/ui/globals.css +1 -123
- package/src/ui/import-dialog.tsx +66 -91
- package/src/ui/large-value-viewer-state.ts +32 -0
- package/src/ui/large-value-viewer.tsx +71 -0
- package/src/ui/panel.tsx +724 -496
- package/src/ui/query-client.tsx +34 -0
- package/src/ui/storage-groups.ts +66 -0
- package/src/ui/typed-value-editor.tsx +14 -15
- package/src/ui/use-storage-entries.ts +207 -0
- package/tsconfig.json +5 -1
- package/dist/devtools/assets/panel-Bm-SWF7d.js +0 -33
- package/dist/devtools/assets/panel-DIqI4WSp.css +0 -1
- package/postcss.config.js +0 -6
- package/src/ui/confirm-dialog.tsx +0 -100
- package/src/ui/editable-table.tsx +0 -300
- package/tailwind.config.ts +0 -94
|
@@ -2,13 +2,23 @@ import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
|
|
|
2
2
|
import { useEffect, useMemo } from 'react';
|
|
3
3
|
import type {
|
|
4
4
|
StorageDeleteEntryEvent,
|
|
5
|
+
StorageDiscoverStoragesRequestEvent,
|
|
5
6
|
StorageEventMap,
|
|
6
|
-
|
|
7
|
+
StorageExportSnapshotRequestEvent,
|
|
8
|
+
StorageGetEntryRequestEvent,
|
|
7
9
|
StorageImportEntriesEvent,
|
|
10
|
+
StorageImportPreviewRequestEvent,
|
|
11
|
+
StorageInvalidationOperation,
|
|
12
|
+
StorageListEntryPreviewsRequestEvent,
|
|
8
13
|
StorageSetEntryEvent,
|
|
14
|
+
StoragePurgeEvent,
|
|
9
15
|
} from '../shared/messaging';
|
|
10
16
|
import type { StorageAdapter } from '../shared/types';
|
|
11
|
-
import { handleImportEntries } from './import';
|
|
17
|
+
import { handleImportEntries, handleImportPreviewRequest } from './import';
|
|
18
|
+
import { handleStorageDiscoveryRequest } from './storage-discovery';
|
|
19
|
+
import { handleListEntryPreviewsRequest } from './entry-preview-pagination';
|
|
20
|
+
import { handleFullEntryRequest } from './full-entry-request';
|
|
21
|
+
import { handleExportSnapshotRequest } from './export-snapshot';
|
|
12
22
|
import { createStorageViews } from './storage-view';
|
|
13
23
|
import { useStorageAgentTools } from './useStorageAgentTools';
|
|
14
24
|
|
|
@@ -32,73 +42,50 @@ export const useRozeniteStoragePlugin = ({
|
|
|
32
42
|
return;
|
|
33
43
|
}
|
|
34
44
|
|
|
35
|
-
const pushSnapshot = async (viewId?: string) => {
|
|
36
|
-
const selectedViews = viewId
|
|
37
|
-
? views.filter((view) => view.id === viewId)
|
|
38
|
-
: views;
|
|
39
|
-
|
|
40
|
-
for (const view of selectedViews) {
|
|
41
|
-
try {
|
|
42
|
-
const entries = await view.getAllEntries();
|
|
43
|
-
client.send('snapshot', {
|
|
44
|
-
type: 'snapshot',
|
|
45
|
-
target: view.target,
|
|
46
|
-
adapterName: view.adapterName,
|
|
47
|
-
storageName: view.storageName,
|
|
48
|
-
capabilities: view.capabilities,
|
|
49
|
-
blacklist: view.blacklist
|
|
50
|
-
? { source: view.blacklist.source, flags: view.blacklist.flags }
|
|
51
|
-
: undefined,
|
|
52
|
-
entries,
|
|
53
|
-
});
|
|
54
|
-
} catch (error) {
|
|
55
|
-
console.warn(
|
|
56
|
-
`[Rozenite] Storage Plugin: Failed to snapshot ${view.target.adapterId}/${view.target.storageId}.`,
|
|
57
|
-
error,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
void pushSnapshot();
|
|
64
|
-
|
|
65
45
|
const viewSubscriptions: { remove: () => void }[] = [];
|
|
66
46
|
let disposed = false;
|
|
67
47
|
|
|
48
|
+
const sendInvalidation = (
|
|
49
|
+
target: StorageSetEntryEvent['target'],
|
|
50
|
+
key?: string,
|
|
51
|
+
operation?: StorageInvalidationOperation,
|
|
52
|
+
) => {
|
|
53
|
+
client.send('storage-invalidated', {
|
|
54
|
+
type: 'storage-invalidated',
|
|
55
|
+
target,
|
|
56
|
+
key,
|
|
57
|
+
operation,
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
68
61
|
// Prevent one storage watcher failure from breaking the whole plugin.
|
|
69
62
|
void Promise.all(
|
|
70
|
-
views
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
});
|
|
79
|
-
},
|
|
80
|
-
onDelete: (key) => {
|
|
81
|
-
client.send('delete-entry', {
|
|
82
|
-
type: 'delete-entry',
|
|
83
|
-
target: view.target,
|
|
84
|
-
key,
|
|
85
|
-
});
|
|
86
|
-
},
|
|
87
|
-
});
|
|
63
|
+
views
|
|
64
|
+
.filter((view) => view.supportsSubscriptions && view.watch)
|
|
65
|
+
.map(async (view) => {
|
|
66
|
+
try {
|
|
67
|
+
const watch = view.watch;
|
|
68
|
+
if (!watch) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
88
71
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
72
|
+
const subscription = await watch((key) => {
|
|
73
|
+
sendInvalidation(view.target, key);
|
|
74
|
+
});
|
|
93
75
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
76
|
+
if (disposed) {
|
|
77
|
+
subscription.remove();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
viewSubscriptions.push(subscription);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.warn(
|
|
84
|
+
`[Rozenite] Storage Plugin: Failed to attach watcher for ${view.target.adapterId}/${view.target.storageId}.`,
|
|
85
|
+
error,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}),
|
|
102
89
|
);
|
|
103
90
|
|
|
104
91
|
const messageSubscriptions = [
|
|
@@ -120,6 +107,7 @@ export const useRozeniteStoragePlugin = ({
|
|
|
120
107
|
|
|
121
108
|
try {
|
|
122
109
|
await view.set(entry);
|
|
110
|
+
sendInvalidation(view.target, entry.key, 'set');
|
|
123
111
|
} catch (error) {
|
|
124
112
|
console.warn(
|
|
125
113
|
`[Rozenite] Storage Plugin: Failed to set entry in ${target.adapterId}/${target.storageId}.`,
|
|
@@ -146,6 +134,7 @@ export const useRozeniteStoragePlugin = ({
|
|
|
146
134
|
|
|
147
135
|
try {
|
|
148
136
|
await view.delete(key);
|
|
137
|
+
sendInvalidation(view.target, key, 'delete');
|
|
149
138
|
} catch (error) {
|
|
150
139
|
console.warn(
|
|
151
140
|
`[Rozenite] Storage Plugin: Failed to delete entry in ${target.adapterId}/${target.storageId}.`,
|
|
@@ -155,25 +144,72 @@ export const useRozeniteStoragePlugin = ({
|
|
|
155
144
|
},
|
|
156
145
|
),
|
|
157
146
|
client.onMessage(
|
|
158
|
-
'
|
|
159
|
-
async ({ target }:
|
|
160
|
-
|
|
161
|
-
|
|
147
|
+
'purge-storage',
|
|
148
|
+
async ({ target }: StoragePurgeEvent) => {
|
|
149
|
+
const view = views.find(
|
|
150
|
+
(candidate) =>
|
|
151
|
+
candidate.target.adapterId === target.adapterId &&
|
|
152
|
+
candidate.target.storageId === target.storageId,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
if (!view) {
|
|
156
|
+
console.warn(
|
|
157
|
+
`[Rozenite] Storage Plugin: Storage target not found for ${target.adapterId}/${target.storageId}`,
|
|
158
|
+
);
|
|
162
159
|
return;
|
|
163
160
|
}
|
|
164
161
|
|
|
165
|
-
|
|
162
|
+
try {
|
|
163
|
+
await view.purge();
|
|
164
|
+
sendInvalidation(view.target, undefined, 'purge');
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.warn(
|
|
167
|
+
`[Rozenite] Storage Plugin: Failed to purge storage in ${target.adapterId}/${target.storageId}.`,
|
|
168
|
+
error,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
),
|
|
173
|
+
client.onMessage(
|
|
174
|
+
'discover-storages',
|
|
175
|
+
async (event: StorageDiscoverStoragesRequestEvent) => {
|
|
176
|
+
const response = await handleStorageDiscoveryRequest(views, event);
|
|
177
|
+
client.send(response.type, response);
|
|
178
|
+
},
|
|
179
|
+
),
|
|
180
|
+
client.onMessage(
|
|
181
|
+
'list-entry-previews',
|
|
182
|
+
async (event: StorageListEntryPreviewsRequestEvent) => {
|
|
183
|
+
const response = await handleListEntryPreviewsRequest(views, event);
|
|
184
|
+
client.send(response.type, response);
|
|
185
|
+
},
|
|
186
|
+
),
|
|
187
|
+
client.onMessage(
|
|
188
|
+
'get-entry',
|
|
189
|
+
async (event: StorageGetEntryRequestEvent) => {
|
|
190
|
+
const response = await handleFullEntryRequest(views, event);
|
|
191
|
+
client.send(response.type, response);
|
|
192
|
+
},
|
|
193
|
+
),
|
|
194
|
+
client.onMessage(
|
|
195
|
+
'export-snapshot',
|
|
196
|
+
async (event: StorageExportSnapshotRequestEvent) => {
|
|
197
|
+
const response = await handleExportSnapshotRequest(views, event);
|
|
198
|
+
client.send(response.type, response);
|
|
199
|
+
},
|
|
200
|
+
),
|
|
201
|
+
client.onMessage(
|
|
202
|
+
'preview-import',
|
|
203
|
+
async (event: StorageImportPreviewRequestEvent) => {
|
|
204
|
+
const response = await handleImportPreviewRequest(views, event);
|
|
205
|
+
client.send(response.type, response);
|
|
166
206
|
},
|
|
167
207
|
),
|
|
168
208
|
client.onMessage(
|
|
169
209
|
'import-entries',
|
|
170
210
|
async (event: StorageImportEntriesEvent) => {
|
|
171
211
|
await handleImportEntries(views, event, (out) => {
|
|
172
|
-
|
|
173
|
-
client.send('set-entry', out);
|
|
174
|
-
} else {
|
|
175
|
-
client.send('import-result', out);
|
|
176
|
-
}
|
|
212
|
+
client.send(out.type, out);
|
|
177
213
|
});
|
|
178
214
|
},
|
|
179
215
|
),
|
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
2
1
|
import { useRozenitePluginAgentTool } from '@rozenite/agent-bridge';
|
|
2
|
+
import { DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT } from '@rozenite/agent-shared';
|
|
3
3
|
import {
|
|
4
4
|
STORAGE_AGENT_PLUGIN_ID,
|
|
5
5
|
storageToolDefinitions,
|
|
6
|
+
type StorageListEntriesArgs,
|
|
6
7
|
} from '../shared/agent-tools';
|
|
7
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
StorageEntry,
|
|
10
|
+
StorageEntryType,
|
|
11
|
+
StorageEntryValue,
|
|
12
|
+
StorageTarget,
|
|
13
|
+
} from '../shared/types';
|
|
8
14
|
import type { StorageView } from './storage-view';
|
|
9
15
|
|
|
10
16
|
const parseValueForType = (
|
|
11
17
|
type: StorageEntryType,
|
|
12
|
-
value: unknown
|
|
18
|
+
value: unknown,
|
|
13
19
|
): StorageEntryValue => {
|
|
14
20
|
if (type === 'string') {
|
|
15
21
|
if (typeof value !== 'string') {
|
|
@@ -42,115 +48,230 @@ const parseValueForType = (
|
|
|
42
48
|
const formatViewRef = (view: StorageView) =>
|
|
43
49
|
`${view.target.adapterId}/${view.target.storageId}`;
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
(adapterId?: string, storageId?: string) => {
|
|
48
|
-
if (views.length === 0) {
|
|
49
|
-
throw new Error('No storages are registered.');
|
|
50
|
-
}
|
|
51
|
+
const isSameTarget = (left: StorageTarget, right: StorageTarget) =>
|
|
52
|
+
left.adapterId === right.adapterId && left.storageId === right.storageId;
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
);
|
|
54
|
+
type ListEntriesCursor = {
|
|
55
|
+
version: 1;
|
|
56
|
+
target: StorageTarget;
|
|
57
|
+
offset: number;
|
|
58
|
+
};
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
adapterId && `adapterId="${adapterId}"`,
|
|
62
|
-
storageId && `storageId="${storageId}"`,
|
|
63
|
-
]
|
|
64
|
-
.filter(Boolean)
|
|
65
|
-
.join(', ');
|
|
66
|
-
throw new Error(
|
|
67
|
-
`No storage matched ${qualifier}. Available: ${views.map(formatViewRef).join(', ')}`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return selected;
|
|
72
|
-
}
|
|
60
|
+
const encodeCursor = (cursor: ListEntriesCursor) =>
|
|
61
|
+
btoa(encodeURIComponent(JSON.stringify(cursor)));
|
|
73
62
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
const decodeCursor = (value: string): ListEntriesCursor => {
|
|
64
|
+
try {
|
|
65
|
+
const cursor: unknown = JSON.parse(decodeURIComponent(atob(value)));
|
|
66
|
+
if (
|
|
67
|
+
typeof cursor !== 'object' ||
|
|
68
|
+
cursor == null ||
|
|
69
|
+
Array.isArray(cursor) ||
|
|
70
|
+
(cursor as { version?: unknown }).version !== 1 ||
|
|
71
|
+
typeof (cursor as { target?: { adapterId?: unknown } }).target
|
|
72
|
+
?.adapterId !== 'string' ||
|
|
73
|
+
typeof (cursor as { target?: { storageId?: unknown } }).target
|
|
74
|
+
?.storageId !== 'string' ||
|
|
75
|
+
!Number.isSafeInteger((cursor as { offset?: unknown }).offset) ||
|
|
76
|
+
(cursor as { offset: number }).offset < 0
|
|
77
|
+
) {
|
|
78
|
+
throw new Error('Invalid cursor');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return cursor as ListEntriesCursor;
|
|
82
|
+
} catch {
|
|
83
|
+
throw new Error(
|
|
84
|
+
'Invalid cursor. Run list-entries again without a cursor to restart pagination.',
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const sanitizeLimit = (limit: number | undefined) => {
|
|
90
|
+
if (
|
|
91
|
+
typeof limit !== 'number' ||
|
|
92
|
+
!Number.isFinite(limit) ||
|
|
93
|
+
!Number.isInteger(limit) ||
|
|
94
|
+
limit < 1
|
|
95
|
+
) {
|
|
96
|
+
return DEFAULT_PAGE_LIMIT;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Math.min(limit, MAX_PAGE_LIMIT);
|
|
100
|
+
};
|
|
79
101
|
|
|
80
|
-
|
|
102
|
+
const sanitizeOffset = (offset: number | undefined) => {
|
|
103
|
+
if (
|
|
104
|
+
typeof offset !== 'number' ||
|
|
105
|
+
!Number.isFinite(offset) ||
|
|
106
|
+
!Number.isSafeInteger(offset) ||
|
|
107
|
+
offset < 0
|
|
108
|
+
) {
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return offset;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const resolveStorage = (
|
|
116
|
+
views: readonly StorageView[],
|
|
117
|
+
adapterId?: string,
|
|
118
|
+
storageId?: string,
|
|
119
|
+
) => {
|
|
120
|
+
if (views.length === 0) {
|
|
121
|
+
throw new Error('No storages are registered.');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (adapterId !== undefined || storageId !== undefined) {
|
|
125
|
+
const matches = views.filter(
|
|
126
|
+
(view) =>
|
|
127
|
+
(adapterId === undefined || view.target.adapterId === adapterId) &&
|
|
128
|
+
(storageId === undefined || view.target.storageId === storageId),
|
|
129
|
+
);
|
|
130
|
+
const qualifier = [
|
|
131
|
+
adapterId !== undefined && `adapterId="${adapterId}"`,
|
|
132
|
+
storageId !== undefined && `storageId="${storageId}"`,
|
|
133
|
+
]
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
.join(', ');
|
|
136
|
+
|
|
137
|
+
if (matches.length === 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`No storage matched ${qualifier}. Available: ${views.map(formatViewRef).join(', ')}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (matches.length > 1) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Multiple storages matched ${qualifier}. Provide both adapterId and storageId. Available: ${matches.map(formatViewRef).join(', ')}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return matches[0]!;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (views.length > 1) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Multiple storages detected. Provide adapterId and/or storageId. Available: ${views.map(formatViewRef).join(', ')}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return views[0]!;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const listStorageKeys = async (
|
|
162
|
+
view: StorageView,
|
|
163
|
+
input: StorageListEntriesArgs,
|
|
164
|
+
) => {
|
|
165
|
+
const limit = sanitizeLimit(input.limit);
|
|
166
|
+
if (input.cursor !== undefined && input.offset !== undefined) {
|
|
167
|
+
throw new Error('Provide either cursor or offset, not both.');
|
|
168
|
+
}
|
|
169
|
+
const cursor =
|
|
170
|
+
input.cursor === undefined ? undefined : decodeCursor(input.cursor);
|
|
171
|
+
if (cursor && !isSameTarget(cursor.target, view.target)) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
'Cursor does not match the requested storage. Run list-entries again without a cursor to restart pagination.',
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const allKeys = await view.getAllKeys();
|
|
178
|
+
const offset = cursor?.offset ?? sanitizeOffset(input.offset);
|
|
179
|
+
const end = Math.min(offset + limit, allKeys.length);
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
adapterId: view.target.adapterId,
|
|
183
|
+
storageId: view.target.storageId,
|
|
184
|
+
total: allKeys.length,
|
|
185
|
+
items: allKeys.slice(offset, end).map((key) => ({ key })),
|
|
186
|
+
page: {
|
|
187
|
+
limit,
|
|
188
|
+
hasMore: end < allKeys.length,
|
|
189
|
+
...(end < allKeys.length
|
|
190
|
+
? {
|
|
191
|
+
nextCursor: encodeCursor({
|
|
192
|
+
version: 1,
|
|
193
|
+
target: view.target,
|
|
194
|
+
offset: end,
|
|
195
|
+
}),
|
|
196
|
+
}
|
|
197
|
+
: {}),
|
|
81
198
|
},
|
|
82
|
-
|
|
83
|
-
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export const createStorageAgentHandlers = (views: readonly StorageView[]) => ({
|
|
203
|
+
listStorages: async () => ({
|
|
204
|
+
storages: views.map((view) => ({
|
|
205
|
+
adapterId: view.target.adapterId,
|
|
206
|
+
storageId: view.target.storageId,
|
|
207
|
+
adapterName: view.adapterName,
|
|
208
|
+
storageName: view.storageName,
|
|
209
|
+
supportedTypes: view.capabilities.supportedTypes,
|
|
210
|
+
})),
|
|
211
|
+
}),
|
|
212
|
+
|
|
213
|
+
listEntries: async (input: StorageListEntriesArgs) => {
|
|
214
|
+
const view = resolveStorage(views, input.adapterId, input.storageId);
|
|
215
|
+
return listStorageKeys(view, input);
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
readEntry: async ({
|
|
219
|
+
adapterId,
|
|
220
|
+
storageId,
|
|
221
|
+
key,
|
|
222
|
+
}: {
|
|
223
|
+
adapterId?: string;
|
|
224
|
+
storageId?: string;
|
|
225
|
+
key: string;
|
|
226
|
+
}) => {
|
|
227
|
+
const view = resolveStorage(views, adapterId, storageId);
|
|
228
|
+
const entry = await view.get(key);
|
|
229
|
+
|
|
230
|
+
if (!entry) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
`Key "${key}" not found in storage "${formatViewRef(view)}".`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
adapterId: view.target.adapterId,
|
|
238
|
+
storageId: view.target.storageId,
|
|
239
|
+
entry,
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
export const useStorageAgentTools = (views: StorageView[]) => {
|
|
245
|
+
const handlers = createStorageAgentHandlers(views);
|
|
84
246
|
|
|
85
247
|
useRozenitePluginAgentTool({
|
|
86
248
|
pluginId: STORAGE_AGENT_PLUGIN_ID,
|
|
87
249
|
tool: storageToolDefinitions.listStorages,
|
|
88
|
-
handler:
|
|
89
|
-
storages: await Promise.all(
|
|
90
|
-
views.map(async (view) => ({
|
|
91
|
-
adapterId: view.target.adapterId,
|
|
92
|
-
storageId: view.target.storageId,
|
|
93
|
-
adapterName: view.adapterName,
|
|
94
|
-
storageName: view.storageName,
|
|
95
|
-
supportedTypes: view.capabilities.supportedTypes,
|
|
96
|
-
entryCount: (await view.getAllKeys()).length,
|
|
97
|
-
}))
|
|
98
|
-
),
|
|
99
|
-
}),
|
|
250
|
+
handler: handlers.listStorages,
|
|
100
251
|
});
|
|
101
252
|
|
|
102
253
|
useRozenitePluginAgentTool({
|
|
103
254
|
pluginId: STORAGE_AGENT_PLUGIN_ID,
|
|
104
255
|
tool: storageToolDefinitions.listEntries,
|
|
105
|
-
handler:
|
|
106
|
-
const view = resolveStorage(adapterId, storageId);
|
|
107
|
-
const allKeys = await view.getAllKeys();
|
|
108
|
-
const safeOffset = Math.max(0, Math.floor(offset));
|
|
109
|
-
const safeLimit = Math.max(1, Math.floor(limit));
|
|
110
|
-
const keys = allKeys.slice(safeOffset, safeOffset + safeLimit);
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
adapterId: view.target.adapterId,
|
|
114
|
-
storageId: view.target.storageId,
|
|
115
|
-
total: allKeys.length,
|
|
116
|
-
offset: safeOffset,
|
|
117
|
-
limit: safeLimit,
|
|
118
|
-
keys,
|
|
119
|
-
};
|
|
120
|
-
},
|
|
256
|
+
handler: handlers.listEntries,
|
|
121
257
|
});
|
|
122
258
|
|
|
123
259
|
useRozenitePluginAgentTool({
|
|
124
260
|
pluginId: STORAGE_AGENT_PLUGIN_ID,
|
|
125
261
|
tool: storageToolDefinitions.readEntry,
|
|
126
|
-
handler:
|
|
127
|
-
const view = resolveStorage(adapterId, storageId);
|
|
128
|
-
const entry = await view.get(key);
|
|
129
|
-
|
|
130
|
-
if (!entry) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`Key "${key}" not found in storage "${formatViewRef(view)}".`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
adapterId: view.target.adapterId,
|
|
138
|
-
storageId: view.target.storageId,
|
|
139
|
-
entry,
|
|
140
|
-
};
|
|
141
|
-
},
|
|
262
|
+
handler: handlers.readEntry,
|
|
142
263
|
});
|
|
143
264
|
|
|
144
265
|
useRozenitePluginAgentTool({
|
|
145
266
|
pluginId: STORAGE_AGENT_PLUGIN_ID,
|
|
146
267
|
tool: storageToolDefinitions.createEntry,
|
|
147
268
|
handler: async ({ adapterId, storageId, key, type, value }) => {
|
|
148
|
-
const view = resolveStorage(adapterId, storageId);
|
|
269
|
+
const view = resolveStorage(views, adapterId, storageId);
|
|
149
270
|
const existing = await view.get(key);
|
|
150
271
|
|
|
151
272
|
if (existing) {
|
|
152
273
|
throw new Error(
|
|
153
|
-
`Key "${key}" already exists in storage "${formatViewRef(view)}". Use edit-entry instead
|
|
274
|
+
`Key "${key}" already exists in storage "${formatViewRef(view)}". Use edit-entry instead.`,
|
|
154
275
|
);
|
|
155
276
|
}
|
|
156
277
|
|
|
@@ -170,12 +291,12 @@ export const useStorageAgentTools = (views: StorageView[]) => {
|
|
|
170
291
|
pluginId: STORAGE_AGENT_PLUGIN_ID,
|
|
171
292
|
tool: storageToolDefinitions.editEntry,
|
|
172
293
|
handler: async ({ adapterId, storageId, key, type, value }) => {
|
|
173
|
-
const view = resolveStorage(adapterId, storageId);
|
|
294
|
+
const view = resolveStorage(views, adapterId, storageId);
|
|
174
295
|
const existing = await view.get(key);
|
|
175
296
|
|
|
176
297
|
if (!existing) {
|
|
177
298
|
throw new Error(
|
|
178
|
-
`Key "${key}" not found in storage "${formatViewRef(view)}". Use create-entry instead
|
|
299
|
+
`Key "${key}" not found in storage "${formatViewRef(view)}". Use create-entry instead.`,
|
|
179
300
|
);
|
|
180
301
|
}
|
|
181
302
|
|
|
@@ -195,7 +316,7 @@ export const useStorageAgentTools = (views: StorageView[]) => {
|
|
|
195
316
|
pluginId: STORAGE_AGENT_PLUGIN_ID,
|
|
196
317
|
tool: storageToolDefinitions.removeEntry,
|
|
197
318
|
handler: async ({ adapterId, storageId, key }) => {
|
|
198
|
-
const view = resolveStorage(adapterId, storageId);
|
|
319
|
+
const view = resolveStorage(views, adapterId, storageId);
|
|
199
320
|
const existed = !!(await view.get(key));
|
|
200
321
|
await view.delete(key);
|
|
201
322
|
|