@rozenite/storage-plugin 2.1.0 → 2.3.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 (99) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/devtools/assets/panel-CcThj-0Z.js +32 -0
  3. package/dist/devtools/assets/panel-DTYbHqnH.css +1 -0
  4. package/dist/devtools/panel.html +2 -2
  5. package/dist/react-native/cjs/package.json +3 -0
  6. package/dist/react-native/cjs/react-native.js +38 -0
  7. package/dist/react-native/cjs/src/react-native/adapters/async-storage.js +60 -0
  8. package/dist/react-native/cjs/src/react-native/adapters/index.js +10 -0
  9. package/dist/react-native/cjs/src/react-native/adapters/mmkv.js +143 -0
  10. package/dist/react-native/cjs/src/react-native/adapters/secure-storage.js +51 -0
  11. package/dist/react-native/cjs/src/react-native/entry-preview-pagination.js +198 -0
  12. package/dist/react-native/cjs/src/react-native/export-snapshot.js +70 -0
  13. package/dist/react-native/cjs/src/react-native/full-entry-request.js +72 -0
  14. package/dist/react-native/cjs/src/react-native/import.js +185 -0
  15. package/dist/react-native/cjs/src/react-native/storage-discovery.js +69 -0
  16. package/dist/react-native/cjs/src/react-native/storage-view.js +128 -0
  17. package/dist/react-native/cjs/src/react-native/useRozeniteStoragePlugin.js +145 -0
  18. package/dist/react-native/cjs/src/react-native/useStorageAgentTools.js +228 -0
  19. package/dist/react-native/cjs/src/shared/agent-tools.js +119 -0
  20. package/dist/react-native/cjs/src/shared/entry-preview.js +48 -0
  21. package/dist/react-native/cjs/src/shared/messaging.js +2 -0
  22. package/dist/react-native/cjs/src/shared/snapshot.js +185 -0
  23. package/dist/react-native/cjs/src/shared/types.js +13 -0
  24. package/dist/react-native/package.json +3 -0
  25. package/dist/react-native/react-native.d.ts +7 -0
  26. package/dist/react-native/react-native.js +40 -0
  27. package/dist/react-native/src/react-native/adapters/async-storage.d.ts +34 -0
  28. package/dist/react-native/src/react-native/adapters/async-storage.js +56 -0
  29. package/dist/react-native/src/react-native/adapters/index.d.ts +6 -0
  30. package/dist/react-native/src/react-native/adapters/index.js +3 -0
  31. package/dist/react-native/src/react-native/adapters/mmkv.d.ts +13 -0
  32. package/dist/react-native/src/react-native/adapters/mmkv.js +139 -0
  33. package/dist/react-native/src/react-native/adapters/secure-storage.d.ts +18 -0
  34. package/dist/react-native/src/react-native/adapters/secure-storage.js +47 -0
  35. package/dist/react-native/src/react-native/entry-preview-pagination.d.ts +7 -0
  36. package/dist/react-native/src/react-native/entry-preview-pagination.js +194 -0
  37. package/dist/react-native/src/react-native/export-snapshot.d.ts +5 -0
  38. package/dist/react-native/src/react-native/export-snapshot.js +66 -0
  39. package/dist/react-native/src/react-native/full-entry-request.d.ts +5 -0
  40. package/dist/react-native/src/react-native/full-entry-request.js +68 -0
  41. package/dist/react-native/src/react-native/import.d.ts +7 -0
  42. package/dist/react-native/src/react-native/import.js +180 -0
  43. package/dist/react-native/src/react-native/storage-discovery.d.ts +5 -0
  44. package/dist/react-native/src/react-native/storage-discovery.js +65 -0
  45. package/dist/react-native/src/react-native/storage-view.d.ts +19 -0
  46. package/dist/react-native/src/react-native/storage-view.js +123 -0
  47. package/dist/react-native/src/react-native/useRozeniteStoragePlugin.d.ts +6 -0
  48. package/dist/react-native/src/react-native/useRozeniteStoragePlugin.js +141 -0
  49. package/dist/react-native/src/react-native/useStorageAgentTools.d.ts +37 -0
  50. package/dist/react-native/src/react-native/useStorageAgentTools.js +223 -0
  51. package/dist/react-native/src/shared/agent-tools.d.ts +76 -0
  52. package/dist/react-native/src/shared/agent-tools.js +116 -0
  53. package/dist/react-native/src/shared/entry-preview.d.ts +4 -0
  54. package/dist/react-native/src/shared/entry-preview.js +44 -0
  55. package/dist/react-native/src/shared/messaging.d.ts +134 -0
  56. package/dist/react-native/src/shared/messaging.js +1 -0
  57. package/dist/react-native/src/shared/snapshot.d.ts +53 -0
  58. package/dist/react-native/src/shared/snapshot.js +179 -0
  59. package/dist/react-native/src/shared/types.d.ts +78 -0
  60. package/dist/react-native/src/shared/types.js +8 -0
  61. package/dist/rozenite.json +1 -1
  62. package/dist/sdk/package.json +3 -0
  63. package/dist/sdk/sdk.d.ts +44 -0
  64. package/dist/sdk/sdk.js +9 -0
  65. package/dist/sdk/src/shared/agent-tools.d.ts +76 -0
  66. package/dist/sdk/src/shared/agent-tools.js +119 -0
  67. package/dist/sdk/src/shared/types.d.ts +78 -0
  68. package/dist/sdk/src/shared/types.js +13 -0
  69. package/package.json +16 -16
  70. package/react-native.ts +8 -1
  71. package/rozenite.config.ts +1 -0
  72. package/src/__tests__/release-bundle.test.ts +32 -0
  73. package/src/ui/__tests__/panel.test.tsx +14 -0
  74. package/src/ui/add-entry-dialog.tsx +63 -73
  75. package/src/ui/binary-value-editor.tsx +1 -1
  76. package/src/ui/edit-entry-dialog.tsx +59 -70
  77. package/src/ui/entry-detail-dialog.tsx +4 -2
  78. package/src/ui/format-value.tsx +1 -1
  79. package/src/ui/import-dialog.tsx +5 -5
  80. package/src/ui/panel.tsx +70 -88
  81. package/tsconfig.json +4 -7
  82. package/dist/devtools/assets/panel-BlDRXgVW.js +0 -32
  83. package/dist/devtools/assets/panel-DBweZnW5.css +0 -1
  84. package/dist/react-native/chunks/async-storage.require.cjs +0 -1
  85. package/dist/react-native/chunks/async-storage.require.js +0 -53
  86. package/dist/react-native/chunks/index.require.cjs +0 -1
  87. package/dist/react-native/chunks/index.require.js +0 -103
  88. package/dist/react-native/chunks/secure-storage.require.cjs +0 -1
  89. package/dist/react-native/chunks/secure-storage.require.js +0 -46
  90. package/dist/react-native/chunks/types.cjs +0 -1
  91. package/dist/react-native/chunks/types.js +0 -11
  92. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +0 -1
  93. package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +0 -1037
  94. package/dist/react-native/index.cjs +0 -1
  95. package/dist/react-native/index.d.ts +0 -365
  96. package/dist/react-native/index.js +0 -28
  97. package/dist/sdk/index.cjs +0 -1
  98. package/dist/sdk/index.d.ts +0 -206
  99. package/dist/sdk/index.js +0 -122
@@ -0,0 +1,223 @@
1
+ import { useRozenitePluginAgentTool } from '@rozenite/agent-bridge';
2
+ import { DEFAULT_PAGE_LIMIT, MAX_PAGE_LIMIT } from '@rozenite/agent-shared';
3
+ import { STORAGE_AGENT_PLUGIN_ID, storageToolDefinitions, } from '../shared/agent-tools';
4
+ const parseValueForType = (type, value) => {
5
+ if (type === 'string') {
6
+ if (typeof value !== 'string') {
7
+ throw new Error('Expected string value for type=string');
8
+ }
9
+ return value;
10
+ }
11
+ if (type === 'number') {
12
+ if (typeof value !== 'number' || Number.isNaN(value)) {
13
+ throw new Error('Expected number value for type=number');
14
+ }
15
+ return value;
16
+ }
17
+ if (type === 'boolean') {
18
+ if (typeof value !== 'boolean') {
19
+ throw new Error('Expected boolean value for type=boolean');
20
+ }
21
+ return value;
22
+ }
23
+ if (!Array.isArray(value) || !value.every((item) => Number.isInteger(item))) {
24
+ throw new Error('Expected number[] value for type=buffer');
25
+ }
26
+ return value;
27
+ };
28
+ const formatViewRef = (view) => `${view.target.adapterId}/${view.target.storageId}`;
29
+ const isSameTarget = (left, right) => left.adapterId === right.adapterId && left.storageId === right.storageId;
30
+ const encodeCursor = (cursor) => btoa(encodeURIComponent(JSON.stringify(cursor)));
31
+ const decodeCursor = (value) => {
32
+ try {
33
+ const cursor = JSON.parse(decodeURIComponent(atob(value)));
34
+ if (typeof cursor !== 'object' ||
35
+ cursor == null ||
36
+ Array.isArray(cursor) ||
37
+ cursor.version !== 1 ||
38
+ typeof cursor.target?.adapterId !== 'string' ||
39
+ typeof cursor.target?.storageId !== 'string' ||
40
+ !Number.isSafeInteger(cursor.offset) ||
41
+ cursor.offset < 0) {
42
+ throw new Error('Invalid cursor');
43
+ }
44
+ return cursor;
45
+ }
46
+ catch {
47
+ throw new Error('Invalid cursor. Run list-entries again without a cursor to restart pagination.');
48
+ }
49
+ };
50
+ const sanitizeLimit = (limit) => {
51
+ if (typeof limit !== 'number' ||
52
+ !Number.isFinite(limit) ||
53
+ !Number.isInteger(limit) ||
54
+ limit < 1) {
55
+ return DEFAULT_PAGE_LIMIT;
56
+ }
57
+ return Math.min(limit, MAX_PAGE_LIMIT);
58
+ };
59
+ const sanitizeOffset = (offset) => {
60
+ if (typeof offset !== 'number' ||
61
+ !Number.isFinite(offset) ||
62
+ !Number.isSafeInteger(offset) ||
63
+ offset < 0) {
64
+ return 0;
65
+ }
66
+ return offset;
67
+ };
68
+ const resolveStorage = (views, adapterId, storageId) => {
69
+ if (views.length === 0) {
70
+ throw new Error('No storages are registered.');
71
+ }
72
+ if (adapterId !== undefined || storageId !== undefined) {
73
+ const matches = views.filter((view) => (adapterId === undefined || view.target.adapterId === adapterId) &&
74
+ (storageId === undefined || view.target.storageId === storageId));
75
+ const qualifier = [
76
+ adapterId !== undefined && `adapterId="${adapterId}"`,
77
+ storageId !== undefined && `storageId="${storageId}"`,
78
+ ]
79
+ .filter(Boolean)
80
+ .join(', ');
81
+ if (matches.length === 0) {
82
+ throw new Error(`No storage matched ${qualifier}. Available: ${views.map(formatViewRef).join(', ')}`);
83
+ }
84
+ if (matches.length > 1) {
85
+ throw new Error(`Multiple storages matched ${qualifier}. Provide both adapterId and storageId. Available: ${matches.map(formatViewRef).join(', ')}`);
86
+ }
87
+ return matches[0];
88
+ }
89
+ if (views.length > 1) {
90
+ throw new Error(`Multiple storages detected. Provide adapterId and/or storageId. Available: ${views.map(formatViewRef).join(', ')}`);
91
+ }
92
+ return views[0];
93
+ };
94
+ const listStorageKeys = async (view, input) => {
95
+ const limit = sanitizeLimit(input.limit);
96
+ if (input.cursor !== undefined && input.offset !== undefined) {
97
+ throw new Error('Provide either cursor or offset, not both.');
98
+ }
99
+ const cursor = input.cursor === undefined ? undefined : decodeCursor(input.cursor);
100
+ if (cursor && !isSameTarget(cursor.target, view.target)) {
101
+ throw new Error('Cursor does not match the requested storage. Run list-entries again without a cursor to restart pagination.');
102
+ }
103
+ const allKeys = await view.getAllKeys();
104
+ const offset = cursor?.offset ?? sanitizeOffset(input.offset);
105
+ const end = Math.min(offset + limit, allKeys.length);
106
+ return {
107
+ adapterId: view.target.adapterId,
108
+ storageId: view.target.storageId,
109
+ total: allKeys.length,
110
+ items: allKeys.slice(offset, end).map((key) => ({ key })),
111
+ page: {
112
+ limit,
113
+ hasMore: end < allKeys.length,
114
+ ...(end < allKeys.length
115
+ ? {
116
+ nextCursor: encodeCursor({
117
+ version: 1,
118
+ target: view.target,
119
+ offset: end,
120
+ }),
121
+ }
122
+ : {}),
123
+ },
124
+ };
125
+ };
126
+ export const createStorageAgentHandlers = (views) => ({
127
+ listStorages: async () => ({
128
+ storages: views.map((view) => ({
129
+ adapterId: view.target.adapterId,
130
+ storageId: view.target.storageId,
131
+ adapterName: view.adapterName,
132
+ storageName: view.storageName,
133
+ supportedTypes: view.capabilities.supportedTypes,
134
+ })),
135
+ }),
136
+ listEntries: async (input) => {
137
+ const view = resolveStorage(views, input.adapterId, input.storageId);
138
+ return listStorageKeys(view, input);
139
+ },
140
+ readEntry: async ({ adapterId, storageId, key, }) => {
141
+ const view = resolveStorage(views, adapterId, storageId);
142
+ const entry = await view.get(key);
143
+ if (!entry) {
144
+ throw new Error(`Key "${key}" not found in storage "${formatViewRef(view)}".`);
145
+ }
146
+ return {
147
+ adapterId: view.target.adapterId,
148
+ storageId: view.target.storageId,
149
+ entry,
150
+ };
151
+ },
152
+ });
153
+ export const useStorageAgentTools = (views) => {
154
+ const handlers = createStorageAgentHandlers(views);
155
+ useRozenitePluginAgentTool({
156
+ pluginId: STORAGE_AGENT_PLUGIN_ID,
157
+ tool: storageToolDefinitions.listStorages,
158
+ handler: handlers.listStorages,
159
+ });
160
+ useRozenitePluginAgentTool({
161
+ pluginId: STORAGE_AGENT_PLUGIN_ID,
162
+ tool: storageToolDefinitions.listEntries,
163
+ handler: handlers.listEntries,
164
+ });
165
+ useRozenitePluginAgentTool({
166
+ pluginId: STORAGE_AGENT_PLUGIN_ID,
167
+ tool: storageToolDefinitions.readEntry,
168
+ handler: handlers.readEntry,
169
+ });
170
+ useRozenitePluginAgentTool({
171
+ pluginId: STORAGE_AGENT_PLUGIN_ID,
172
+ tool: storageToolDefinitions.createEntry,
173
+ handler: async ({ adapterId, storageId, key, type, value }) => {
174
+ const view = resolveStorage(views, adapterId, storageId);
175
+ const existing = await view.get(key);
176
+ if (existing) {
177
+ throw new Error(`Key "${key}" already exists in storage "${formatViewRef(view)}". Use edit-entry instead.`);
178
+ }
179
+ const parsedValue = parseValueForType(type, value);
180
+ await view.set({ key, type, value: parsedValue });
181
+ return {
182
+ adapterId: view.target.adapterId,
183
+ storageId: view.target.storageId,
184
+ created: true,
185
+ key,
186
+ };
187
+ },
188
+ });
189
+ useRozenitePluginAgentTool({
190
+ pluginId: STORAGE_AGENT_PLUGIN_ID,
191
+ tool: storageToolDefinitions.editEntry,
192
+ handler: async ({ adapterId, storageId, key, type, value }) => {
193
+ const view = resolveStorage(views, adapterId, storageId);
194
+ const existing = await view.get(key);
195
+ if (!existing) {
196
+ throw new Error(`Key "${key}" not found in storage "${formatViewRef(view)}". Use create-entry instead.`);
197
+ }
198
+ const parsedValue = parseValueForType(type, value);
199
+ await view.set({ key, type, value: parsedValue });
200
+ return {
201
+ adapterId: view.target.adapterId,
202
+ storageId: view.target.storageId,
203
+ edited: true,
204
+ key,
205
+ };
206
+ },
207
+ });
208
+ useRozenitePluginAgentTool({
209
+ pluginId: STORAGE_AGENT_PLUGIN_ID,
210
+ tool: storageToolDefinitions.removeEntry,
211
+ handler: async ({ adapterId, storageId, key }) => {
212
+ const view = resolveStorage(views, adapterId, storageId);
213
+ const existed = !!(await view.get(key));
214
+ await view.delete(key);
215
+ return {
216
+ adapterId: view.target.adapterId,
217
+ storageId: view.target.storageId,
218
+ removed: existed,
219
+ key,
220
+ };
221
+ },
222
+ });
223
+ };
@@ -0,0 +1,76 @@
1
+ import { type AgentToolContract } from '@rozenite/agent-shared';
2
+ import type { StorageEntry, StorageEntryType, StorageTarget } from './types';
3
+ type StorageSelection = Partial<StorageTarget>;
4
+ export declare const STORAGE_AGENT_PLUGIN_ID = "@rozenite/storage-plugin";
5
+ export declare const STORAGE_AGENT_ENTRY_TYPES: readonly ["string", "number", "boolean", "buffer"];
6
+ export type StorageListStoragesArgs = undefined;
7
+ export type StorageListStoragesItem = {
8
+ adapterId: string;
9
+ storageId: string;
10
+ adapterName: string;
11
+ storageName: string;
12
+ supportedTypes: StorageEntryType[];
13
+ };
14
+ export type StorageListStoragesResult = {
15
+ storages: StorageListStoragesItem[];
16
+ };
17
+ export type StorageListEntriesArgs = StorageSelection & {
18
+ limit?: number;
19
+ cursor?: string;
20
+ /** @deprecated Use the opaque cursor returned by a previous page. */
21
+ offset?: number;
22
+ };
23
+ export type StorageListEntriesResult = {
24
+ adapterId: string;
25
+ storageId: string;
26
+ total: number;
27
+ items: Array<{
28
+ key: string;
29
+ }>;
30
+ page: {
31
+ limit: number;
32
+ hasMore: boolean;
33
+ nextCursor?: string;
34
+ };
35
+ };
36
+ export type StorageReadEntryArgs = StorageSelection & {
37
+ key: string;
38
+ };
39
+ export type StorageReadEntryResult = {
40
+ adapterId: string;
41
+ storageId: string;
42
+ entry: StorageEntry;
43
+ };
44
+ export type StorageWriteEntryArgs = StorageSelection & StorageEntry;
45
+ export type StorageCreateEntryArgs = StorageWriteEntryArgs;
46
+ export type StorageCreateEntryResult = {
47
+ adapterId: string;
48
+ storageId: string;
49
+ created: true;
50
+ key: string;
51
+ };
52
+ export type StorageEditEntryArgs = StorageWriteEntryArgs;
53
+ export type StorageEditEntryResult = {
54
+ adapterId: string;
55
+ storageId: string;
56
+ edited: true;
57
+ key: string;
58
+ };
59
+ export type StorageRemoveEntryArgs = StorageSelection & {
60
+ key: string;
61
+ };
62
+ export type StorageRemoveEntryResult = {
63
+ adapterId: string;
64
+ storageId: string;
65
+ removed: boolean;
66
+ key: string;
67
+ };
68
+ export declare const storageToolDefinitions: {
69
+ readonly listStorages: AgentToolContract<undefined, StorageListStoragesResult>;
70
+ readonly listEntries: AgentToolContract<StorageListEntriesArgs, StorageListEntriesResult>;
71
+ readonly readEntry: AgentToolContract<StorageReadEntryArgs, StorageReadEntryResult>;
72
+ readonly createEntry: AgentToolContract<StorageWriteEntryArgs, StorageCreateEntryResult>;
73
+ readonly editEntry: AgentToolContract<StorageWriteEntryArgs, StorageEditEntryResult>;
74
+ readonly removeEntry: AgentToolContract<StorageRemoveEntryArgs, StorageRemoveEntryResult>;
75
+ };
76
+ export {};
@@ -0,0 +1,116 @@
1
+ import { defineAgentToolContract, definePaginatedAgentToolContract, } from '@rozenite/agent-shared';
2
+ const sharedStorageProperties = {
3
+ adapterId: {
4
+ type: 'string',
5
+ description: 'Storage adapter ID. Required when multiple adapters are configured.',
6
+ },
7
+ storageId: {
8
+ type: 'string',
9
+ description: 'Storage node ID within the adapter. Required when multiple storages are configured.',
10
+ },
11
+ };
12
+ export const STORAGE_AGENT_PLUGIN_ID = '@rozenite/storage-plugin';
13
+ export const STORAGE_AGENT_ENTRY_TYPES = [
14
+ 'string',
15
+ 'number',
16
+ 'boolean',
17
+ 'buffer',
18
+ ];
19
+ export const storageToolDefinitions = {
20
+ listStorages: defineAgentToolContract({
21
+ name: 'list-storages',
22
+ description: 'List all storage adapters and their storage nodes currently available on the device, including supported entry types. Entry counts are omitted to avoid enumerating every storage.',
23
+ inputSchema: { type: 'object', properties: {} },
24
+ }),
25
+ listEntries: definePaginatedAgentToolContract({
26
+ name: 'list-entries',
27
+ description: 'List keys in a storage. This call intentionally does not return entry values to keep responses token-efficient.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ ...sharedStorageProperties,
32
+ limit: {
33
+ type: 'number',
34
+ description: 'Pagination size. Defaults to 20 and is capped at 100.',
35
+ },
36
+ cursor: {
37
+ type: 'string',
38
+ description: 'Opaque pagination cursor from a previous list-entries call.',
39
+ },
40
+ offset: {
41
+ type: 'number',
42
+ description: 'Deprecated initial pagination offset. Must be a non-negative integer and cannot be combined with cursor.',
43
+ },
44
+ },
45
+ },
46
+ pagination: {
47
+ kind: 'cursor',
48
+ fields: ['key'],
49
+ defaultFields: ['key'],
50
+ },
51
+ }),
52
+ readEntry: defineAgentToolContract({
53
+ name: 'read-entry',
54
+ description: 'Read a single storage entry value by key.',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ ...sharedStorageProperties,
59
+ key: { type: 'string', description: 'Entry key.' },
60
+ },
61
+ required: ['key'],
62
+ },
63
+ }),
64
+ createEntry: defineAgentToolContract({
65
+ name: 'create-entry',
66
+ description: 'Create a new storage entry. Fails if the key already exists.',
67
+ inputSchema: {
68
+ type: 'object',
69
+ properties: {
70
+ ...sharedStorageProperties,
71
+ key: { type: 'string', description: 'Entry key.' },
72
+ type: {
73
+ type: 'string',
74
+ enum: [...STORAGE_AGENT_ENTRY_TYPES],
75
+ description: 'Entry value type. Must be one of the supported types for the target storage (see list-storages).',
76
+ },
77
+ value: {
78
+ description: 'Entry value matching the provided type.',
79
+ },
80
+ },
81
+ required: ['key', 'type', 'value'],
82
+ },
83
+ }),
84
+ editEntry: defineAgentToolContract({
85
+ name: 'edit-entry',
86
+ description: 'Edit an existing storage entry. Fails if the key does not exist.',
87
+ inputSchema: {
88
+ type: 'object',
89
+ properties: {
90
+ ...sharedStorageProperties,
91
+ key: { type: 'string', description: 'Entry key.' },
92
+ type: {
93
+ type: 'string',
94
+ enum: [...STORAGE_AGENT_ENTRY_TYPES],
95
+ description: 'Entry value type. Must be one of the supported types for the target storage (see list-storages).',
96
+ },
97
+ value: {
98
+ description: 'Entry value matching the provided type.',
99
+ },
100
+ },
101
+ required: ['key', 'type', 'value'],
102
+ },
103
+ }),
104
+ removeEntry: defineAgentToolContract({
105
+ name: 'remove-entry',
106
+ description: 'Remove a storage entry by key.',
107
+ inputSchema: {
108
+ type: 'object',
109
+ properties: {
110
+ ...sharedStorageProperties,
111
+ key: { type: 'string', description: 'Entry key.' },
112
+ },
113
+ required: ['key'],
114
+ },
115
+ }),
116
+ };
@@ -0,0 +1,4 @@
1
+ import type { StorageEntry, StorageEntryPreview } from './types';
2
+ export declare const MAX_STRING_PREVIEW_CODE_POINTS = 256;
3
+ export declare const MAX_BUFFER_PREVIEW_BYTES = 16;
4
+ export declare const toStorageEntryPreview: (entry: StorageEntry) => StorageEntryPreview;
@@ -0,0 +1,44 @@
1
+ export const MAX_STRING_PREVIEW_CODE_POINTS = 256;
2
+ export const MAX_BUFFER_PREVIEW_BYTES = 16;
3
+ const toHexPair = (byte) => byte.toString(16).toUpperCase().padStart(2, '0');
4
+ const truncateString = (value) => {
5
+ let preview = '';
6
+ let codePointCount = 0;
7
+ for (const codePoint of value) {
8
+ if (codePointCount === MAX_STRING_PREVIEW_CODE_POINTS) {
9
+ return { preview, isTruncated: true };
10
+ }
11
+ preview += codePoint;
12
+ codePointCount += 1;
13
+ }
14
+ return { preview, isTruncated: false };
15
+ };
16
+ export const toStorageEntryPreview = (entry) => {
17
+ if (entry.type === 'string') {
18
+ return {
19
+ key: entry.key,
20
+ type: entry.type,
21
+ ...truncateString(entry.value),
22
+ // JavaScript strings expose their length in UTF-16 code units.
23
+ valueSize: entry.value.length,
24
+ };
25
+ }
26
+ if (entry.type === 'buffer') {
27
+ const shownBytes = entry.value.slice(0, MAX_BUFFER_PREVIEW_BYTES);
28
+ return {
29
+ key: entry.key,
30
+ type: entry.type,
31
+ preview: shownBytes.map(toHexPair).join(' '),
32
+ valueSize: entry.value.length,
33
+ isTruncated: entry.value.length > MAX_BUFFER_PREVIEW_BYTES,
34
+ };
35
+ }
36
+ const preview = String(entry.value);
37
+ return {
38
+ key: entry.key,
39
+ type: entry.type,
40
+ preview,
41
+ valueSize: preview.length,
42
+ isTruncated: false,
43
+ };
44
+ };
@@ -0,0 +1,134 @@
1
+ import type { StorageDescriptor, StorageEntry, StorageEntryPreview, StorageTarget } from './types';
2
+ import type { ImportPreview, StorageSnapshotV1 } from './snapshot';
3
+ export type StorageSetEntryEvent = {
4
+ type: 'set-entry';
5
+ target: StorageTarget;
6
+ entry: StorageEntry;
7
+ };
8
+ export type StorageDeleteEntryEvent = {
9
+ type: 'delete-entry';
10
+ target: StorageTarget;
11
+ key: string;
12
+ };
13
+ export type StoragePurgeEvent = {
14
+ type: 'purge-storage';
15
+ target: StorageTarget;
16
+ };
17
+ export type StorageInvalidationOperation = 'set' | 'delete' | 'import' | 'purge';
18
+ export type StorageInvalidatedEvent = {
19
+ type: 'storage-invalidated';
20
+ target: StorageTarget;
21
+ /** Native subscriptions only expose a key, not the operation. */
22
+ key?: string;
23
+ operation?: StorageInvalidationOperation;
24
+ /** Updated entry count for the storage after the invalidation. */
25
+ entryCount: number;
26
+ };
27
+ /**
28
+ * Announced by the device once it is listening for panel requests. The panel is
29
+ * recreated on every app reload and asks for storages immediately, which can
30
+ * land before the app's React tree has mounted the plugin; this lets the panel
31
+ * ask again instead of waiting forever on a dropped request.
32
+ */
33
+ export type StorageDeviceReadyEvent = {
34
+ type: 'device-ready';
35
+ };
36
+ export type StorageDiscoverStoragesRequestEvent = {
37
+ type: 'discover-storages';
38
+ requestId: string;
39
+ };
40
+ export type StorageDiscoverStoragesResponseEvent = {
41
+ type: 'storage-descriptors';
42
+ requestId: string;
43
+ storages: StorageDescriptor[];
44
+ };
45
+ export type StorageListEntryPreviewsRequestEvent = {
46
+ type: 'list-entry-previews';
47
+ requestId: string;
48
+ target: StorageTarget;
49
+ search?: string;
50
+ keySortDirection?: 'ascending' | 'descending';
51
+ cursor?: string;
52
+ limit: number;
53
+ };
54
+ export type StorageListEntryPreviewsResponseEvent = {
55
+ type: 'entry-previews';
56
+ requestId: string;
57
+ target: StorageTarget;
58
+ items: StorageEntryPreview[];
59
+ nextCursor?: string;
60
+ previousCursor?: string;
61
+ };
62
+ export type StorageGetEntryRequestEvent = {
63
+ type: 'get-entry';
64
+ requestId: string;
65
+ target: StorageTarget;
66
+ key: string;
67
+ };
68
+ export type StorageGetEntryResponseEvent = {
69
+ type: 'entry';
70
+ requestId: string;
71
+ target: StorageTarget;
72
+ entry: StorageEntry;
73
+ };
74
+ export type StorageExportSnapshotRequestEvent = {
75
+ type: 'export-snapshot';
76
+ requestId: string;
77
+ target: StorageTarget;
78
+ };
79
+ export type StorageExportSnapshotResponseEvent = {
80
+ type: 'export-snapshot-result';
81
+ requestId: string;
82
+ target: StorageTarget;
83
+ snapshot: StorageSnapshotV1;
84
+ };
85
+ export type StorageImportPreviewRequestEvent = {
86
+ type: 'preview-import';
87
+ requestId: string;
88
+ target: StorageTarget;
89
+ snapshot: StorageSnapshotV1;
90
+ };
91
+ export type StorageImportPreviewResponseEvent = {
92
+ type: 'import-preview';
93
+ requestId: string;
94
+ target: StorageTarget;
95
+ preview: ImportPreview;
96
+ };
97
+ export type StorageRequestError = {
98
+ requestId: string;
99
+ code: 'TARGET_NOT_FOUND' | 'ENTRY_NOT_FOUND' | 'INVALID_CURSOR' | 'INVALID_REQUEST' | 'READ_FAILED' | 'WRITE_FAILED';
100
+ message: string;
101
+ resetPagination?: boolean;
102
+ };
103
+ export type StorageRequestErrorEvent = StorageRequestError & {
104
+ type: 'storage-request-error';
105
+ };
106
+ export type StorageImportEntriesEvent = {
107
+ type: 'import-entries';
108
+ requestId: string;
109
+ target: StorageTarget;
110
+ entries: StorageEntry[];
111
+ };
112
+ export type StorageImportProgressEvent = {
113
+ type: 'import-progress';
114
+ requestId: string;
115
+ target: StorageTarget;
116
+ written: number;
117
+ total: number;
118
+ };
119
+ export type StorageImportResultEvent = {
120
+ type: 'import-result';
121
+ requestId: string;
122
+ target: StorageTarget;
123
+ ok: boolean;
124
+ written: number;
125
+ total: number;
126
+ failedKey?: string;
127
+ error?: string;
128
+ };
129
+ export type StorageEvent = StorageSetEntryEvent | StorageDeleteEntryEvent | StoragePurgeEvent | StorageInvalidatedEvent | StorageDeviceReadyEvent | StorageDiscoverStoragesRequestEvent | StorageDiscoverStoragesResponseEvent | StorageListEntryPreviewsRequestEvent | StorageListEntryPreviewsResponseEvent | StorageGetEntryRequestEvent | StorageGetEntryResponseEvent | StorageExportSnapshotRequestEvent | StorageExportSnapshotResponseEvent | StorageImportPreviewRequestEvent | StorageImportPreviewResponseEvent | StorageRequestErrorEvent | StorageImportEntriesEvent | StorageImportProgressEvent | StorageImportResultEvent;
130
+ export type StorageEventMap = {
131
+ [K in StorageEvent['type']]: Extract<StorageEvent, {
132
+ type: K;
133
+ }>;
134
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ import { type StorageCapabilities, type StorageEntry, type StorageEntryType, type StorageTarget } from './types';
2
+ export type StorageSnapshotV1 = {
3
+ version: 1;
4
+ plugin: string;
5
+ createdAt: string;
6
+ storage: {
7
+ adapterId: string;
8
+ storageId: string;
9
+ adapterName: string;
10
+ storageName: string;
11
+ capabilities: StorageCapabilities;
12
+ };
13
+ entries: StorageEntry[];
14
+ };
15
+ export type ParseError = {
16
+ path: string;
17
+ message: string;
18
+ };
19
+ export type ParseResult = {
20
+ ok: true;
21
+ snapshot: StorageSnapshotV1;
22
+ } | {
23
+ ok: false;
24
+ error: ParseError;
25
+ };
26
+ export declare const parseSnapshot: (raw: unknown) => ParseResult;
27
+ export declare const buildSnapshot: (args: {
28
+ target: StorageTarget;
29
+ adapterName: string;
30
+ storageName: string;
31
+ capabilities: StorageCapabilities;
32
+ entries: StorageEntry[];
33
+ }) => StorageSnapshotV1;
34
+ export type ImportPreview = {
35
+ newKeys: string[];
36
+ overwriteKeys: string[];
37
+ skippedKeys: {
38
+ key: string;
39
+ reason: 'blacklist';
40
+ }[];
41
+ unsupportedTypes: {
42
+ key: string;
43
+ type: StorageEntryType;
44
+ }[];
45
+ acceptedEntryIndexes: number[];
46
+ metadataMismatch: boolean;
47
+ };
48
+ export declare const computePreview: (snapshot: StorageSnapshotV1, current: {
49
+ target: StorageTarget;
50
+ capabilities: StorageCapabilities;
51
+ entryKeys: Set<string>;
52
+ isBlacklisted: (key: string) => boolean;
53
+ }) => ImportPreview;