@useavalon/avalon 0.1.13 → 0.1.15

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 (230) hide show
  1. package/dist/mod.js +1 -0
  2. package/dist/src/build/integration-bundler-plugin.js +1 -0
  3. package/dist/src/build/integration-config.js +1 -0
  4. package/dist/src/build/integration-detection-plugin.js +1 -0
  5. package/dist/src/build/integration-resolver-plugin.js +1 -0
  6. package/dist/src/build/island-manifest.js +1 -0
  7. package/dist/src/build/island-types-generator.js +5 -0
  8. package/dist/src/build/mdx-island-transform.js +2 -0
  9. package/dist/src/build/mdx-plugin.js +1 -0
  10. package/dist/src/build/page-island-transform.js +3 -0
  11. package/dist/src/build/prop-extractors/index.js +1 -0
  12. package/dist/src/build/prop-extractors/lit.js +1 -0
  13. package/dist/src/build/prop-extractors/qwik.js +1 -0
  14. package/dist/src/build/prop-extractors/solid.js +1 -0
  15. package/dist/src/build/prop-extractors/svelte.js +1 -0
  16. package/dist/src/build/prop-extractors/vue.js +1 -0
  17. package/dist/src/build/sidecar-file-manager.js +1 -0
  18. package/dist/src/build/sidecar-renderer.js +6 -0
  19. package/dist/src/client/adapters/index.js +1 -0
  20. package/dist/src/client/components.js +1 -0
  21. package/dist/src/client/css-hmr-handler.js +1 -0
  22. package/dist/src/client/framework-adapter.js +13 -0
  23. package/dist/src/client/hmr-coordinator.js +1 -0
  24. package/dist/src/client/hmr-error-overlay.js +214 -0
  25. package/dist/src/client/main.js +39 -0
  26. package/dist/src/components/Image.js +1 -0
  27. package/dist/src/components/IslandErrorBoundary.js +1 -0
  28. package/dist/src/components/LayoutDataErrorBoundary.js +1 -0
  29. package/dist/src/components/LayoutErrorBoundary.js +1 -0
  30. package/dist/src/components/PersistentIsland.js +1 -0
  31. package/dist/src/components/StreamingErrorBoundary.js +1 -0
  32. package/dist/src/components/StreamingLayout.js +29 -0
  33. package/dist/src/core/components/component-analyzer.js +1 -0
  34. package/dist/src/core/components/component-detection.js +5 -0
  35. package/dist/src/core/components/enhanced-framework-detector.js +1 -0
  36. package/dist/src/core/components/framework-registry.js +1 -0
  37. package/dist/src/core/content/mdx-processor.js +1 -0
  38. package/dist/src/core/integrations/index.js +1 -0
  39. package/dist/src/core/integrations/loader.js +1 -0
  40. package/dist/src/core/integrations/registry.js +1 -0
  41. package/dist/src/core/islands/island-persistence.js +1 -0
  42. package/dist/src/core/islands/island-state-serializer.js +1 -0
  43. package/dist/src/core/islands/persistent-island-context.js +1 -0
  44. package/dist/src/core/islands/use-persistent-state.js +1 -0
  45. package/dist/src/core/layout/enhanced-layout-resolver.js +1 -0
  46. package/dist/src/core/layout/layout-cache-manager.js +1 -0
  47. package/dist/src/core/layout/layout-composer.js +1 -0
  48. package/dist/src/core/layout/layout-data-loader.js +1 -0
  49. package/dist/src/core/layout/layout-discovery.js +1 -0
  50. package/dist/src/core/layout/layout-matcher.js +1 -0
  51. package/dist/src/core/layout/layout-types.js +1 -0
  52. package/dist/src/core/modules/framework-module-resolver.js +1 -0
  53. package/dist/src/islands/component-analysis.js +1 -0
  54. package/dist/src/islands/css-utils.js +17 -0
  55. package/dist/src/islands/discovery/index.js +1 -0
  56. package/dist/src/islands/discovery/registry.js +1 -0
  57. package/dist/src/islands/discovery/resolver.js +2 -0
  58. package/dist/src/islands/discovery/scanner.js +1 -0
  59. package/dist/src/islands/discovery/types.js +1 -0
  60. package/dist/src/islands/discovery/validator.js +18 -0
  61. package/dist/src/islands/discovery/watcher.js +1 -0
  62. package/dist/src/islands/framework-detection.js +1 -0
  63. package/dist/src/islands/integration-loader.js +1 -0
  64. package/dist/src/islands/island.js +1 -0
  65. package/dist/src/islands/render-cache.js +1 -0
  66. package/dist/src/islands/types.js +1 -0
  67. package/dist/src/islands/universal-css-collector.js +5 -0
  68. package/dist/src/islands/universal-head-collector.js +2 -0
  69. package/dist/src/layout-system.js +1 -0
  70. package/dist/src/middleware/discovery.js +1 -0
  71. package/dist/src/middleware/executor.js +1 -0
  72. package/dist/src/middleware/index.js +1 -0
  73. package/dist/src/middleware/types.js +1 -0
  74. package/dist/src/nitro/build-config.js +1 -0
  75. package/dist/src/nitro/config.js +1 -0
  76. package/dist/src/nitro/error-handler.js +198 -0
  77. package/dist/src/nitro/index.js +1 -0
  78. package/dist/src/nitro/island-manifest.js +2 -0
  79. package/dist/src/nitro/middleware-adapter.js +1 -0
  80. package/dist/src/nitro/renderer.js +183 -0
  81. package/dist/src/nitro/route-discovery.js +1 -0
  82. package/dist/src/nitro/types.js +1 -0
  83. package/dist/src/render/collect-css.js +3 -0
  84. package/dist/src/render/error-pages.js +48 -0
  85. package/dist/src/render/isolated-ssr-renderer.js +1 -0
  86. package/dist/src/render/ssr.js +90 -0
  87. package/dist/src/schemas/api.js +1 -0
  88. package/dist/src/schemas/core.js +1 -0
  89. package/dist/src/schemas/index.js +1 -0
  90. package/dist/src/schemas/layout.js +1 -0
  91. package/dist/src/schemas/routing/index.js +1 -0
  92. package/dist/src/schemas/routing.js +1 -0
  93. package/dist/src/types/as-island.js +1 -0
  94. package/dist/src/types/layout.js +1 -0
  95. package/dist/src/types/routing.js +1 -0
  96. package/dist/src/types/types.js +1 -0
  97. package/dist/src/utils/dev-logger.js +12 -0
  98. package/dist/src/utils/fs.js +1 -0
  99. package/dist/src/vite-plugin/auto-discover.js +1 -0
  100. package/dist/src/vite-plugin/config.js +1 -0
  101. package/dist/src/vite-plugin/errors.js +1 -0
  102. package/dist/src/vite-plugin/image-optimization.js +45 -0
  103. package/dist/src/vite-plugin/integration-activator.js +1 -0
  104. package/dist/src/vite-plugin/island-sidecar-plugin.js +1 -0
  105. package/dist/src/vite-plugin/module-discovery.js +1 -0
  106. package/dist/src/vite-plugin/nitro-integration.js +42 -0
  107. package/dist/src/vite-plugin/plugin.js +1 -0
  108. package/dist/src/vite-plugin/types.js +1 -0
  109. package/dist/src/vite-plugin/validation.js +2 -0
  110. package/package.json +14 -20
  111. package/mod.ts +0 -302
  112. package/src/build/integration-bundler-plugin.ts +0 -116
  113. package/src/build/integration-config.ts +0 -168
  114. package/src/build/integration-detection-plugin.ts +0 -117
  115. package/src/build/integration-resolver-plugin.ts +0 -90
  116. package/src/build/island-manifest.ts +0 -269
  117. package/src/build/island-types-generator.ts +0 -476
  118. package/src/build/mdx-island-transform.ts +0 -464
  119. package/src/build/mdx-plugin.ts +0 -98
  120. package/src/build/page-island-transform.ts +0 -598
  121. package/src/build/prop-extractors/index.ts +0 -21
  122. package/src/build/prop-extractors/lit.ts +0 -140
  123. package/src/build/prop-extractors/qwik.ts +0 -16
  124. package/src/build/prop-extractors/solid.ts +0 -125
  125. package/src/build/prop-extractors/svelte.ts +0 -194
  126. package/src/build/prop-extractors/vue.ts +0 -111
  127. package/src/build/sidecar-file-manager.ts +0 -104
  128. package/src/build/sidecar-renderer.ts +0 -30
  129. package/src/client/adapters/index.ts +0 -21
  130. package/src/client/components.ts +0 -35
  131. package/src/client/css-hmr-handler.ts +0 -344
  132. package/src/client/framework-adapter.ts +0 -462
  133. package/src/client/hmr-coordinator.ts +0 -396
  134. package/src/client/hmr-error-overlay.js +0 -533
  135. package/src/client/main.js +0 -824
  136. package/src/components/Image.tsx +0 -123
  137. package/src/components/IslandErrorBoundary.tsx +0 -145
  138. package/src/components/LayoutDataErrorBoundary.tsx +0 -141
  139. package/src/components/LayoutErrorBoundary.tsx +0 -127
  140. package/src/components/PersistentIsland.tsx +0 -52
  141. package/src/components/StreamingErrorBoundary.tsx +0 -233
  142. package/src/components/StreamingLayout.tsx +0 -538
  143. package/src/core/components/component-analyzer.ts +0 -192
  144. package/src/core/components/component-detection.ts +0 -508
  145. package/src/core/components/enhanced-framework-detector.ts +0 -500
  146. package/src/core/components/framework-registry.ts +0 -563
  147. package/src/core/content/mdx-processor.ts +0 -46
  148. package/src/core/integrations/index.ts +0 -19
  149. package/src/core/integrations/loader.ts +0 -125
  150. package/src/core/integrations/registry.ts +0 -175
  151. package/src/core/islands/island-persistence.ts +0 -325
  152. package/src/core/islands/island-state-serializer.ts +0 -258
  153. package/src/core/islands/persistent-island-context.tsx +0 -80
  154. package/src/core/islands/use-persistent-state.ts +0 -68
  155. package/src/core/layout/enhanced-layout-resolver.ts +0 -322
  156. package/src/core/layout/layout-cache-manager.ts +0 -485
  157. package/src/core/layout/layout-composer.ts +0 -357
  158. package/src/core/layout/layout-data-loader.ts +0 -516
  159. package/src/core/layout/layout-discovery.ts +0 -243
  160. package/src/core/layout/layout-matcher.ts +0 -299
  161. package/src/core/layout/layout-types.ts +0 -110
  162. package/src/core/modules/framework-module-resolver.ts +0 -273
  163. package/src/islands/component-analysis.ts +0 -213
  164. package/src/islands/css-utils.ts +0 -565
  165. package/src/islands/discovery/index.ts +0 -80
  166. package/src/islands/discovery/registry.ts +0 -340
  167. package/src/islands/discovery/resolver.ts +0 -477
  168. package/src/islands/discovery/scanner.ts +0 -386
  169. package/src/islands/discovery/types.ts +0 -117
  170. package/src/islands/discovery/validator.ts +0 -544
  171. package/src/islands/discovery/watcher.ts +0 -368
  172. package/src/islands/framework-detection.ts +0 -428
  173. package/src/islands/integration-loader.ts +0 -490
  174. package/src/islands/island.tsx +0 -565
  175. package/src/islands/render-cache.ts +0 -550
  176. package/src/islands/types.ts +0 -80
  177. package/src/islands/universal-css-collector.ts +0 -157
  178. package/src/islands/universal-head-collector.ts +0 -137
  179. package/src/layout-system.ts +0 -218
  180. package/src/middleware/discovery.ts +0 -268
  181. package/src/middleware/executor.ts +0 -315
  182. package/src/middleware/index.ts +0 -76
  183. package/src/middleware/types.ts +0 -99
  184. package/src/nitro/build-config.ts +0 -576
  185. package/src/nitro/config.ts +0 -483
  186. package/src/nitro/error-handler.ts +0 -636
  187. package/src/nitro/index.ts +0 -173
  188. package/src/nitro/island-manifest.ts +0 -584
  189. package/src/nitro/middleware-adapter.ts +0 -260
  190. package/src/nitro/renderer.ts +0 -1471
  191. package/src/nitro/route-discovery.ts +0 -439
  192. package/src/nitro/types.ts +0 -321
  193. package/src/render/collect-css.ts +0 -198
  194. package/src/render/error-pages.ts +0 -79
  195. package/src/render/isolated-ssr-renderer.ts +0 -654
  196. package/src/render/ssr.ts +0 -1030
  197. package/src/schemas/api.ts +0 -30
  198. package/src/schemas/core.ts +0 -64
  199. package/src/schemas/index.ts +0 -212
  200. package/src/schemas/layout.ts +0 -279
  201. package/src/schemas/routing/index.ts +0 -38
  202. package/src/schemas/routing.ts +0 -376
  203. package/src/types/as-island.ts +0 -20
  204. package/src/types/layout.ts +0 -285
  205. package/src/types/routing.ts +0 -555
  206. package/src/types/types.ts +0 -5
  207. package/src/utils/dev-logger.ts +0 -299
  208. package/src/utils/fs.ts +0 -151
  209. package/src/vite-plugin/auto-discover.ts +0 -551
  210. package/src/vite-plugin/config.ts +0 -266
  211. package/src/vite-plugin/errors.ts +0 -127
  212. package/src/vite-plugin/image-optimization.ts +0 -156
  213. package/src/vite-plugin/integration-activator.ts +0 -126
  214. package/src/vite-plugin/island-sidecar-plugin.ts +0 -176
  215. package/src/vite-plugin/module-discovery.ts +0 -189
  216. package/src/vite-plugin/nitro-integration.ts +0 -1354
  217. package/src/vite-plugin/plugin.ts +0 -403
  218. package/src/vite-plugin/types.ts +0 -327
  219. package/src/vite-plugin/validation.ts +0 -228
  220. /package/{src → dist/src}/client/types/framework-runtime.d.ts +0 -0
  221. /package/{src → dist/src}/client/types/vite-hmr.d.ts +0 -0
  222. /package/{src → dist/src}/client/types/vite-virtual-modules.d.ts +0 -0
  223. /package/{src → dist/src}/layout-system.d.ts +0 -0
  224. /package/{src → dist/src}/types/image.d.ts +0 -0
  225. /package/{src → dist/src}/types/index.d.ts +0 -0
  226. /package/{src → dist/src}/types/island-jsx.d.ts +0 -0
  227. /package/{src → dist/src}/types/island-prop.d.ts +0 -0
  228. /package/{src → dist/src}/types/mdx.d.ts +0 -0
  229. /package/{src → dist/src}/types/urlpattern.d.ts +0 -0
  230. /package/{src → dist/src}/types/vite-env.d.ts +0 -0
@@ -1,125 +0,0 @@
1
- import { registry } from "./registry.ts";
2
- import type { Integration } from "@useavalon/core";
3
-
4
- /**
5
- * Cache for loaded integrations to avoid repeated dynamic imports
6
- */
7
- const integrationCache = new Map<string, Integration>();
8
-
9
- /**
10
- * Load an integration by name, using cache if available
11
- */
12
- export async function loadIntegration(framework: string): Promise<Integration> {
13
- // Check cache first
14
- if (integrationCache.has(framework)) {
15
- return integrationCache.get(framework)!;
16
- }
17
-
18
- // Load from registry (which handles dynamic imports)
19
- const integration = await registry.load(framework);
20
-
21
- // Cache the loaded integration
22
- integrationCache.set(framework, integration);
23
-
24
- return integration;
25
- }
26
-
27
- /**
28
- * Detect framework from file path and load the appropriate integration
29
- */
30
- export async function detectAndLoadIntegration(src: string): Promise<Integration> {
31
- const framework = detectFrameworkFromPath(src);
32
- return await loadIntegration(framework);
33
- }
34
-
35
- /**
36
- * Detect framework from file path based on extension and naming conventions
37
- */
38
- export function detectFrameworkFromPath(src: string): string {
39
- // Vue files
40
- if (src.endsWith(".vue")) {
41
- return "vue";
42
- }
43
-
44
- // Svelte files
45
- if (src.endsWith(".svelte")) {
46
- return "svelte";
47
- }
48
-
49
- // Solid files (convention: .solid.tsx or .solid.jsx)
50
- if (src.includes(".solid.")) {
51
- return "solid";
52
- }
53
-
54
- // Qwik files (convention: .qwik.tsx or .qwik.jsx)
55
- if (src.includes(".qwik.")) {
56
- return "qwik";
57
- }
58
-
59
- // Default to Preact for .tsx and .jsx files
60
- return "preact";
61
- }
62
-
63
- /**
64
- * Detect framework from file content by analyzing imports and patterns
65
- */
66
- export function detectFrameworkFromContent(
67
- src: string,
68
- content?: string
69
- ): string {
70
- // First try path-based detection
71
- const pathFramework = detectFrameworkFromPath(src);
72
-
73
- // If we have a definitive answer from path, use it
74
- if (pathFramework !== "preact" || !content) {
75
- return pathFramework;
76
- }
77
-
78
- // For .tsx/.jsx files, analyze content to distinguish between Preact and Solid
79
- if (content.includes("solid-js")) {
80
- return "solid";
81
- }
82
-
83
- if (content.includes("@builder.io/qwik")) {
84
- return "qwik";
85
- }
86
-
87
- if (content.includes("preact")) {
88
- return "preact";
89
- }
90
-
91
- // Default to Preact
92
- return "preact";
93
- }
94
-
95
- /**
96
- * Preload integrations for the given frameworks
97
- * Useful for warming up the cache during build or startup
98
- */
99
- export async function preloadIntegrations(frameworks: string[]): Promise<void> {
100
- await Promise.all(
101
- frameworks.map(framework => loadIntegration(framework))
102
- );
103
- }
104
-
105
- /**
106
- * Get all currently loaded integrations
107
- */
108
- export function getLoadedIntegrations(): Integration[] {
109
- return Array.from(integrationCache.values());
110
- }
111
-
112
- /**
113
- * Clear the integration cache
114
- * Useful for testing or hot module replacement
115
- */
116
- export function clearIntegrationCache(): void {
117
- integrationCache.clear();
118
- }
119
-
120
- /**
121
- * Check if an integration is loaded in cache
122
- */
123
- export function isIntegrationLoaded(framework: string): boolean {
124
- return integrationCache.has(framework);
125
- }
@@ -1,175 +0,0 @@
1
- import type { Integration } from "@useavalon/core";
2
- import { dirname, join } from "node:path";
3
- import { statSync } from "node:fs";
4
-
5
- /**
6
- * Find the root of the Avalon monorepo by looking for packages/integrations
7
- */
8
- function findMonorepoRoot(): string {
9
- let currentDir = process.cwd();
10
-
11
- // Walk up the directory tree looking for packages/integrations
12
- for (let i = 0; i < 10; i++) {
13
- try {
14
- const integrationsPath = join(currentDir, "packages", "integrations");
15
- const stat = statSync(integrationsPath);
16
- if (stat.isDirectory()) {
17
- return currentDir;
18
- }
19
- } catch {
20
- // Directory doesn't exist, try parent
21
- }
22
-
23
- const parent = dirname(currentDir);
24
- if (parent === currentDir) {
25
- // Reached root, stop
26
- break;
27
- }
28
- currentDir = parent;
29
- }
30
-
31
- // Fallback to cwd
32
- return process.cwd();
33
- }
34
-
35
- /**
36
- * IntegrationRegistry manages loaded framework integrations.
37
- * It provides registration, retrieval, and dynamic loading of integrations.
38
- */
39
- export class IntegrationRegistry {
40
- private readonly integrations = new Map<string, Integration>();
41
- private readonly loadingPromises = new Map<string, Promise<Integration>>();
42
-
43
- /**
44
- * Register an integration instance
45
- */
46
- register(integration: Integration): void {
47
- if (!integration.name) {
48
- throw new Error("Integration must have a name");
49
- }
50
- this.integrations.set(integration.name, integration);
51
- }
52
-
53
- /**
54
- * Get a registered integration by name
55
- */
56
- get(name: string): Integration | undefined {
57
- return this.integrations.get(name);
58
- }
59
-
60
- /**
61
- * Check if an integration is registered
62
- */
63
- has(name: string): boolean {
64
- return this.integrations.has(name);
65
- }
66
-
67
- /**
68
- * Dynamically load an integration by name
69
- * Returns cached integration if already loaded
70
- */
71
- async load(name: string): Promise<Integration> {
72
- // Check if already loaded
73
- const existing = this.integrations.get(name);
74
- if (existing) {
75
- return existing;
76
- }
77
-
78
- // Check if currently loading (prevent duplicate loads)
79
- const loadingPromise = this.loadingPromises.get(name);
80
- if (loadingPromise) {
81
- return loadingPromise;
82
- }
83
-
84
- // Start loading
85
- const promise = this.loadIntegration(name);
86
- this.loadingPromises.set(name, promise);
87
-
88
- try {
89
- const integration = await promise;
90
- this.register(integration);
91
- return integration;
92
- } finally {
93
- this.loadingPromises.delete(name);
94
- }
95
- }
96
-
97
- private async loadIntegration(name: string): Promise<Integration> {
98
- const integrationKey = `${name}Integration`;
99
-
100
- // 1. Try loading from the installed npm package first (@useavalon/<name>)
101
- try {
102
- const packageName = `@useavalon/${name}`;
103
- const module = await import(/* @vite-ignore */ packageName);
104
- const integration = module[integrationKey] || module.default;
105
- if (integration) return integration as Integration;
106
- } catch {
107
- // Package not installed or import failed — try monorepo path
108
- }
109
-
110
- // 2. Monorepo fallback: resolve via packages/integrations/<name>/mod.ts
111
- try {
112
- const monorepoRoot = findMonorepoRoot();
113
- const integrationPath = join(monorepoRoot, "packages", "integrations", name, "mod.ts");
114
- const fileUrl = `file://${integrationPath}`;
115
- const module = await import(/* @vite-ignore */ fileUrl);
116
- const integration = module[integrationKey] || module.default;
117
- if (integration) return integration as Integration;
118
- } catch {
119
- // Monorepo path also failed
120
- }
121
-
122
- throw new Error(
123
- `Failed to load integration for framework '${name}'. ` +
124
- `Make sure @useavalon/${name} is installed.\n` +
125
- `Install it with: bun add @useavalon/${name}`,
126
- );
127
- }
128
-
129
- /**
130
- * Get all registered integrations
131
- */
132
- getAll(): Integration[] {
133
- return Array.from(this.integrations.values());
134
- }
135
-
136
- /**
137
- * Get all registered integration names
138
- */
139
- getAllNames(): string[] {
140
- return Array.from(this.integrations.keys());
141
- }
142
-
143
- /**
144
- * Unregister an integration
145
- */
146
- unregister(name: string): boolean {
147
- return this.integrations.delete(name);
148
- }
149
-
150
- /**
151
- * Clear all registered integrations
152
- */
153
- clear(): void {
154
- this.integrations.clear();
155
- this.loadingPromises.clear();
156
- }
157
-
158
- /**
159
- * Get the count of registered integrations
160
- */
161
- get size(): number {
162
- return this.integrations.size;
163
- }
164
- }
165
-
166
- // Global singleton registry instance
167
- // Use globalThis to ensure the registry is shared across all module contexts
168
- // This is important because Vite's ssrLoadModule creates new module contexts
169
- declare global {
170
- var __avalonIntegrationRegistry: IntegrationRegistry | undefined;
171
- }
172
-
173
- globalThis.__avalonIntegrationRegistry ??= new IntegrationRegistry();
174
-
175
- export const registry = globalThis.__avalonIntegrationRegistry;
@@ -1,325 +0,0 @@
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
- });