@useavalon/avalon 0.1.11 → 0.1.13
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/README.md +54 -54
- package/mod.ts +302 -302
- package/package.json +49 -26
- package/src/build/integration-bundler-plugin.ts +116 -116
- package/src/build/integration-config.ts +168 -168
- package/src/build/integration-detection-plugin.ts +117 -117
- package/src/build/integration-resolver-plugin.ts +90 -90
- package/src/build/island-manifest.ts +269 -269
- package/src/build/island-types-generator.ts +476 -476
- package/src/build/mdx-island-transform.ts +464 -464
- package/src/build/mdx-plugin.ts +98 -98
- package/src/build/page-island-transform.ts +598 -598
- package/src/build/prop-extractors/index.ts +21 -21
- package/src/build/prop-extractors/lit.ts +140 -140
- package/src/build/prop-extractors/qwik.ts +16 -16
- package/src/build/prop-extractors/solid.ts +125 -125
- package/src/build/prop-extractors/svelte.ts +194 -194
- package/src/build/prop-extractors/vue.ts +111 -111
- package/src/build/sidecar-file-manager.ts +104 -104
- package/src/build/sidecar-renderer.ts +30 -30
- package/src/client/adapters/index.ts +21 -13
- package/src/client/components.ts +35 -35
- package/src/client/css-hmr-handler.ts +344 -344
- package/src/client/framework-adapter.ts +462 -462
- package/src/client/hmr-coordinator.ts +396 -396
- package/src/client/hmr-error-overlay.js +533 -533
- package/src/client/main.js +824 -816
- package/src/client/types/framework-runtime.d.ts +68 -68
- package/src/client/types/vite-hmr.d.ts +46 -46
- package/src/client/types/vite-virtual-modules.d.ts +70 -60
- package/src/components/Image.tsx +123 -123
- package/src/components/IslandErrorBoundary.tsx +145 -145
- package/src/components/LayoutDataErrorBoundary.tsx +141 -141
- package/src/components/LayoutErrorBoundary.tsx +127 -127
- package/src/components/PersistentIsland.tsx +52 -52
- package/src/components/StreamingErrorBoundary.tsx +233 -233
- package/src/components/StreamingLayout.tsx +538 -538
- package/src/core/components/component-analyzer.ts +192 -192
- package/src/core/components/component-detection.ts +508 -508
- package/src/core/components/enhanced-framework-detector.ts +500 -500
- package/src/core/components/framework-registry.ts +563 -563
- package/src/core/content/mdx-processor.ts +46 -46
- package/src/core/integrations/index.ts +19 -19
- package/src/core/integrations/loader.ts +125 -125
- package/src/core/integrations/registry.ts +175 -175
- package/src/core/islands/island-persistence.ts +325 -325
- package/src/core/islands/island-state-serializer.ts +258 -258
- package/src/core/islands/persistent-island-context.tsx +80 -80
- package/src/core/islands/use-persistent-state.ts +68 -68
- package/src/core/layout/enhanced-layout-resolver.ts +322 -322
- package/src/core/layout/layout-cache-manager.ts +485 -485
- package/src/core/layout/layout-composer.ts +357 -357
- package/src/core/layout/layout-data-loader.ts +516 -516
- package/src/core/layout/layout-discovery.ts +243 -243
- package/src/core/layout/layout-matcher.ts +299 -299
- package/src/core/layout/layout-types.ts +110 -110
- package/src/core/modules/framework-module-resolver.ts +273 -273
- package/src/islands/component-analysis.ts +213 -213
- package/src/islands/css-utils.ts +565 -565
- package/src/islands/discovery/index.ts +80 -80
- package/src/islands/discovery/registry.ts +340 -340
- package/src/islands/discovery/resolver.ts +477 -477
- package/src/islands/discovery/scanner.ts +386 -386
- package/src/islands/discovery/types.ts +117 -117
- package/src/islands/discovery/validator.ts +544 -544
- package/src/islands/discovery/watcher.ts +368 -368
- package/src/islands/framework-detection.ts +428 -428
- package/src/islands/integration-loader.ts +490 -490
- package/src/islands/island.tsx +565 -565
- package/src/islands/render-cache.ts +550 -550
- package/src/islands/types.ts +80 -80
- package/src/islands/universal-css-collector.ts +157 -157
- package/src/islands/universal-head-collector.ts +137 -137
- package/src/layout-system.d.ts +592 -592
- package/src/layout-system.ts +218 -218
- package/src/middleware/discovery.ts +268 -268
- package/src/middleware/executor.ts +315 -315
- package/src/middleware/index.ts +76 -76
- package/src/middleware/types.ts +99 -99
- package/src/nitro/build-config.ts +575 -575
- package/src/nitro/config.ts +483 -483
- package/src/nitro/error-handler.ts +636 -636
- package/src/nitro/index.ts +173 -173
- package/src/nitro/island-manifest.ts +584 -584
- package/src/nitro/middleware-adapter.ts +260 -260
- package/src/nitro/renderer.ts +1471 -1471
- package/src/nitro/route-discovery.ts +439 -439
- package/src/nitro/types.ts +321 -321
- package/src/render/collect-css.ts +198 -198
- package/src/render/error-pages.ts +79 -79
- package/src/render/isolated-ssr-renderer.ts +654 -654
- package/src/render/ssr.ts +1030 -1030
- package/src/schemas/api.ts +30 -30
- package/src/schemas/core.ts +64 -64
- package/src/schemas/index.ts +212 -212
- package/src/schemas/layout.ts +279 -279
- package/src/schemas/routing/index.ts +38 -38
- package/src/schemas/routing.ts +376 -376
- package/src/types/as-island.ts +20 -20
- package/src/types/image.d.ts +106 -106
- package/src/types/index.d.ts +22 -22
- package/src/types/island-jsx.d.ts +33 -33
- package/src/types/island-prop.d.ts +20 -20
- package/src/types/layout.ts +285 -285
- package/src/types/mdx.d.ts +6 -6
- package/src/types/routing.ts +555 -555
- package/src/types/types.ts +5 -5
- package/src/types/urlpattern.d.ts +49 -49
- package/src/types/vite-env.d.ts +11 -11
- package/src/utils/dev-logger.ts +299 -299
- package/src/utils/fs.ts +151 -151
- package/src/vite-plugin/auto-discover.ts +551 -551
- package/src/vite-plugin/config.ts +266 -266
- package/src/vite-plugin/errors.ts +127 -127
- package/src/vite-plugin/image-optimization.ts +156 -156
- package/src/vite-plugin/integration-activator.ts +126 -126
- package/src/vite-plugin/island-sidecar-plugin.ts +176 -176
- package/src/vite-plugin/module-discovery.ts +189 -189
- package/src/vite-plugin/nitro-integration.ts +1354 -1354
- package/src/vite-plugin/plugin.ts +403 -409
- package/src/vite-plugin/types.ts +327 -327
- package/src/vite-plugin/validation.ts +228 -228
- package/src/client/adapters/index.js +0 -12
- package/src/client/adapters/lit-adapter.js +0 -467
- package/src/client/adapters/lit-adapter.ts +0 -654
- package/src/client/adapters/preact-adapter.js +0 -223
- package/src/client/adapters/preact-adapter.ts +0 -331
- package/src/client/adapters/qwik-adapter.js +0 -259
- package/src/client/adapters/qwik-adapter.ts +0 -345
- package/src/client/adapters/react-adapter.js +0 -220
- package/src/client/adapters/react-adapter.ts +0 -353
- package/src/client/adapters/solid-adapter.js +0 -295
- package/src/client/adapters/solid-adapter.ts +0 -451
- package/src/client/adapters/svelte-adapter.js +0 -368
- package/src/client/adapters/svelte-adapter.ts +0 -524
- package/src/client/adapters/vue-adapter.js +0 -278
- package/src/client/adapters/vue-adapter.ts +0 -467
- package/src/client/components.js +0 -23
- package/src/client/css-hmr-handler.js +0 -263
- package/src/client/framework-adapter.js +0 -283
- package/src/client/hmr-coordinator.js +0 -274
|
@@ -1,325 +1,325 @@
|
|
|
1
|
-
import type { IslandState } from '../../schemas/layout.ts';
|
|
2
|
-
import type { IIslandPersistence } from '../../types/layout.ts';
|
|
3
|
-
import { IslandStateSerializer } from './island-state-serializer.ts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* IslandPersistence class for state management across navigation
|
|
7
|
-
*
|
|
8
|
-
* Handles saving, loading, and clearing island state using browser storage
|
|
9
|
-
* with support for both sessionStorage and localStorage persistence strategies.
|
|
10
|
-
*/
|
|
11
|
-
export class IslandPersistence implements IIslandPersistence {
|
|
12
|
-
private storageType: 'session' | 'local';
|
|
13
|
-
private keyPrefix: string;
|
|
14
|
-
private storage: Storage | null = null;
|
|
15
|
-
|
|
16
|
-
constructor(
|
|
17
|
-
options: {
|
|
18
|
-
storageType?: 'session' | 'local';
|
|
19
|
-
keyPrefix?: string;
|
|
20
|
-
} = {}
|
|
21
|
-
) {
|
|
22
|
-
this.storageType = options.storageType || 'session';
|
|
23
|
-
this.keyPrefix = options.keyPrefix || 'island-state';
|
|
24
|
-
|
|
25
|
-
// Initialize storage if available (browser environment)
|
|
26
|
-
if (typeof window !== 'undefined') {
|
|
27
|
-
this.storage = this.storageType === 'session' ? sessionStorage : localStorage;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Save island state to browser storage
|
|
33
|
-
*/
|
|
34
|
-
saveState(id: string, state: IslandState): void {
|
|
35
|
-
if (!this.storage) {
|
|
36
|
-
console.warn('Island persistence: Storage not available (server-side or unsupported browser)');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const key = this.getStorageKey(id);
|
|
42
|
-
|
|
43
|
-
// Use the IslandStateSerializer for proper serialization
|
|
44
|
-
const serializedState = IslandStateSerializer.serialize({
|
|
45
|
-
state,
|
|
46
|
-
timestamp: Date.now(),
|
|
47
|
-
version: '1.0',
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
this.storage.setItem(key, serializedState);
|
|
51
|
-
console.log(`Island state saved for ${id}`);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error(`Failed to save island state for ${id}:`, error);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Load island state from browser storage
|
|
59
|
-
*/
|
|
60
|
-
loadState(id: string): IslandState | null {
|
|
61
|
-
if (!this.storage) {
|
|
62
|
-
console.warn('Island persistence: Storage not available (server-side or unsupported browser)');
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const key = this.getStorageKey(id);
|
|
68
|
-
const serializedState = this.storage.getItem(key);
|
|
69
|
-
|
|
70
|
-
if (!serializedState) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Use the IslandStateSerializer for proper deserialization
|
|
75
|
-
const parsed = IslandStateSerializer.deserialize(serializedState);
|
|
76
|
-
|
|
77
|
-
// Validate the stored data structure
|
|
78
|
-
if (!parsed.state || !parsed.timestamp || !parsed.version) {
|
|
79
|
-
console.warn(`Invalid island state format for ${id}, clearing...`);
|
|
80
|
-
this.clearState(id);
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
console.log(`Island state loaded for ${id}`);
|
|
85
|
-
return parsed.state as Record<string, unknown>;
|
|
86
|
-
} catch (error) {
|
|
87
|
-
console.error(`Failed to load island state for ${id}:`, error);
|
|
88
|
-
// Clear corrupted state
|
|
89
|
-
this.clearState(id);
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Clear island state from browser storage
|
|
96
|
-
*/
|
|
97
|
-
clearState(id: string): void {
|
|
98
|
-
if (!this.storage) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const key = this.getStorageKey(id);
|
|
104
|
-
this.storage.removeItem(key);
|
|
105
|
-
console.log(`Island state cleared for ${id}`);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error(`Failed to clear island state for ${id}:`, error);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Check if state exists for an island
|
|
113
|
-
*/
|
|
114
|
-
hasState(id: string): boolean {
|
|
115
|
-
if (!this.storage) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
const key = this.getStorageKey(id);
|
|
121
|
-
return this.storage.getItem(key) !== null;
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.error(`Failed to check island state for ${id}:`, error);
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Get all stored island IDs
|
|
130
|
-
*/
|
|
131
|
-
getStoredIds(): string[] {
|
|
132
|
-
if (!this.storage) {
|
|
133
|
-
return [];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const ids: string[] = [];
|
|
138
|
-
const prefixLength = this.keyPrefix.length + 1; // +1 for the separator
|
|
139
|
-
|
|
140
|
-
for (let i = 0; i < this.storage.length; i++) {
|
|
141
|
-
const key = this.storage.key(i);
|
|
142
|
-
if (key && key.startsWith(this.keyPrefix + ':')) {
|
|
143
|
-
ids.push(key.substring(prefixLength));
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return ids;
|
|
148
|
-
} catch (error) {
|
|
149
|
-
console.error('Failed to get stored island IDs:', error);
|
|
150
|
-
return [];
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Clear all stored states
|
|
156
|
-
*/
|
|
157
|
-
clearAllStates(): void {
|
|
158
|
-
if (!this.storage) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const keysToRemove: string[] = [];
|
|
164
|
-
|
|
165
|
-
for (let i = 0; i < this.storage.length; i++) {
|
|
166
|
-
const key = this.storage.key(i);
|
|
167
|
-
if (key && key.startsWith(this.keyPrefix + ':')) {
|
|
168
|
-
keysToRemove.push(key);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
keysToRemove.forEach(key => this.storage!.removeItem(key));
|
|
173
|
-
console.log(`Cleared ${keysToRemove.length} island states`);
|
|
174
|
-
} catch (error) {
|
|
175
|
-
console.error('Failed to clear all island states:', error);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Get the storage key for an island ID
|
|
181
|
-
*/
|
|
182
|
-
private getStorageKey(id: string): string {
|
|
183
|
-
return `${this.keyPrefix}:${id}`;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Get current storage configuration
|
|
188
|
-
*/
|
|
189
|
-
getConfig(): { storageType: string; keyPrefix: string; available: boolean } {
|
|
190
|
-
return {
|
|
191
|
-
storageType: this.storageType,
|
|
192
|
-
keyPrefix: this.keyPrefix,
|
|
193
|
-
available: this.storage !== null,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Get storage usage statistics
|
|
199
|
-
*/
|
|
200
|
-
getStorageStats(): { totalKeys: number; islandKeys: number; estimatedSize: number } {
|
|
201
|
-
if (!this.storage) {
|
|
202
|
-
return { totalKeys: 0, islandKeys: 0, estimatedSize: 0 };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
try {
|
|
206
|
-
let islandKeys = 0;
|
|
207
|
-
let estimatedSize = 0;
|
|
208
|
-
|
|
209
|
-
for (let i = 0; i < this.storage.length; i++) {
|
|
210
|
-
const key = this.storage.key(i);
|
|
211
|
-
if (key && key.startsWith(this.keyPrefix + ':')) {
|
|
212
|
-
islandKeys++;
|
|
213
|
-
const value = this.storage.getItem(key);
|
|
214
|
-
if (value) {
|
|
215
|
-
estimatedSize += key.length + value.length;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
totalKeys: this.storage.length,
|
|
222
|
-
islandKeys,
|
|
223
|
-
estimatedSize,
|
|
224
|
-
};
|
|
225
|
-
} catch (error) {
|
|
226
|
-
console.error('Failed to get storage stats:', error);
|
|
227
|
-
return { totalKeys: 0, islandKeys: 0, estimatedSize: 0 };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* JSON.stringify replacer function to handle special types
|
|
233
|
-
*/
|
|
234
|
-
private replacer(key: string, value: unknown): unknown {
|
|
235
|
-
// Handle Date objects
|
|
236
|
-
if (value instanceof Date) {
|
|
237
|
-
return {
|
|
238
|
-
__type: 'Date',
|
|
239
|
-
__value: value.toISOString(),
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Handle RegExp objects
|
|
244
|
-
if (value instanceof RegExp) {
|
|
245
|
-
return {
|
|
246
|
-
__type: 'RegExp',
|
|
247
|
-
__value: {
|
|
248
|
-
source: value.source,
|
|
249
|
-
flags: value.flags,
|
|
250
|
-
},
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// Handle Map objects
|
|
255
|
-
if (value instanceof Map) {
|
|
256
|
-
return {
|
|
257
|
-
__type: 'Map',
|
|
258
|
-
__value: Array.from(value.entries()),
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Handle Set objects
|
|
263
|
-
if (value instanceof Set) {
|
|
264
|
-
return {
|
|
265
|
-
__type: 'Set',
|
|
266
|
-
__value: Array.from(value.values()),
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Handle functions (convert to null - functions can't be serialized)
|
|
271
|
-
if (typeof value === 'function') {
|
|
272
|
-
console.warn(`Function found in island state at key "${key}", converting to null`);
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Handle undefined (convert to null)
|
|
277
|
-
if (value === undefined) {
|
|
278
|
-
return null;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return value;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* JSON.parse reviver function to restore special types
|
|
286
|
-
*/
|
|
287
|
-
private reviver(key: string, value: unknown): unknown {
|
|
288
|
-
// Check if this is a special type object
|
|
289
|
-
if (value && typeof value === 'object') {
|
|
290
|
-
const obj = value as Record<string, unknown>;
|
|
291
|
-
if (obj.__type && obj.__value !== undefined) {
|
|
292
|
-
switch (obj.__type) {
|
|
293
|
-
case 'Date':
|
|
294
|
-
return new Date(obj.__value as string);
|
|
295
|
-
|
|
296
|
-
case 'RegExp': {
|
|
297
|
-
const rv = obj.__value as { source: string; flags: string };
|
|
298
|
-
return new RegExp(rv.source, rv.flags);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
case 'Map':
|
|
302
|
-
return new Map(obj.__value as Iterable<[unknown, unknown]>);
|
|
303
|
-
|
|
304
|
-
case 'Set':
|
|
305
|
-
return new Set(obj.__value as Iterable<unknown>);
|
|
306
|
-
|
|
307
|
-
default:
|
|
308
|
-
console.warn(`Unknown special type "${obj.__type}" in serialized state`);
|
|
309
|
-
return obj.__value;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return value;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Default island persistence instance
|
|
320
|
-
* Uses sessionStorage by default for better privacy and performance
|
|
321
|
-
*/
|
|
322
|
-
export const defaultIslandPersistence = new IslandPersistence({
|
|
323
|
-
storageType: 'session',
|
|
324
|
-
keyPrefix: 'island-state',
|
|
325
|
-
});
|
|
1
|
+
import type { IslandState } from '../../schemas/layout.ts';
|
|
2
|
+
import type { IIslandPersistence } from '../../types/layout.ts';
|
|
3
|
+
import { IslandStateSerializer } from './island-state-serializer.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* IslandPersistence class for state management across navigation
|
|
7
|
+
*
|
|
8
|
+
* Handles saving, loading, and clearing island state using browser storage
|
|
9
|
+
* with support for both sessionStorage and localStorage persistence strategies.
|
|
10
|
+
*/
|
|
11
|
+
export class IslandPersistence implements IIslandPersistence {
|
|
12
|
+
private storageType: 'session' | 'local';
|
|
13
|
+
private keyPrefix: string;
|
|
14
|
+
private storage: Storage | null = null;
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
options: {
|
|
18
|
+
storageType?: 'session' | 'local';
|
|
19
|
+
keyPrefix?: string;
|
|
20
|
+
} = {}
|
|
21
|
+
) {
|
|
22
|
+
this.storageType = options.storageType || 'session';
|
|
23
|
+
this.keyPrefix = options.keyPrefix || 'island-state';
|
|
24
|
+
|
|
25
|
+
// Initialize storage if available (browser environment)
|
|
26
|
+
if (typeof window !== 'undefined') {
|
|
27
|
+
this.storage = this.storageType === 'session' ? sessionStorage : localStorage;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Save island state to browser storage
|
|
33
|
+
*/
|
|
34
|
+
saveState(id: string, state: IslandState): void {
|
|
35
|
+
if (!this.storage) {
|
|
36
|
+
console.warn('Island persistence: Storage not available (server-side or unsupported browser)');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const key = this.getStorageKey(id);
|
|
42
|
+
|
|
43
|
+
// Use the IslandStateSerializer for proper serialization
|
|
44
|
+
const serializedState = IslandStateSerializer.serialize({
|
|
45
|
+
state,
|
|
46
|
+
timestamp: Date.now(),
|
|
47
|
+
version: '1.0',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.storage.setItem(key, serializedState);
|
|
51
|
+
console.log(`Island state saved for ${id}`);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(`Failed to save island state for ${id}:`, error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load island state from browser storage
|
|
59
|
+
*/
|
|
60
|
+
loadState(id: string): IslandState | null {
|
|
61
|
+
if (!this.storage) {
|
|
62
|
+
console.warn('Island persistence: Storage not available (server-side or unsupported browser)');
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const key = this.getStorageKey(id);
|
|
68
|
+
const serializedState = this.storage.getItem(key);
|
|
69
|
+
|
|
70
|
+
if (!serializedState) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Use the IslandStateSerializer for proper deserialization
|
|
75
|
+
const parsed = IslandStateSerializer.deserialize(serializedState);
|
|
76
|
+
|
|
77
|
+
// Validate the stored data structure
|
|
78
|
+
if (!parsed.state || !parsed.timestamp || !parsed.version) {
|
|
79
|
+
console.warn(`Invalid island state format for ${id}, clearing...`);
|
|
80
|
+
this.clearState(id);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(`Island state loaded for ${id}`);
|
|
85
|
+
return parsed.state as Record<string, unknown>;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.error(`Failed to load island state for ${id}:`, error);
|
|
88
|
+
// Clear corrupted state
|
|
89
|
+
this.clearState(id);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Clear island state from browser storage
|
|
96
|
+
*/
|
|
97
|
+
clearState(id: string): void {
|
|
98
|
+
if (!this.storage) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const key = this.getStorageKey(id);
|
|
104
|
+
this.storage.removeItem(key);
|
|
105
|
+
console.log(`Island state cleared for ${id}`);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.error(`Failed to clear island state for ${id}:`, error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if state exists for an island
|
|
113
|
+
*/
|
|
114
|
+
hasState(id: string): boolean {
|
|
115
|
+
if (!this.storage) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const key = this.getStorageKey(id);
|
|
121
|
+
return this.storage.getItem(key) !== null;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error(`Failed to check island state for ${id}:`, error);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get all stored island IDs
|
|
130
|
+
*/
|
|
131
|
+
getStoredIds(): string[] {
|
|
132
|
+
if (!this.storage) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const ids: string[] = [];
|
|
138
|
+
const prefixLength = this.keyPrefix.length + 1; // +1 for the separator
|
|
139
|
+
|
|
140
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
141
|
+
const key = this.storage.key(i);
|
|
142
|
+
if (key && key.startsWith(this.keyPrefix + ':')) {
|
|
143
|
+
ids.push(key.substring(prefixLength));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return ids;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Failed to get stored island IDs:', error);
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Clear all stored states
|
|
156
|
+
*/
|
|
157
|
+
clearAllStates(): void {
|
|
158
|
+
if (!this.storage) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const keysToRemove: string[] = [];
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
166
|
+
const key = this.storage.key(i);
|
|
167
|
+
if (key && key.startsWith(this.keyPrefix + ':')) {
|
|
168
|
+
keysToRemove.push(key);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
keysToRemove.forEach(key => this.storage!.removeItem(key));
|
|
173
|
+
console.log(`Cleared ${keysToRemove.length} island states`);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('Failed to clear all island states:', error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get the storage key for an island ID
|
|
181
|
+
*/
|
|
182
|
+
private getStorageKey(id: string): string {
|
|
183
|
+
return `${this.keyPrefix}:${id}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get current storage configuration
|
|
188
|
+
*/
|
|
189
|
+
getConfig(): { storageType: string; keyPrefix: string; available: boolean } {
|
|
190
|
+
return {
|
|
191
|
+
storageType: this.storageType,
|
|
192
|
+
keyPrefix: this.keyPrefix,
|
|
193
|
+
available: this.storage !== null,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get storage usage statistics
|
|
199
|
+
*/
|
|
200
|
+
getStorageStats(): { totalKeys: number; islandKeys: number; estimatedSize: number } {
|
|
201
|
+
if (!this.storage) {
|
|
202
|
+
return { totalKeys: 0, islandKeys: 0, estimatedSize: 0 };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
let islandKeys = 0;
|
|
207
|
+
let estimatedSize = 0;
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
210
|
+
const key = this.storage.key(i);
|
|
211
|
+
if (key && key.startsWith(this.keyPrefix + ':')) {
|
|
212
|
+
islandKeys++;
|
|
213
|
+
const value = this.storage.getItem(key);
|
|
214
|
+
if (value) {
|
|
215
|
+
estimatedSize += key.length + value.length;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
totalKeys: this.storage.length,
|
|
222
|
+
islandKeys,
|
|
223
|
+
estimatedSize,
|
|
224
|
+
};
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error('Failed to get storage stats:', error);
|
|
227
|
+
return { totalKeys: 0, islandKeys: 0, estimatedSize: 0 };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* JSON.stringify replacer function to handle special types
|
|
233
|
+
*/
|
|
234
|
+
private replacer(key: string, value: unknown): unknown {
|
|
235
|
+
// Handle Date objects
|
|
236
|
+
if (value instanceof Date) {
|
|
237
|
+
return {
|
|
238
|
+
__type: 'Date',
|
|
239
|
+
__value: value.toISOString(),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Handle RegExp objects
|
|
244
|
+
if (value instanceof RegExp) {
|
|
245
|
+
return {
|
|
246
|
+
__type: 'RegExp',
|
|
247
|
+
__value: {
|
|
248
|
+
source: value.source,
|
|
249
|
+
flags: value.flags,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Handle Map objects
|
|
255
|
+
if (value instanceof Map) {
|
|
256
|
+
return {
|
|
257
|
+
__type: 'Map',
|
|
258
|
+
__value: Array.from(value.entries()),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Handle Set objects
|
|
263
|
+
if (value instanceof Set) {
|
|
264
|
+
return {
|
|
265
|
+
__type: 'Set',
|
|
266
|
+
__value: Array.from(value.values()),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Handle functions (convert to null - functions can't be serialized)
|
|
271
|
+
if (typeof value === 'function') {
|
|
272
|
+
console.warn(`Function found in island state at key "${key}", converting to null`);
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Handle undefined (convert to null)
|
|
277
|
+
if (value === undefined) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return value;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* JSON.parse reviver function to restore special types
|
|
286
|
+
*/
|
|
287
|
+
private reviver(key: string, value: unknown): unknown {
|
|
288
|
+
// Check if this is a special type object
|
|
289
|
+
if (value && typeof value === 'object') {
|
|
290
|
+
const obj = value as Record<string, unknown>;
|
|
291
|
+
if (obj.__type && obj.__value !== undefined) {
|
|
292
|
+
switch (obj.__type) {
|
|
293
|
+
case 'Date':
|
|
294
|
+
return new Date(obj.__value as string);
|
|
295
|
+
|
|
296
|
+
case 'RegExp': {
|
|
297
|
+
const rv = obj.__value as { source: string; flags: string };
|
|
298
|
+
return new RegExp(rv.source, rv.flags);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
case 'Map':
|
|
302
|
+
return new Map(obj.__value as Iterable<[unknown, unknown]>);
|
|
303
|
+
|
|
304
|
+
case 'Set':
|
|
305
|
+
return new Set(obj.__value as Iterable<unknown>);
|
|
306
|
+
|
|
307
|
+
default:
|
|
308
|
+
console.warn(`Unknown special type "${obj.__type}" in serialized state`);
|
|
309
|
+
return obj.__value;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Default island persistence instance
|
|
320
|
+
* Uses sessionStorage by default for better privacy and performance
|
|
321
|
+
*/
|
|
322
|
+
export const defaultIslandPersistence = new IslandPersistence({
|
|
323
|
+
storageType: 'session',
|
|
324
|
+
keyPrefix: 'island-state',
|
|
325
|
+
});
|