@useavalon/avalon 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +54 -0
  2. package/mod.ts +301 -0
  3. package/package.json +85 -0
  4. package/src/build/README.md +310 -0
  5. package/src/build/integration-bundler-plugin.ts +116 -0
  6. package/src/build/integration-config.ts +168 -0
  7. package/src/build/integration-detection-plugin.ts +117 -0
  8. package/src/build/integration-resolver-plugin.ts +90 -0
  9. package/src/build/island-manifest.ts +269 -0
  10. package/src/build/island-types-generator.ts +476 -0
  11. package/src/build/mdx-island-transform.ts +464 -0
  12. package/src/build/mdx-plugin.ts +98 -0
  13. package/src/build/page-island-transform.ts +598 -0
  14. package/src/build/prop-extractors/index.ts +21 -0
  15. package/src/build/prop-extractors/lit.ts +140 -0
  16. package/src/build/prop-extractors/qwik.ts +16 -0
  17. package/src/build/prop-extractors/solid.ts +125 -0
  18. package/src/build/prop-extractors/svelte.ts +194 -0
  19. package/src/build/prop-extractors/vue.ts +111 -0
  20. package/src/build/sidecar-file-manager.ts +104 -0
  21. package/src/build/sidecar-renderer.ts +30 -0
  22. package/src/client/adapters/index.ts +13 -0
  23. package/src/client/adapters/lit-adapter.ts +654 -0
  24. package/src/client/adapters/preact-adapter.ts +331 -0
  25. package/src/client/adapters/qwik-adapter.ts +345 -0
  26. package/src/client/adapters/react-adapter.ts +353 -0
  27. package/src/client/adapters/solid-adapter.ts +451 -0
  28. package/src/client/adapters/svelte-adapter.ts +524 -0
  29. package/src/client/adapters/vue-adapter.ts +467 -0
  30. package/src/client/components.ts +35 -0
  31. package/src/client/css-hmr-handler.ts +344 -0
  32. package/src/client/framework-adapter.ts +462 -0
  33. package/src/client/hmr-coordinator.ts +396 -0
  34. package/src/client/hmr-error-overlay.js +533 -0
  35. package/src/client/main.js +816 -0
  36. package/src/client/tests/css-hmr-handler.test.ts +360 -0
  37. package/src/client/tests/framework-adapter.test.ts +519 -0
  38. package/src/client/tests/hmr-coordinator.test.ts +176 -0
  39. package/src/client/tests/hydration-option-parsing.test.ts +107 -0
  40. package/src/client/tests/lit-adapter.test.ts +427 -0
  41. package/src/client/tests/preact-adapter.test.ts +353 -0
  42. package/src/client/tests/qwik-adapter.test.ts +343 -0
  43. package/src/client/tests/react-adapter.test.ts +317 -0
  44. package/src/client/tests/solid-adapter.test.ts +396 -0
  45. package/src/client/tests/svelte-adapter.test.ts +387 -0
  46. package/src/client/tests/vue-adapter.test.ts +407 -0
  47. package/src/client/types/framework-runtime.d.ts +68 -0
  48. package/src/client/types/vite-hmr.d.ts +46 -0
  49. package/src/client/types/vite-virtual-modules.d.ts +60 -0
  50. package/src/components/Image.tsx +123 -0
  51. package/src/components/IslandErrorBoundary.tsx +145 -0
  52. package/src/components/LayoutDataErrorBoundary.tsx +141 -0
  53. package/src/components/LayoutErrorBoundary.tsx +127 -0
  54. package/src/components/PersistentIsland.tsx +52 -0
  55. package/src/components/StreamingErrorBoundary.tsx +233 -0
  56. package/src/components/StreamingLayout.tsx +538 -0
  57. package/src/components/tests/component-analyzer.test.ts +96 -0
  58. package/src/components/tests/component-detection.test.ts +347 -0
  59. package/src/components/tests/persistent-islands.test.ts +398 -0
  60. package/src/core/components/component-analyzer.ts +192 -0
  61. package/src/core/components/component-detection.ts +508 -0
  62. package/src/core/components/enhanced-framework-detector.ts +500 -0
  63. package/src/core/components/framework-registry.ts +563 -0
  64. package/src/core/components/tests/enhanced-framework-detector.test.ts +577 -0
  65. package/src/core/components/tests/framework-registry.test.ts +465 -0
  66. package/src/core/content/mdx-processor.ts +46 -0
  67. package/src/core/integrations/README.md +282 -0
  68. package/src/core/integrations/index.ts +19 -0
  69. package/src/core/integrations/loader.ts +125 -0
  70. package/src/core/integrations/registry.ts +195 -0
  71. package/src/core/islands/island-persistence.ts +325 -0
  72. package/src/core/islands/island-state-serializer.ts +258 -0
  73. package/src/core/islands/persistent-island-context.tsx +80 -0
  74. package/src/core/islands/use-persistent-state.ts +68 -0
  75. package/src/core/layout/enhanced-layout-resolver.ts +322 -0
  76. package/src/core/layout/layout-cache-manager.ts +485 -0
  77. package/src/core/layout/layout-composer.ts +357 -0
  78. package/src/core/layout/layout-data-loader.ts +516 -0
  79. package/src/core/layout/layout-discovery.ts +243 -0
  80. package/src/core/layout/layout-matcher.ts +299 -0
  81. package/src/core/layout/layout-types.ts +110 -0
  82. package/src/core/layout/tests/enhanced-layout-resolver.test.ts +477 -0
  83. package/src/core/layout/tests/layout-cache-optimization.test.ts +149 -0
  84. package/src/core/layout/tests/layout-composer.test.ts +486 -0
  85. package/src/core/layout/tests/layout-data-loader.test.ts +443 -0
  86. package/src/core/layout/tests/layout-discovery.test.ts +253 -0
  87. package/src/core/layout/tests/layout-matcher.test.ts +480 -0
  88. package/src/core/modules/framework-module-resolver.ts +273 -0
  89. package/src/core/modules/tests/framework-module-resolver.test.ts +263 -0
  90. package/src/core/modules/tests/module-resolution-integration.test.ts +117 -0
  91. package/src/islands/component-analysis.ts +213 -0
  92. package/src/islands/css-utils.ts +565 -0
  93. package/src/islands/discovery/index.ts +80 -0
  94. package/src/islands/discovery/registry.ts +340 -0
  95. package/src/islands/discovery/resolver.ts +477 -0
  96. package/src/islands/discovery/scanner.ts +386 -0
  97. package/src/islands/discovery/tests/island-discovery.test.ts +881 -0
  98. package/src/islands/discovery/types.ts +117 -0
  99. package/src/islands/discovery/validator.ts +544 -0
  100. package/src/islands/discovery/watcher.ts +368 -0
  101. package/src/islands/framework-detection.ts +428 -0
  102. package/src/islands/integration-loader.ts +490 -0
  103. package/src/islands/island.tsx +565 -0
  104. package/src/islands/render-cache.ts +550 -0
  105. package/src/islands/types.ts +80 -0
  106. package/src/islands/universal-css-collector.ts +157 -0
  107. package/src/islands/universal-head-collector.ts +137 -0
  108. package/src/layout-system.d.ts +592 -0
  109. package/src/layout-system.ts +218 -0
  110. package/src/middleware/__tests__/discovery.test.ts +107 -0
  111. package/src/middleware/discovery.ts +268 -0
  112. package/src/middleware/executor.ts +315 -0
  113. package/src/middleware/index.ts +76 -0
  114. package/src/middleware/types.ts +99 -0
  115. package/src/nitro/build-config.ts +576 -0
  116. package/src/nitro/config.ts +483 -0
  117. package/src/nitro/error-handler.ts +636 -0
  118. package/src/nitro/index.ts +173 -0
  119. package/src/nitro/island-manifest.ts +584 -0
  120. package/src/nitro/middleware-adapter.ts +260 -0
  121. package/src/nitro/renderer.ts +1458 -0
  122. package/src/nitro/route-discovery.ts +439 -0
  123. package/src/nitro/types.ts +321 -0
  124. package/src/render/collect-css.ts +198 -0
  125. package/src/render/error-pages.ts +79 -0
  126. package/src/render/isolated-ssr-renderer.ts +654 -0
  127. package/src/render/ssr.ts +1030 -0
  128. package/src/schemas/api.ts +30 -0
  129. package/src/schemas/core.ts +64 -0
  130. package/src/schemas/index.ts +212 -0
  131. package/src/schemas/layout.ts +279 -0
  132. package/src/schemas/routing/index.ts +38 -0
  133. package/src/schemas/routing.ts +376 -0
  134. package/src/types/as-island.ts +20 -0
  135. package/src/types/image.d.ts +106 -0
  136. package/src/types/index.d.ts +22 -0
  137. package/src/types/island-jsx.d.ts +33 -0
  138. package/src/types/island-prop.d.ts +20 -0
  139. package/src/types/layout.ts +285 -0
  140. package/src/types/mdx.d.ts +6 -0
  141. package/src/types/routing.ts +555 -0
  142. package/src/types/tests/layout-types.test.ts +197 -0
  143. package/src/types/types.ts +5 -0
  144. package/src/types/urlpattern.d.ts +49 -0
  145. package/src/types/vite-env.d.ts +11 -0
  146. package/src/utils/dev-logger.ts +299 -0
  147. package/src/utils/fs.ts +151 -0
  148. package/src/vite-plugin/auto-discover.ts +551 -0
  149. package/src/vite-plugin/config.ts +266 -0
  150. package/src/vite-plugin/errors.ts +127 -0
  151. package/src/vite-plugin/image-optimization.ts +151 -0
  152. package/src/vite-plugin/integration-activator.ts +126 -0
  153. package/src/vite-plugin/island-sidecar-plugin.ts +176 -0
  154. package/src/vite-plugin/module-discovery.ts +189 -0
  155. package/src/vite-plugin/nitro-integration.ts +1334 -0
  156. package/src/vite-plugin/plugin.ts +329 -0
  157. package/src/vite-plugin/tests/image-optimization.test.ts +54 -0
  158. package/src/vite-plugin/types.ts +327 -0
  159. package/src/vite-plugin/validation.ts +228 -0
@@ -0,0 +1,282 @@
1
+ # Integration System
2
+
3
+ The Avalon integration system provides a modular, extensible architecture for framework integrations. Each framework (Preact, Vue, Solid, Svelte) is an independent package with its own versioning and dependencies.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ src/core/integrations/
9
+ ├── registry.ts # Integration registry for managing loaded integrations
10
+ ├── loader.ts # Dynamic integration loading with caching
11
+ ├── validator.ts # Integration validation and compliance checking
12
+ ├── config-loader.ts # Configuration file loading and parsing
13
+ ├── startup.ts # System initialization and validation
14
+ ├── cli.ts # CLI commands for managing integrations
15
+ └── index.ts # Public API exports
16
+ ```
17
+
18
+ ## Key Components
19
+
20
+ ### Registry (`registry.ts`)
21
+
22
+ The `IntegrationRegistry` manages loaded framework integrations:
23
+
24
+ ```typescript
25
+ import { registry } from "./src/core/integrations/registry.ts";
26
+
27
+ // Load an integration
28
+ const integration = await registry.load("preact");
29
+
30
+ // Check if loaded
31
+ if (registry.has("preact")) {
32
+ const preact = registry.get("preact");
33
+ }
34
+
35
+ // Get all loaded integrations
36
+ const all = registry.getAll();
37
+ ```
38
+
39
+ ### Loader (`loader.ts`)
40
+
41
+ Dynamic integration loading with caching:
42
+
43
+ ```typescript
44
+ import { loadIntegration } from "./src/core/integrations/loader.ts";
45
+
46
+ // Load integration (cached)
47
+ const integration = await loadIntegration("vue");
48
+
49
+ // Preload multiple integrations
50
+ await preloadIntegrations(["preact", "vue", "solid"]);
51
+
52
+ // Check if loaded
53
+ if (isIntegrationLoaded("svelte")) {
54
+ // ...
55
+ }
56
+ ```
57
+
58
+ ### Validator (`validator.ts`)
59
+
60
+ Validate integrations against the required interface:
61
+
62
+ ```typescript
63
+ import { validateIntegration } from "./src/core/integrations/validator.ts";
64
+
65
+ const result = validateIntegration(integration);
66
+
67
+ if (result.valid) {
68
+ console.log("Integration is valid");
69
+ } else {
70
+ console.error("Errors:", result.errors);
71
+ console.warn("Warnings:", result.warnings);
72
+ }
73
+ ```
74
+
75
+ ### Configuration (`config-loader.ts`)
76
+
77
+ Load and parse `avalon.config.ts`:
78
+
79
+ ```typescript
80
+ import { loadConfig } from "./src/core/integrations/config-loader.ts";
81
+
82
+ const result = await loadConfig();
83
+
84
+ if (result.found) {
85
+ console.log("Config:", result.config);
86
+ console.log("Path:", result.configPath);
87
+ }
88
+ ```
89
+
90
+ ### Startup (`startup.ts`)
91
+
92
+ Initialize the integration system:
93
+
94
+ ```typescript
95
+ import { initializeIntegrations } from "./src/core/integrations/startup.ts";
96
+
97
+ const result = await initializeIntegrations();
98
+
99
+ if (result.success) {
100
+ console.log("Loaded:", result.loadedIntegrations);
101
+ } else {
102
+ console.error("Errors:", result.errors);
103
+ }
104
+ ```
105
+
106
+ ## Configuration
107
+
108
+ Create an `avalon.config.ts` file in your project root:
109
+
110
+ ```typescript
111
+ export default {
112
+ integrations: [
113
+ { name: "preact", enabled: true },
114
+ { name: "vue", enabled: true },
115
+ { name: "solid", enabled: true },
116
+ { name: "svelte", enabled: true },
117
+ ],
118
+ autoDiscoverIntegrations: true,
119
+ validateIntegrations: true,
120
+ showWarnings: true,
121
+ };
122
+ ```
123
+
124
+ See [Integration Configuration](../../../docs/integration-configuration.md) for full documentation.
125
+
126
+ ## CLI Commands
127
+
128
+ ```bash
129
+ # List integrations
130
+ deno run --allow-read --allow-env src/core/integrations/cli.ts list
131
+
132
+ # Initialize system
133
+ deno run --allow-read --allow-env src/core/integrations/cli.ts init
134
+
135
+ # Validate integrations
136
+ deno run --allow-read --allow-env src/core/integrations/cli.ts validate
137
+
138
+ # Show integration info
139
+ deno run --allow-read --allow-env src/core/integrations/cli.ts info preact
140
+
141
+ # Generate config file
142
+ deno run --allow-read --allow-write src/core/integrations/cli.ts generate-config
143
+ ```
144
+
145
+ ## Error Handling
146
+
147
+ The system provides helpful error messages:
148
+
149
+ ### Missing Integration
150
+
151
+ ```typescript
152
+ try {
153
+ await loadIntegration("vue");
154
+ } catch (error) {
155
+ // Error includes installation instructions
156
+ console.error(error.message);
157
+ }
158
+ ```
159
+
160
+ ### Validation Errors
161
+
162
+ ```typescript
163
+ const result = validateIntegration(integration);
164
+
165
+ if (!result.valid) {
166
+ // Detailed error messages
167
+ result.errors.forEach(error => console.error(error));
168
+ }
169
+ ```
170
+
171
+ ### Configuration Errors
172
+
173
+ ```typescript
174
+ const config = await loadConfig();
175
+
176
+ if (config.errors.length > 0) {
177
+ // Configuration validation errors
178
+ config.errors.forEach(error => console.error(error));
179
+ }
180
+ ```
181
+
182
+ ## Integration Discovery
183
+
184
+ The system supports multiple discovery methods:
185
+
186
+ ### 1. Explicit Configuration
187
+
188
+ List integrations in `avalon.config.ts`:
189
+
190
+ ```typescript
191
+ export default {
192
+ integrations: [
193
+ { name: "preact", enabled: true },
194
+ ],
195
+ };
196
+ ```
197
+
198
+ ### 2. Auto-Discovery
199
+
200
+ Enable auto-discovery to load integrations based on usage:
201
+
202
+ ```typescript
203
+ export default {
204
+ autoDiscoverIntegrations: true,
205
+ };
206
+ ```
207
+
208
+ ### 3. Manual Loading
209
+
210
+ Load integrations programmatically:
211
+
212
+ ```typescript
213
+ import { loadIntegration } from "./src/core/integrations/loader.ts";
214
+
215
+ const integration = await loadIntegration("solid");
216
+ ```
217
+
218
+ ## Validation
219
+
220
+ Integrations are validated to ensure they implement the required interface:
221
+
222
+ ```typescript
223
+ interface Integration {
224
+ name: string;
225
+ version: string;
226
+ render(params: RenderParams): Promise<RenderResult>;
227
+ getHydrationScript(): string;
228
+ config(): IntegrationConfig;
229
+ vitePlugin?(): any | any[];
230
+ }
231
+ ```
232
+
233
+ Validation checks:
234
+ - Required properties exist and have correct types
235
+ - Required methods are functions
236
+ - Configuration is valid
237
+ - Detection patterns are RegExp objects
238
+
239
+ ## Best Practices
240
+
241
+ ### Development
242
+
243
+ ```typescript
244
+ export default {
245
+ autoDiscoverIntegrations: true, // Flexible
246
+ validateIntegrations: true, // Catch issues early
247
+ showWarnings: true, // See all issues
248
+ };
249
+ ```
250
+
251
+ ### Production
252
+
253
+ ```typescript
254
+ export default {
255
+ integrations: [
256
+ // Only list what you use
257
+ { name: "preact", enabled: true },
258
+ ],
259
+ autoDiscoverIntegrations: false, // Explicit control
260
+ validateIntegrations: true, // Ensure compatibility
261
+ showWarnings: false, // Clean logs
262
+ };
263
+ ```
264
+
265
+ ## Testing
266
+
267
+ Test the configuration system:
268
+
269
+ ```bash
270
+ deno run --allow-read --allow-env src/core/integrations/test-config.ts
271
+ ```
272
+
273
+ ## API Reference
274
+
275
+ See [Integration Configuration](../../../docs/integration-configuration.md) for complete API documentation.
276
+
277
+ ## Related Documentation
278
+
279
+ - [Integration System Design](../../../.kiro/specs/framework-integrations/design.md)
280
+ - [Integration Requirements](../../../.kiro/specs/framework-integrations/requirements.md)
281
+ - [Integration Configuration](../../../docs/integration-configuration.md)
282
+ - [Creating Custom Integrations](../../../docs/custom-integrations.md)
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Integration system exports
3
+ * Central export point for all integration-related functionality
4
+ */
5
+
6
+ // Registry
7
+ export { IntegrationRegistry, registry } from "./registry.ts";
8
+
9
+ // Loader
10
+ export {
11
+ loadIntegration,
12
+ detectAndLoadIntegration,
13
+ detectFrameworkFromPath,
14
+ detectFrameworkFromContent,
15
+ preloadIntegrations,
16
+ getLoadedIntegrations,
17
+ clearIntegrationCache,
18
+ isIntegrationLoaded,
19
+ } from "./loader.ts";
@@ -0,0 +1,125 @@
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
+ }
@@ -0,0 +1,195 @@
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
+ /**
98
+ * Internal method to load integration module
99
+ * Note: In development mode, integrations should be pre-loaded via preloader.ts
100
+ * before Vite's SSR context starts. This method is a fallback for production
101
+ * or when integrations weren't pre-loaded.
102
+ */
103
+ private async loadIntegration(name: string): Promise<Integration> {
104
+ try {
105
+ // Use absolute file:// URL to bypass Vite's module resolution
106
+ // This ensures we use Deno's native import which handles npm: specifiers correctly
107
+ const monorepoRoot = findMonorepoRoot();
108
+ const integrationPath = join(monorepoRoot, "packages", "integrations", name, "mod.ts");
109
+ const fileUrl = `file://${integrationPath}`;
110
+
111
+ // Dynamic import with file:// URL bypasses Vite's SSR module loader
112
+ const module = await import(/* @vite-ignore */ fileUrl);
113
+
114
+ // Look for the integration export (e.g., preactIntegration)
115
+ const integrationKey = `${name}Integration`;
116
+ const integration = module[integrationKey] || module.default;
117
+
118
+ if (!integration) {
119
+ throw new Error(
120
+ `Integration module '${name}' does not export '${integrationKey}' or a default export`
121
+ );
122
+ }
123
+
124
+ return integration as Integration;
125
+ } catch (error) {
126
+ // Check if this is a Vite SSR context issue
127
+ const errorMessage = error instanceof Error ? error.message : String(error);
128
+ const isViteIssue = errorMessage.includes('ERR_UNSUPPORTED_ESM_URL_SCHEME') ||
129
+ errorMessage.includes('Only file and data URLs are supported');
130
+
131
+ if (isViteIssue) {
132
+ throw new Error(
133
+ `Integration '${name}' could not be loaded within Vite's SSR context. ` +
134
+ `This usually means the integration wasn't pre-loaded at server startup. ` +
135
+ `Make sure preloadIntegrationsNative() is called before Vite server starts.`,
136
+ { cause: error }
137
+ );
138
+ }
139
+
140
+ throw new Error(
141
+ `Failed to load integration for framework '${name}'. ` +
142
+ `Make sure @useavalon/${name} is installed.\n` +
143
+ `Install it with: bun add @useavalon/${name}`,
144
+ { cause: error }
145
+ );
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Get all registered integrations
151
+ */
152
+ getAll(): Integration[] {
153
+ return Array.from(this.integrations.values());
154
+ }
155
+
156
+ /**
157
+ * Get all registered integration names
158
+ */
159
+ getAllNames(): string[] {
160
+ return Array.from(this.integrations.keys());
161
+ }
162
+
163
+ /**
164
+ * Unregister an integration
165
+ */
166
+ unregister(name: string): boolean {
167
+ return this.integrations.delete(name);
168
+ }
169
+
170
+ /**
171
+ * Clear all registered integrations
172
+ */
173
+ clear(): void {
174
+ this.integrations.clear();
175
+ this.loadingPromises.clear();
176
+ }
177
+
178
+ /**
179
+ * Get the count of registered integrations
180
+ */
181
+ get size(): number {
182
+ return this.integrations.size;
183
+ }
184
+ }
185
+
186
+ // Global singleton registry instance
187
+ // Use globalThis to ensure the registry is shared across all module contexts
188
+ // This is important because Vite's ssrLoadModule creates new module contexts
189
+ declare global {
190
+ var __avalonIntegrationRegistry: IntegrationRegistry | undefined;
191
+ }
192
+
193
+ globalThis.__avalonIntegrationRegistry ??= new IntegrationRegistry();
194
+
195
+ export const registry = globalThis.__avalonIntegrationRegistry;