@rozenite/storage-plugin 2.2.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.
- package/CHANGELOG.md +16 -0
- package/dist/devtools/assets/panel-CcThj-0Z.js +32 -0
- package/dist/devtools/assets/panel-DTYbHqnH.css +1 -0
- package/dist/devtools/panel.html +2 -2
- package/dist/react-native/cjs/package.json +3 -0
- package/dist/react-native/cjs/react-native.js +38 -0
- package/dist/react-native/cjs/src/react-native/adapters/async-storage.js +60 -0
- package/dist/react-native/cjs/src/react-native/adapters/index.js +10 -0
- package/dist/react-native/cjs/src/react-native/adapters/mmkv.js +143 -0
- package/dist/react-native/cjs/src/react-native/adapters/secure-storage.js +51 -0
- package/dist/react-native/cjs/src/react-native/entry-preview-pagination.js +198 -0
- package/dist/react-native/cjs/src/react-native/export-snapshot.js +70 -0
- package/dist/react-native/cjs/src/react-native/full-entry-request.js +72 -0
- package/dist/react-native/cjs/src/react-native/import.js +185 -0
- package/dist/react-native/cjs/src/react-native/storage-discovery.js +69 -0
- package/dist/react-native/cjs/src/react-native/storage-view.js +128 -0
- package/dist/react-native/cjs/src/react-native/useRozeniteStoragePlugin.js +145 -0
- package/dist/react-native/cjs/src/react-native/useStorageAgentTools.js +228 -0
- package/dist/react-native/cjs/src/shared/agent-tools.js +119 -0
- package/dist/react-native/cjs/src/shared/entry-preview.js +48 -0
- package/dist/react-native/cjs/src/shared/messaging.js +2 -0
- package/dist/react-native/cjs/src/shared/snapshot.js +185 -0
- package/dist/react-native/cjs/src/shared/types.js +13 -0
- package/dist/react-native/package.json +3 -0
- package/dist/react-native/react-native.d.ts +7 -0
- package/dist/react-native/react-native.js +40 -0
- package/dist/react-native/src/react-native/adapters/async-storage.d.ts +34 -0
- package/dist/react-native/src/react-native/adapters/async-storage.js +56 -0
- package/dist/react-native/src/react-native/adapters/index.d.ts +6 -0
- package/dist/react-native/src/react-native/adapters/index.js +3 -0
- package/dist/react-native/src/react-native/adapters/mmkv.d.ts +13 -0
- package/dist/react-native/src/react-native/adapters/mmkv.js +139 -0
- package/dist/react-native/src/react-native/adapters/secure-storage.d.ts +18 -0
- package/dist/react-native/src/react-native/adapters/secure-storage.js +47 -0
- package/dist/react-native/src/react-native/entry-preview-pagination.d.ts +7 -0
- package/dist/react-native/src/react-native/entry-preview-pagination.js +194 -0
- package/dist/react-native/src/react-native/export-snapshot.d.ts +5 -0
- package/dist/react-native/src/react-native/export-snapshot.js +66 -0
- package/dist/react-native/src/react-native/full-entry-request.d.ts +5 -0
- package/dist/react-native/src/react-native/full-entry-request.js +68 -0
- package/dist/react-native/src/react-native/import.d.ts +7 -0
- package/dist/react-native/src/react-native/import.js +180 -0
- package/dist/react-native/src/react-native/storage-discovery.d.ts +5 -0
- package/dist/react-native/src/react-native/storage-discovery.js +65 -0
- package/dist/react-native/src/react-native/storage-view.d.ts +19 -0
- package/dist/react-native/src/react-native/storage-view.js +123 -0
- package/dist/react-native/src/react-native/useRozeniteStoragePlugin.d.ts +6 -0
- package/dist/react-native/src/react-native/useRozeniteStoragePlugin.js +141 -0
- package/dist/react-native/src/react-native/useStorageAgentTools.d.ts +37 -0
- package/dist/react-native/src/react-native/useStorageAgentTools.js +223 -0
- package/dist/react-native/src/shared/agent-tools.d.ts +76 -0
- package/dist/react-native/src/shared/agent-tools.js +116 -0
- package/dist/react-native/src/shared/entry-preview.d.ts +4 -0
- package/dist/react-native/src/shared/entry-preview.js +44 -0
- package/dist/react-native/src/shared/messaging.d.ts +134 -0
- package/dist/react-native/src/shared/messaging.js +1 -0
- package/dist/react-native/src/shared/snapshot.d.ts +53 -0
- package/dist/react-native/src/shared/snapshot.js +179 -0
- package/dist/react-native/src/shared/types.d.ts +78 -0
- package/dist/react-native/src/shared/types.js +8 -0
- package/dist/rozenite.json +1 -1
- package/dist/sdk/package.json +3 -0
- package/dist/sdk/sdk.d.ts +44 -0
- package/dist/sdk/sdk.js +9 -0
- package/dist/sdk/src/shared/agent-tools.d.ts +76 -0
- package/dist/sdk/src/shared/agent-tools.js +119 -0
- package/dist/sdk/src/shared/types.d.ts +78 -0
- package/dist/sdk/src/shared/types.js +13 -0
- package/package.json +16 -16
- package/react-native.ts +8 -1
- package/rozenite.config.ts +1 -0
- package/src/__tests__/release-bundle.test.ts +32 -0
- package/dist/devtools/assets/panel-D5z1BY8U.js +0 -32
- package/dist/devtools/assets/panel-DVlWRg6d.css +0 -1
- package/dist/react-native/chunks/async-storage.require.cjs +0 -1
- package/dist/react-native/chunks/async-storage.require.js +0 -53
- package/dist/react-native/chunks/index.require.cjs +0 -1
- package/dist/react-native/chunks/index.require.js +0 -103
- package/dist/react-native/chunks/secure-storage.require.cjs +0 -1
- package/dist/react-native/chunks/secure-storage.require.js +0 -46
- package/dist/react-native/chunks/types.cjs +0 -1
- package/dist/react-native/chunks/types.js +0 -11
- package/dist/react-native/chunks/useRozeniteStoragePlugin.require.cjs +0 -1
- package/dist/react-native/chunks/useRozeniteStoragePlugin.require.js +0 -1037
- package/dist/react-native/index.cjs +0 -1
- package/dist/react-native/index.d.ts +0 -365
- package/dist/react-native/index.js +0 -28
- package/dist/sdk/index.cjs +0 -1
- package/dist/sdk/index.d.ts +0 -206
- package/dist/sdk/index.js +0 -122
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleExportSnapshotRequest = void 0;
|
|
4
|
+
const snapshot_1 = require("../shared/snapshot");
|
|
5
|
+
const getRequestId = (payload) => typeof payload === 'object' &&
|
|
6
|
+
payload != null &&
|
|
7
|
+
!Array.isArray(payload) &&
|
|
8
|
+
typeof payload.requestId === 'string'
|
|
9
|
+
? payload.requestId
|
|
10
|
+
: '';
|
|
11
|
+
const isTarget = (value) => typeof value === 'object' &&
|
|
12
|
+
value != null &&
|
|
13
|
+
!Array.isArray(value) &&
|
|
14
|
+
typeof value.adapterId === 'string' &&
|
|
15
|
+
value.adapterId.trim().length > 0 &&
|
|
16
|
+
typeof value.storageId === 'string' &&
|
|
17
|
+
value.storageId.trim().length > 0;
|
|
18
|
+
const isSameTarget = (left, right) => left.adapterId === right.adapterId && left.storageId === right.storageId;
|
|
19
|
+
const validateRequest = (payload) => typeof payload === 'object' &&
|
|
20
|
+
payload != null &&
|
|
21
|
+
!Array.isArray(payload) &&
|
|
22
|
+
payload.type === 'export-snapshot' &&
|
|
23
|
+
typeof payload.requestId === 'string' &&
|
|
24
|
+
payload.requestId.trim().length > 0 &&
|
|
25
|
+
isTarget(payload.target);
|
|
26
|
+
const handleExportSnapshotRequest = async (views, payload) => {
|
|
27
|
+
const requestId = getRequestId(payload);
|
|
28
|
+
if (!validateRequest(payload)) {
|
|
29
|
+
return {
|
|
30
|
+
type: 'storage-request-error',
|
|
31
|
+
requestId,
|
|
32
|
+
code: 'INVALID_REQUEST',
|
|
33
|
+
message: 'Export requests require a valid target and request ID.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const view = views.find((candidate) => isSameTarget(candidate.target, payload.target));
|
|
37
|
+
if (!view) {
|
|
38
|
+
return {
|
|
39
|
+
type: 'storage-request-error',
|
|
40
|
+
requestId,
|
|
41
|
+
code: 'TARGET_NOT_FOUND',
|
|
42
|
+
message: 'The requested storage target was not found.',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
// getAllEntries applies the storage view's blacklist before reading values.
|
|
47
|
+
const entries = await view.getAllEntries();
|
|
48
|
+
return {
|
|
49
|
+
type: 'export-snapshot-result',
|
|
50
|
+
requestId,
|
|
51
|
+
target: view.target,
|
|
52
|
+
snapshot: (0, snapshot_1.buildSnapshot)({
|
|
53
|
+
target: view.target,
|
|
54
|
+
adapterName: view.adapterName,
|
|
55
|
+
storageName: view.storageName,
|
|
56
|
+
capabilities: view.capabilities,
|
|
57
|
+
entries,
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return {
|
|
63
|
+
type: 'storage-request-error',
|
|
64
|
+
requestId,
|
|
65
|
+
code: 'READ_FAILED',
|
|
66
|
+
message: 'Failed to export the requested storage.',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
exports.handleExportSnapshotRequest = handleExportSnapshotRequest;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleFullEntryRequest = void 0;
|
|
4
|
+
const getRequestId = (payload) => typeof payload === 'object' &&
|
|
5
|
+
payload != null &&
|
|
6
|
+
!Array.isArray(payload) &&
|
|
7
|
+
typeof payload.requestId === 'string'
|
|
8
|
+
? payload.requestId
|
|
9
|
+
: '';
|
|
10
|
+
const isTarget = (value) => typeof value === 'object' &&
|
|
11
|
+
value != null &&
|
|
12
|
+
!Array.isArray(value) &&
|
|
13
|
+
typeof value.adapterId === 'string' &&
|
|
14
|
+
value.adapterId.trim().length > 0 &&
|
|
15
|
+
typeof value.storageId === 'string' &&
|
|
16
|
+
value.storageId.trim().length > 0;
|
|
17
|
+
const isSameTarget = (left, right) => left.adapterId === right.adapterId && left.storageId === right.storageId;
|
|
18
|
+
const validateRequest = (payload) => typeof payload === 'object' &&
|
|
19
|
+
payload != null &&
|
|
20
|
+
!Array.isArray(payload) &&
|
|
21
|
+
payload.type === 'get-entry' &&
|
|
22
|
+
typeof payload.requestId === 'string' &&
|
|
23
|
+
payload.requestId.trim().length > 0 &&
|
|
24
|
+
isTarget(payload.target) &&
|
|
25
|
+
typeof payload.key === 'string';
|
|
26
|
+
const handleFullEntryRequest = async (views, payload) => {
|
|
27
|
+
const requestId = getRequestId(payload);
|
|
28
|
+
if (!validateRequest(payload)) {
|
|
29
|
+
return {
|
|
30
|
+
type: 'storage-request-error',
|
|
31
|
+
requestId,
|
|
32
|
+
code: 'INVALID_REQUEST',
|
|
33
|
+
message: 'Full entry requests require a valid target, request ID, and string key.',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const view = views.find((candidate) => isSameTarget(candidate.target, payload.target));
|
|
37
|
+
if (!view) {
|
|
38
|
+
return {
|
|
39
|
+
type: 'storage-request-error',
|
|
40
|
+
requestId,
|
|
41
|
+
code: 'TARGET_NOT_FOUND',
|
|
42
|
+
message: 'The requested storage target was not found.',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
// StorageView applies the same blacklist policy used by preview requests.
|
|
47
|
+
const entry = await view.get(payload.key);
|
|
48
|
+
if (!entry) {
|
|
49
|
+
return {
|
|
50
|
+
type: 'storage-request-error',
|
|
51
|
+
requestId,
|
|
52
|
+
code: 'ENTRY_NOT_FOUND',
|
|
53
|
+
message: 'The requested storage entry was not found.',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
type: 'entry',
|
|
58
|
+
requestId,
|
|
59
|
+
target: view.target,
|
|
60
|
+
entry,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return {
|
|
65
|
+
type: 'storage-request-error',
|
|
66
|
+
requestId,
|
|
67
|
+
code: 'READ_FAILED',
|
|
68
|
+
message: 'Failed to read the requested storage entry.',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.handleFullEntryRequest = handleFullEntryRequest;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleImportEntries = exports.handleImportPreviewRequest = void 0;
|
|
4
|
+
const snapshot_1 = require("../shared/snapshot");
|
|
5
|
+
const types_1 = require("../shared/types");
|
|
6
|
+
const isSameTarget = (left, right) => left.adapterId === right.adapterId && left.storageId === right.storageId;
|
|
7
|
+
const isTarget = (value) => typeof value === 'object' &&
|
|
8
|
+
value != null &&
|
|
9
|
+
!Array.isArray(value) &&
|
|
10
|
+
typeof value.adapterId === 'string' &&
|
|
11
|
+
value.adapterId.trim().length > 0 &&
|
|
12
|
+
typeof value.storageId === 'string' &&
|
|
13
|
+
value.storageId.trim().length > 0;
|
|
14
|
+
const getRequestId = (payload) => typeof payload === 'object' &&
|
|
15
|
+
payload != null &&
|
|
16
|
+
!Array.isArray(payload) &&
|
|
17
|
+
typeof payload.requestId === 'string'
|
|
18
|
+
? payload.requestId
|
|
19
|
+
: '';
|
|
20
|
+
const isEntry = (value) => {
|
|
21
|
+
if (typeof value !== 'object' || value == null || Array.isArray(value)) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const entry = value;
|
|
25
|
+
if (typeof entry.key !== 'string')
|
|
26
|
+
return false;
|
|
27
|
+
if (entry.type === 'string')
|
|
28
|
+
return typeof entry.value === 'string';
|
|
29
|
+
if (entry.type === 'number')
|
|
30
|
+
return typeof entry.value === 'number';
|
|
31
|
+
if (entry.type === 'boolean')
|
|
32
|
+
return typeof entry.value === 'boolean';
|
|
33
|
+
return (entry.type === 'buffer' &&
|
|
34
|
+
Array.isArray(entry.value) &&
|
|
35
|
+
entry.value.every((byte) => typeof byte === 'number' && Number.isInteger(byte) && byte >= 0 && byte <= 255));
|
|
36
|
+
};
|
|
37
|
+
const isBlacklisted = (view, key) => {
|
|
38
|
+
if (!view.blacklist)
|
|
39
|
+
return false;
|
|
40
|
+
view.blacklist.lastIndex = 0;
|
|
41
|
+
return view.blacklist.test(key);
|
|
42
|
+
};
|
|
43
|
+
const findView = (views, target) => views.find((candidate) => isSameTarget(candidate.target, target));
|
|
44
|
+
const handleImportPreviewRequest = async (views, payload) => {
|
|
45
|
+
const requestId = getRequestId(payload);
|
|
46
|
+
const request = payload;
|
|
47
|
+
const parsedSnapshot = (0, snapshot_1.parseSnapshot)(request.snapshot);
|
|
48
|
+
if (typeof request !== 'object' ||
|
|
49
|
+
request == null ||
|
|
50
|
+
request.type !== 'preview-import' ||
|
|
51
|
+
typeof request.requestId !== 'string' ||
|
|
52
|
+
request.requestId.trim().length === 0 ||
|
|
53
|
+
!isTarget(request.target) ||
|
|
54
|
+
!parsedSnapshot.ok) {
|
|
55
|
+
return {
|
|
56
|
+
type: 'storage-request-error',
|
|
57
|
+
requestId,
|
|
58
|
+
code: 'INVALID_REQUEST',
|
|
59
|
+
message: 'Import preview requests require a valid target, request ID, and snapshot.',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const view = findView(views, request.target);
|
|
63
|
+
if (!view) {
|
|
64
|
+
return {
|
|
65
|
+
type: 'storage-request-error',
|
|
66
|
+
requestId,
|
|
67
|
+
code: 'TARGET_NOT_FOUND',
|
|
68
|
+
message: 'The requested storage target was not found.',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const entryKeys = new Set(await view.getAllKeys());
|
|
73
|
+
return {
|
|
74
|
+
type: 'import-preview',
|
|
75
|
+
requestId,
|
|
76
|
+
target: view.target,
|
|
77
|
+
preview: (0, snapshot_1.computePreview)(parsedSnapshot.snapshot, {
|
|
78
|
+
target: view.target,
|
|
79
|
+
capabilities: view.capabilities,
|
|
80
|
+
entryKeys,
|
|
81
|
+
isBlacklisted: (key) => isBlacklisted(view, key),
|
|
82
|
+
}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return {
|
|
87
|
+
type: 'storage-request-error',
|
|
88
|
+
requestId,
|
|
89
|
+
code: 'READ_FAILED',
|
|
90
|
+
message: 'Failed to inspect the requested storage for import.',
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
exports.handleImportPreviewRequest = handleImportPreviewRequest;
|
|
95
|
+
const handleImportEntries = async (views, payload, emit) => {
|
|
96
|
+
const requestId = getRequestId(payload);
|
|
97
|
+
const event = payload;
|
|
98
|
+
if (typeof event !== 'object' ||
|
|
99
|
+
event == null ||
|
|
100
|
+
event.type !== 'import-entries' ||
|
|
101
|
+
typeof event.requestId !== 'string' ||
|
|
102
|
+
event.requestId.trim().length === 0 ||
|
|
103
|
+
!isTarget(event.target) ||
|
|
104
|
+
!Array.isArray(event.entries) ||
|
|
105
|
+
!event.entries.every(isEntry)) {
|
|
106
|
+
emit({
|
|
107
|
+
type: 'import-result',
|
|
108
|
+
requestId,
|
|
109
|
+
target: isTarget(event.target) ? event.target : { adapterId: '', storageId: '' },
|
|
110
|
+
ok: false,
|
|
111
|
+
written: 0,
|
|
112
|
+
total: 0,
|
|
113
|
+
error: 'Import requests require a valid target, request ID, and entries.',
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const view = findView(views, event.target);
|
|
118
|
+
if (!view) {
|
|
119
|
+
emit({
|
|
120
|
+
type: 'import-result',
|
|
121
|
+
requestId: event.requestId,
|
|
122
|
+
target: event.target,
|
|
123
|
+
ok: false,
|
|
124
|
+
written: 0,
|
|
125
|
+
total: event.entries.length,
|
|
126
|
+
error: 'Target storage not found',
|
|
127
|
+
});
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Recheck policy at apply time so a preview cannot bypass a changed target.
|
|
131
|
+
const entries = event.entries.filter((entry) => (0, types_1.supportsType)(view.capabilities, entry.type) && !isBlacklisted(view, entry.key));
|
|
132
|
+
for (let i = 0; i < entries.length; i++) {
|
|
133
|
+
const entry = entries[i];
|
|
134
|
+
try {
|
|
135
|
+
await view.set(entry);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if (i > 0) {
|
|
139
|
+
const entryCount = (await view.getAllKeys()).length;
|
|
140
|
+
emit({
|
|
141
|
+
type: 'storage-invalidated',
|
|
142
|
+
target: event.target,
|
|
143
|
+
operation: 'import',
|
|
144
|
+
entryCount,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
emit({
|
|
148
|
+
type: 'import-result',
|
|
149
|
+
requestId: event.requestId,
|
|
150
|
+
target: event.target,
|
|
151
|
+
ok: false,
|
|
152
|
+
written: i,
|
|
153
|
+
total: entries.length,
|
|
154
|
+
failedKey: entry.key,
|
|
155
|
+
error: error instanceof Error ? error.message : String(error),
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
emit({
|
|
160
|
+
type: 'import-progress',
|
|
161
|
+
requestId: event.requestId,
|
|
162
|
+
target: event.target,
|
|
163
|
+
written: i + 1,
|
|
164
|
+
total: entries.length,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (entries.length > 0) {
|
|
168
|
+
const entryCount = (await view.getAllKeys()).length;
|
|
169
|
+
emit({
|
|
170
|
+
type: 'storage-invalidated',
|
|
171
|
+
target: event.target,
|
|
172
|
+
operation: 'import',
|
|
173
|
+
entryCount,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
emit({
|
|
177
|
+
type: 'import-result',
|
|
178
|
+
requestId: event.requestId,
|
|
179
|
+
target: event.target,
|
|
180
|
+
ok: true,
|
|
181
|
+
written: entries.length,
|
|
182
|
+
total: entries.length,
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
exports.handleImportEntries = handleImportEntries;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleStorageDiscoveryRequest = void 0;
|
|
4
|
+
const getRequestId = (payload) => {
|
|
5
|
+
if (typeof payload === 'object' &&
|
|
6
|
+
payload != null &&
|
|
7
|
+
!Array.isArray(payload) &&
|
|
8
|
+
typeof payload.requestId === 'string') {
|
|
9
|
+
return payload.requestId;
|
|
10
|
+
}
|
|
11
|
+
return '';
|
|
12
|
+
};
|
|
13
|
+
const validateRequest = (payload) => typeof payload === 'object' &&
|
|
14
|
+
payload != null &&
|
|
15
|
+
!Array.isArray(payload) &&
|
|
16
|
+
payload.type === 'discover-storages' &&
|
|
17
|
+
typeof payload.requestId === 'string' &&
|
|
18
|
+
payload.requestId.trim().length > 0;
|
|
19
|
+
const toDescriptor = async (view) => ({
|
|
20
|
+
target: view.target,
|
|
21
|
+
adapterName: view.adapterName,
|
|
22
|
+
storageName: view.storageName,
|
|
23
|
+
entryCount: (await view.getAllKeys()).length,
|
|
24
|
+
capabilities: view.capabilities,
|
|
25
|
+
supportsSubscriptions: view.supportsSubscriptions,
|
|
26
|
+
});
|
|
27
|
+
const hasDuplicateTargets = (views) => {
|
|
28
|
+
const targets = new Set();
|
|
29
|
+
return views.some(({ target }) => {
|
|
30
|
+
const id = `${target.adapterId}:${target.storageId}`;
|
|
31
|
+
if (targets.has(id)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
targets.add(id);
|
|
35
|
+
return false;
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
const handleStorageDiscoveryRequest = async (views, payload) => {
|
|
39
|
+
const requestId = getRequestId(payload);
|
|
40
|
+
if (!validateRequest(payload)) {
|
|
41
|
+
return {
|
|
42
|
+
type: 'storage-request-error',
|
|
43
|
+
requestId,
|
|
44
|
+
code: 'INVALID_REQUEST',
|
|
45
|
+
message: 'Storage discovery requires a non-empty request ID.',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
if (hasDuplicateTargets(views)) {
|
|
50
|
+
return {
|
|
51
|
+
type: 'storage-request-error',
|
|
52
|
+
requestId,
|
|
53
|
+
code: 'INVALID_REQUEST',
|
|
54
|
+
message: 'Configured storages must have unique targets.',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const storages = await Promise.all(views.map(toDescriptor));
|
|
58
|
+
return { type: 'storage-descriptors', requestId, storages };
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return {
|
|
62
|
+
type: 'storage-request-error',
|
|
63
|
+
requestId,
|
|
64
|
+
code: 'READ_FAILED',
|
|
65
|
+
message: 'Failed to discover configured storages.',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
exports.handleStorageDiscoveryRequest = handleStorageDiscoveryRequest;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStorageViews = exports.createStorageView = void 0;
|
|
4
|
+
const types_1 = require("../shared/types");
|
|
5
|
+
const isAsyncStorage = (storage) => storage.kind === 'async';
|
|
6
|
+
const shouldFilterKey = (storage, key) => {
|
|
7
|
+
if (storage.shouldFilterKey?.(key)) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (!storage.blacklist) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
storage.blacklist.lastIndex = 0;
|
|
14
|
+
return storage.blacklist.test(key);
|
|
15
|
+
};
|
|
16
|
+
const checkTypeSupport = (capabilities, type, target) => {
|
|
17
|
+
if ((0, types_1.supportsType)(capabilities, type)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Type "${type}" is not supported by storage "${target.storageId}" in adapter "${target.adapterId}".`);
|
|
21
|
+
};
|
|
22
|
+
const getAllKeys = async (storage) => {
|
|
23
|
+
if (isAsyncStorage(storage)) {
|
|
24
|
+
return storage.getAllKeys();
|
|
25
|
+
}
|
|
26
|
+
return storage.getAllKeys();
|
|
27
|
+
};
|
|
28
|
+
const getEntry = async (storage, key) => {
|
|
29
|
+
if (isAsyncStorage(storage)) {
|
|
30
|
+
return storage.get(key);
|
|
31
|
+
}
|
|
32
|
+
return storage.get(key);
|
|
33
|
+
};
|
|
34
|
+
const setEntry = async (storage, entry) => {
|
|
35
|
+
if (isAsyncStorage(storage)) {
|
|
36
|
+
await storage.set(entry);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
storage.set(entry);
|
|
40
|
+
};
|
|
41
|
+
const deleteEntry = async (storage, key) => {
|
|
42
|
+
if (isAsyncStorage(storage)) {
|
|
43
|
+
await storage.delete(key);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
storage.delete(key);
|
|
47
|
+
};
|
|
48
|
+
const clearStorage = async (storage) => {
|
|
49
|
+
await storage.clear();
|
|
50
|
+
};
|
|
51
|
+
const createStorageView = (adapter, storageNode) => {
|
|
52
|
+
const storage = storageNode.storage;
|
|
53
|
+
const subscribe = storage.subscribe;
|
|
54
|
+
const locallyMutatingKeys = new Set();
|
|
55
|
+
let isLocallyPurging = false;
|
|
56
|
+
const target = {
|
|
57
|
+
adapterId: adapter.id,
|
|
58
|
+
storageId: storageNode.id,
|
|
59
|
+
};
|
|
60
|
+
const get = async (key) => {
|
|
61
|
+
if (shouldFilterKey(storageNode, key)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
return getEntry(storage, key);
|
|
65
|
+
};
|
|
66
|
+
const getAllEntries = async () => {
|
|
67
|
+
const keys = await getAllKeys(storage);
|
|
68
|
+
const visibleEntries = await Promise.all(keys.filter((key) => !shouldFilterKey(storageNode, key)).map((key) => getEntry(storage, key)));
|
|
69
|
+
return visibleEntries.filter((entry) => !!entry);
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
id: (0, types_1.getStorageViewId)(target),
|
|
73
|
+
target,
|
|
74
|
+
adapterName: adapter.name,
|
|
75
|
+
storageName: storageNode.name,
|
|
76
|
+
capabilities: storageNode.capabilities,
|
|
77
|
+
supportsSubscriptions: subscribe != null,
|
|
78
|
+
blacklist: storageNode.blacklist,
|
|
79
|
+
get,
|
|
80
|
+
set: async (entry) => {
|
|
81
|
+
checkTypeSupport(storageNode.capabilities, entry.type, target);
|
|
82
|
+
locallyMutatingKeys.add(entry.key);
|
|
83
|
+
try {
|
|
84
|
+
await setEntry(storage, entry);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
locallyMutatingKeys.delete(entry.key);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
delete: async (key) => {
|
|
91
|
+
locallyMutatingKeys.add(key);
|
|
92
|
+
try {
|
|
93
|
+
await deleteEntry(storage, key);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
locallyMutatingKeys.delete(key);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
purge: async () => {
|
|
100
|
+
isLocallyPurging = true;
|
|
101
|
+
try {
|
|
102
|
+
await clearStorage(storage);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
isLocallyPurging = false;
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
getAllKeys: async () => {
|
|
109
|
+
const keys = await getAllKeys(storage);
|
|
110
|
+
return keys.filter((key) => !shouldFilterKey(storageNode, key));
|
|
111
|
+
},
|
|
112
|
+
getAllEntries,
|
|
113
|
+
...(subscribe
|
|
114
|
+
? {
|
|
115
|
+
watch: async (onChange) => subscribe((key) => {
|
|
116
|
+
if (!isLocallyPurging &&
|
|
117
|
+
!locallyMutatingKeys.has(key) &&
|
|
118
|
+
!shouldFilterKey(storageNode, key)) {
|
|
119
|
+
onChange(key);
|
|
120
|
+
}
|
|
121
|
+
}),
|
|
122
|
+
}
|
|
123
|
+
: {}),
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
exports.createStorageView = createStorageView;
|
|
127
|
+
const createStorageViews = (storages) => storages.flatMap((adapter) => adapter.storages.map((storageNode) => (0, exports.createStorageView)(adapter, storageNode)));
|
|
128
|
+
exports.createStorageViews = createStorageViews;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useRozeniteStoragePlugin = void 0;
|
|
4
|
+
const plugin_bridge_1 = require("@rozenite/plugin-bridge");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const import_1 = require("./import");
|
|
7
|
+
const storage_discovery_1 = require("./storage-discovery");
|
|
8
|
+
const entry_preview_pagination_1 = require("./entry-preview-pagination");
|
|
9
|
+
const full_entry_request_1 = require("./full-entry-request");
|
|
10
|
+
const export_snapshot_1 = require("./export-snapshot");
|
|
11
|
+
const storage_view_1 = require("./storage-view");
|
|
12
|
+
const useStorageAgentTools_1 = require("./useStorageAgentTools");
|
|
13
|
+
const useRozeniteStoragePlugin = ({ storages }) => {
|
|
14
|
+
const views = (0, react_1.useMemo)(() => (0, storage_view_1.createStorageViews)(storages), [storages]);
|
|
15
|
+
(0, useStorageAgentTools_1.useStorageAgentTools)(views);
|
|
16
|
+
const client = (0, plugin_bridge_1.useRozeniteDevToolsClient)({
|
|
17
|
+
pluginId: '@rozenite/storage-plugin',
|
|
18
|
+
});
|
|
19
|
+
(0, react_1.useEffect)(() => {
|
|
20
|
+
if (!client) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const viewSubscriptions = [];
|
|
24
|
+
let disposed = false;
|
|
25
|
+
const sendInvalidation = async (target, key, operation) => {
|
|
26
|
+
const view = views.find((candidate) => candidate.target.adapterId === target.adapterId &&
|
|
27
|
+
candidate.target.storageId === target.storageId);
|
|
28
|
+
const entryCount = view ? (await view.getAllKeys()).length : 0;
|
|
29
|
+
client.send('storage-invalidated', {
|
|
30
|
+
type: 'storage-invalidated',
|
|
31
|
+
target,
|
|
32
|
+
key,
|
|
33
|
+
operation,
|
|
34
|
+
entryCount,
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
// Prevent one storage watcher failure from breaking the whole plugin.
|
|
38
|
+
void Promise.all(views
|
|
39
|
+
.filter((view) => view.supportsSubscriptions && view.watch)
|
|
40
|
+
.map(async (view) => {
|
|
41
|
+
try {
|
|
42
|
+
const watch = view.watch;
|
|
43
|
+
if (!watch) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const subscription = await watch((key) => {
|
|
47
|
+
void sendInvalidation(view.target, key);
|
|
48
|
+
});
|
|
49
|
+
if (disposed) {
|
|
50
|
+
subscription.remove();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
viewSubscriptions.push(subscription);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.warn(`[Rozenite] Storage Plugin: Failed to attach watcher for ${view.target.adapterId}/${view.target.storageId}.`, error);
|
|
57
|
+
}
|
|
58
|
+
}));
|
|
59
|
+
const messageSubscriptions = [
|
|
60
|
+
client.onMessage('set-entry', async ({ target, entry }) => {
|
|
61
|
+
const view = views.find((candidate) => candidate.target.adapterId === target.adapterId &&
|
|
62
|
+
candidate.target.storageId === target.storageId);
|
|
63
|
+
if (!view) {
|
|
64
|
+
console.warn(`[Rozenite] Storage Plugin: Storage target not found for ${target.adapterId}/${target.storageId}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
await view.set(entry);
|
|
69
|
+
await sendInvalidation(view.target, entry.key, 'set');
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.warn(`[Rozenite] Storage Plugin: Failed to set entry in ${target.adapterId}/${target.storageId}.`, error);
|
|
73
|
+
}
|
|
74
|
+
}),
|
|
75
|
+
client.onMessage('delete-entry', async ({ target, key }) => {
|
|
76
|
+
const view = views.find((candidate) => candidate.target.adapterId === target.adapterId &&
|
|
77
|
+
candidate.target.storageId === target.storageId);
|
|
78
|
+
if (!view) {
|
|
79
|
+
console.warn(`[Rozenite] Storage Plugin: Storage target not found for ${target.adapterId}/${target.storageId}`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
await view.delete(key);
|
|
84
|
+
await sendInvalidation(view.target, key, 'delete');
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.warn(`[Rozenite] Storage Plugin: Failed to delete entry in ${target.adapterId}/${target.storageId}.`, error);
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
client.onMessage('purge-storage', async ({ target }) => {
|
|
91
|
+
const view = views.find((candidate) => candidate.target.adapterId === target.adapterId &&
|
|
92
|
+
candidate.target.storageId === target.storageId);
|
|
93
|
+
if (!view) {
|
|
94
|
+
console.warn(`[Rozenite] Storage Plugin: Storage target not found for ${target.adapterId}/${target.storageId}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await view.purge();
|
|
99
|
+
await sendInvalidation(view.target, undefined, 'purge');
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
console.warn(`[Rozenite] Storage Plugin: Failed to purge storage in ${target.adapterId}/${target.storageId}.`, error);
|
|
103
|
+
}
|
|
104
|
+
}),
|
|
105
|
+
client.onMessage('discover-storages', async (event) => {
|
|
106
|
+
const response = await (0, storage_discovery_1.handleStorageDiscoveryRequest)(views, event);
|
|
107
|
+
client.send(response.type, response);
|
|
108
|
+
}),
|
|
109
|
+
client.onMessage('list-entry-previews', async (event) => {
|
|
110
|
+
const response = await (0, entry_preview_pagination_1.handleListEntryPreviewsRequest)(views, event);
|
|
111
|
+
client.send(response.type, response);
|
|
112
|
+
}),
|
|
113
|
+
client.onMessage('get-entry', async (event) => {
|
|
114
|
+
const response = await (0, full_entry_request_1.handleFullEntryRequest)(views, event);
|
|
115
|
+
client.send(response.type, response);
|
|
116
|
+
}),
|
|
117
|
+
client.onMessage('export-snapshot', async (event) => {
|
|
118
|
+
const response = await (0, export_snapshot_1.handleExportSnapshotRequest)(views, event);
|
|
119
|
+
client.send(response.type, response);
|
|
120
|
+
}),
|
|
121
|
+
client.onMessage('preview-import', async (event) => {
|
|
122
|
+
const response = await (0, import_1.handleImportPreviewRequest)(views, event);
|
|
123
|
+
client.send(response.type, response);
|
|
124
|
+
}),
|
|
125
|
+
client.onMessage('import-entries', async (event) => {
|
|
126
|
+
await (0, import_1.handleImportEntries)(views, event, (out) => {
|
|
127
|
+
client.send(out.type, out);
|
|
128
|
+
});
|
|
129
|
+
}),
|
|
130
|
+
];
|
|
131
|
+
// Sent last, so a panel that reacts to it is guaranteed to find the
|
|
132
|
+
// handlers above already listening. The panel is recreated on every app
|
|
133
|
+
// reload and asks for storages as soon as it boots, which usually beats the
|
|
134
|
+
// app's React tree to the punch; without this the panel's only request is
|
|
135
|
+
// dropped and it waits for storages forever.
|
|
136
|
+
client.send('device-ready', { type: 'device-ready' });
|
|
137
|
+
return () => {
|
|
138
|
+
disposed = true;
|
|
139
|
+
viewSubscriptions.forEach((subscription) => subscription.remove());
|
|
140
|
+
messageSubscriptions.forEach((subscription) => subscription.remove());
|
|
141
|
+
};
|
|
142
|
+
}, [client, views]);
|
|
143
|
+
return client;
|
|
144
|
+
};
|
|
145
|
+
exports.useRozeniteStoragePlugin = useRozeniteStoragePlugin;
|