@useavalon/avalon 0.1.13 → 0.1.14

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,243 +0,0 @@
1
- import { join, resolve, relative } from 'node:path';
2
- import { statSync } from 'node:fs';
3
-
4
- import type {
5
- ComponentType,
6
- LayoutRoute,
7
- LayoutHandler,
8
- LayoutDiscoveryOptions,
9
- LayoutContext,
10
- LayoutData,
11
- LayoutProps,
12
- LayoutErrorInfo,
13
- } from './layout-types.ts';
14
-
15
- export type { LayoutDiscoveryOptions } from './layout-types.ts';
16
-
17
- interface LayoutFileExport {
18
- default: ComponentType<LayoutProps>;
19
- layoutLoader?: (ctx: LayoutContext) => Promise<LayoutData>;
20
- }
21
-
22
- /**
23
- * Converts an absolute file path to a valid ESM import specifier.
24
- * Windows absolute paths (C:\...) are converted to file:// URLs.
25
- */
26
- function toImportSpecifier(filePath: string): string {
27
- if (/^[A-Za-z]:[\\/]/.test(filePath)) {
28
- return `file:///${filePath.replaceAll('\\', '/')}`;
29
- }
30
- return filePath;
31
- }
32
-
33
- /**
34
- * Checks whether a file exists at the given path using statSync.
35
- */
36
- function fileExists(filePath: string): boolean {
37
- try {
38
- statSync(filePath);
39
- return true;
40
- } catch {
41
- return false;
42
- }
43
- }
44
-
45
- /**
46
- * Builds the path hierarchy for a route.
47
- * For "/admin/users" returns ['', '/admin', '/admin/users'].
48
- */
49
- function buildPathHierarchy(routePath: string): string[] {
50
- const segments = routePath.split('/').filter(Boolean);
51
- const paths: string[] = [''];
52
- for (let i = 0; i < segments.length; i++) {
53
- paths.push('/' + segments.slice(0, i + 1).join('/'));
54
- }
55
- return paths;
56
- }
57
-
58
- /**
59
- * Simplified Layout Discovery System
60
- *
61
- * Looks for _layout.tsx files in the path hierarchy from root to the current route.
62
- *
63
- * For route /admin/users/123:
64
- * - Check src/layouts/_layout.tsx (root layout)
65
- * - Check src/layouts/admin/_layout.tsx
66
- * - Check src/layouts/admin/users/_layout.tsx
67
- */
68
- export class LayoutDiscovery {
69
- private readonly layoutCache = new Map<string, LayoutHandler>();
70
- private readonly routeCache = new Map<string, LayoutRoute[]>();
71
- private readonly baseDirectory: string;
72
- private readonly filePattern: string;
73
- private readonly developmentMode: boolean;
74
-
75
- constructor(options: LayoutDiscoveryOptions) {
76
- this.baseDirectory = resolve(options.baseDirectory);
77
- this.filePattern = options.filePattern || '_layout.tsx';
78
- this.developmentMode = options.developmentMode || false;
79
-
80
- if (this.developmentMode) {
81
- console.log(`[LayoutDiscovery] baseDirectory=${this.baseDirectory}, filePattern=${this.filePattern}`);
82
- }
83
- }
84
-
85
- /**
86
- * Discovers all layout files for a given route path by walking up the path hierarchy
87
- */
88
- discoverLayouts(routePath: string): Promise<LayoutRoute[]> {
89
- const cacheKey = `layouts-${routePath}`;
90
-
91
- if (this.routeCache.has(cacheKey)) {
92
- return Promise.resolve(this.routeCache.get(cacheKey)!);
93
- }
94
-
95
- const routes: LayoutRoute[] = [];
96
- const pathsToCheck = buildPathHierarchy(routePath);
97
-
98
- for (const pathToCheck of pathsToCheck) {
99
- const fsPath = pathToCheck === '' ? this.baseDirectory : join(this.baseDirectory, pathToCheck);
100
- const layoutFilePath = join(fsPath, this.filePattern);
101
-
102
- if (fileExists(layoutFilePath)) {
103
- const depth = pathToCheck === '' ? 0 : pathToCheck.split('/').filter(Boolean).length;
104
- routes.push({
105
- pattern: new URLPattern({ pathname: depth === 0 ? '*' : `${pathToCheck}/*` }),
106
- layoutPath: layoutFilePath,
107
- priority: depth * 10,
108
- type: depth === 0 ? 'root' : 'nested',
109
- depth,
110
- });
111
- }
112
- }
113
-
114
- routes.sort((a, b) => a.priority - b.priority);
115
- this.routeCache.set(cacheKey, routes);
116
- return Promise.resolve(routes);
117
- }
118
-
119
- /**
120
- * Builds a complete layout chain for a URL
121
- */
122
- async buildLayoutChain(url: URL): Promise<LayoutHandler[]> {
123
- const routes = await this.discoverLayouts(url.pathname);
124
- const layoutChain: LayoutHandler[] = [];
125
-
126
- for (const route of routes) {
127
- try {
128
- const handler = await this.loadLayout(route.layoutPath);
129
- if (handler) {
130
- layoutChain.push(handler);
131
- }
132
- } catch (error) {
133
- if (this.developmentMode) {
134
- console.warn(
135
- `[Layout] Failed to load ${route.layoutPath}: ${error instanceof Error ? error.message : String(error)}`,
136
- );
137
- }
138
- }
139
- }
140
-
141
- return layoutChain;
142
- }
143
-
144
- /**
145
- * Builds layout chain with data loading
146
- */
147
- async buildLayoutChainWithData(
148
- url: URL,
149
- context: LayoutContext,
150
- ): Promise<{ handlers: LayoutHandler[]; data: LayoutData[]; errors: LayoutErrorInfo[] }> {
151
- const handlers = await this.buildLayoutChain(url);
152
- const data: LayoutData[] = [];
153
- const errors: LayoutErrorInfo[] = [];
154
-
155
- for (const handler of handlers) {
156
- if (handler.loader) {
157
- try {
158
- const layoutData = await handler.loader(context);
159
- data.push(layoutData);
160
- } catch (error) {
161
- if (this.developmentMode) {
162
- console.warn(`[Layout] Data loader error for ${handler.path}:`, error);
163
- }
164
- errors.push({ layoutPath: handler.path, errorType: 'loader', timestamp: Date.now() });
165
- data.push({});
166
- }
167
- } else {
168
- data.push({});
169
- }
170
- }
171
-
172
- return { handlers, data, errors };
173
- }
174
-
175
- /**
176
- * Loads layout handler from file
177
- */
178
- private async loadLayout(filePath: string): Promise<LayoutHandler | null> {
179
- if (this.layoutCache.has(filePath)) {
180
- return this.layoutCache.get(filePath)!;
181
- }
182
-
183
- try {
184
- const importPath = toImportSpecifier(filePath);
185
- const layoutModule = (await import(/* @vite-ignore */ importPath)) as LayoutFileExport;
186
-
187
- if (!layoutModule.default || typeof layoutModule.default !== 'function') {
188
- if (this.developmentMode) {
189
- console.warn(`[Layout] No default export in ${filePath}`);
190
- }
191
- return null;
192
- }
193
-
194
- const relativePath = relative(this.baseDirectory, filePath);
195
- const pathSegments = relativePath.split('/').filter(Boolean);
196
- const priority = Math.max(0, (pathSegments.length - 1) * 10);
197
-
198
- const handler: LayoutHandler = {
199
- component: layoutModule.default,
200
- loader: layoutModule.layoutLoader,
201
- path: filePath,
202
- priority,
203
- };
204
-
205
- this.layoutCache.set(filePath, handler);
206
- return handler;
207
- } catch (error) {
208
- if (this.developmentMode) {
209
- console.warn(`[Layout] Failed to load ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
210
- }
211
- return null;
212
- }
213
- }
214
-
215
- /** Clears all caches */
216
- clearCache(): void {
217
- this.layoutCache.clear();
218
- this.routeCache.clear();
219
- }
220
-
221
- /** Clears cache for a specific layout file */
222
- clearLayoutCache(filePath: string): void {
223
- this.layoutCache.delete(filePath);
224
- this.routeCache.clear();
225
- }
226
-
227
- /** Gets cache statistics (for debugging) */
228
- getCacheStats(): { layoutCount: number; routeCacheCount: number } {
229
- return {
230
- layoutCount: this.layoutCache.size,
231
- routeCacheCount: this.routeCache.size,
232
- };
233
- }
234
-
235
- /** Gets the current discovery options */
236
- getOptions(): LayoutDiscoveryOptions {
237
- return {
238
- baseDirectory: this.baseDirectory,
239
- filePattern: this.filePattern,
240
- developmentMode: this.developmentMode,
241
- };
242
- }
243
- }
@@ -1,299 +0,0 @@
1
- import type { LayoutRule, RouteInfo } from './layout-types.ts';
2
-
3
- /**
4
- * Built-in layout rules for common scenarios
5
- */
6
- export class BuiltInLayoutRules {
7
- /**
8
- * Rule to skip HTML layouts for API routes
9
- * Requirements: 4.1
10
- */
11
- static readonly API_ROUTES_SKIP_LAYOUTS: LayoutRule = {
12
- matches: (route: RouteInfo): boolean => {
13
- return route.path.startsWith('/api/');
14
- },
15
- apply: false,
16
- priority: 100,
17
- };
18
-
19
- /**
20
- * Rule to apply mobile-specific layouts for mobile user agents
21
- * Requirements: 4.2
22
- */
23
- static readonly MOBILE_LAYOUT_DETECTION: LayoutRule = {
24
- matches: (route: RouteInfo, layoutPath?: string): boolean => {
25
- const userAgent = route.headers.get('user-agent')?.toLowerCase() || '';
26
- const isMobile = /mobile|android|iphone|ipad|phone|tablet/i.test(userAgent);
27
- const isMobileLayout = layoutPath?.includes('/mobile/') ?? false;
28
- // Skip mobile layouts for non-mobile user agents; skip non-mobile layouts for mobile users
29
- if (isMobileLayout) return !isMobile;
30
- return isMobile;
31
- },
32
- apply: false,
33
- priority: 50,
34
- };
35
-
36
- /**
37
- * Rule to skip layouts based on specific headers
38
- * Requirements: 4.3
39
- */
40
- static readonly HEADER_BASED_SKIP: LayoutRule = {
41
- matches: (route: RouteInfo): boolean => {
42
- const skipLayout = route.headers.get('x-skip-layout');
43
- return skipLayout === 'true' || skipLayout === '1';
44
- },
45
- apply: false,
46
- priority: 90,
47
- };
48
-
49
- /**
50
- * Rule to apply admin layouts only for admin routes
51
- * Requirements: 4.3
52
- */
53
- static readonly ADMIN_LAYOUT_RESTRICTION: LayoutRule = {
54
- matches: (route: RouteInfo, layoutPath?: string): boolean => {
55
- // Only restrict admin layouts — if no layoutPath or not an admin layout, don't match
56
- if (!layoutPath?.includes('/admin/')) return false;
57
- // Admin layout should only apply to admin routes
58
- return !route.path.startsWith('/admin/');
59
- },
60
- apply: false,
61
- priority: 60,
62
- };
63
-
64
- static getAllRules(): LayoutRule[] {
65
- return [
66
- this.API_ROUTES_SKIP_LAYOUTS,
67
- this.MOBILE_LAYOUT_DETECTION,
68
- this.HEADER_BASED_SKIP,
69
- this.ADMIN_LAYOUT_RESTRICTION,
70
- ];
71
- }
72
- }
73
-
74
- /**
75
- * Layout matcher class that handles conditional layout rendering based on rules
76
- * Requirements: 4.1, 4.2, 4.3, 4.4, 4.5
77
- */
78
- export class LayoutMatcher {
79
- private rules: LayoutRule[] = [];
80
- private readonly developmentMode: boolean;
81
-
82
- constructor(options: { developmentMode?: boolean } = {}) {
83
- this.developmentMode = options.developmentMode || false;
84
- this.addBuiltInRules();
85
- }
86
-
87
- /**
88
- * Add a new layout rule
89
- * Requirements: 4.3
90
- */
91
- addRule(rule: LayoutRule): void {
92
- if (!rule.matches || typeof rule.matches !== 'function') {
93
- throw new Error('Layout rule must have a valid matches function');
94
- }
95
- if (typeof rule.apply !== 'boolean') {
96
- throw new TypeError('Layout rule must have a boolean apply property');
97
- }
98
- if (typeof rule.priority !== 'number') {
99
- throw new TypeError('Layout rule must have a numeric priority');
100
- }
101
-
102
- this.rules.push(rule);
103
- this.sortRulesByPriority();
104
-
105
- if (this.developmentMode) {
106
- console.log(`[LayoutMatcher] Added rule with priority ${rule.priority}`);
107
- }
108
- }
109
-
110
- /**
111
- * Remove a layout rule
112
- * Requirements: 4.3
113
- */
114
- removeRule(rule: LayoutRule): void {
115
- const index = this.rules.indexOf(rule);
116
- if (index > -1) {
117
- this.rules.splice(index, 1);
118
- if (this.developmentMode) {
119
- console.log(`[LayoutMatcher] Removed rule with priority ${rule.priority}`);
120
- }
121
- }
122
- }
123
-
124
- /**
125
- * Check if a layout should be applied based on all rules
126
- * Requirements: 4.1, 4.2, 4.3, 4.4, 4.5
127
- */
128
- shouldApplyLayout(layoutPath: string, route: RouteInfo): boolean {
129
- try {
130
- const matchingRules = this.getMatchingRules(route, layoutPath);
131
-
132
- if (matchingRules.length === 0) {
133
- return true;
134
- }
135
-
136
- const result = this.resolveRuleConflicts(matchingRules);
137
-
138
- if (this.developmentMode) {
139
- console.log(
140
- `[LayoutMatcher] Layout ${layoutPath} for route ${route.path}: ${result ? 'APPLY' : 'SKIP'} ` +
141
- `(${matchingRules.length} rules matched)`,
142
- );
143
- }
144
-
145
- return result;
146
- } catch (error) {
147
- if (this.developmentMode) {
148
- console.warn(
149
- `[LayoutMatcher] Error evaluating rules for layout ${layoutPath}: ${
150
- error instanceof Error ? error.message : String(error)
151
- }`,
152
- );
153
- }
154
- return true;
155
- }
156
- }
157
-
158
- getRules(): LayoutRule[] {
159
- return [...this.rules];
160
- }
161
-
162
- clearRules(): void {
163
- this.rules = [];
164
- if (this.developmentMode) {
165
- console.log('[LayoutMatcher] Cleared all rules');
166
- }
167
- }
168
-
169
- private addBuiltInRules(): void {
170
- for (const rule of BuiltInLayoutRules.getAllRules()) {
171
- this.rules.push(rule);
172
- }
173
- this.sortRulesByPriority();
174
- if (this.developmentMode) {
175
- console.log(`[LayoutMatcher] Added ${this.rules.length} built-in rules`);
176
- }
177
- }
178
-
179
- private sortRulesByPriority(): void {
180
- this.rules.sort((a, b) => b.priority - a.priority);
181
- }
182
-
183
- private getMatchingRules(route: RouteInfo, layoutPath?: string): LayoutRule[] {
184
- const matchingRules: LayoutRule[] = [];
185
- for (const rule of this.rules) {
186
- try {
187
- if (rule.matches(route, layoutPath)) {
188
- matchingRules.push(rule);
189
- }
190
- } catch (error) {
191
- if (this.developmentMode) {
192
- console.warn(
193
- `[LayoutMatcher] Error in rule evaluation: ${error instanceof Error ? error.message : String(error)}`,
194
- );
195
- }
196
- }
197
- }
198
- return matchingRules;
199
- }
200
-
201
- private resolveRuleConflicts(matchingRules: LayoutRule[]): boolean {
202
- if (matchingRules.length === 0) return true;
203
- if (matchingRules.length === 1) return matchingRules[0].apply;
204
-
205
- const rulesByPriority = new Map<number, LayoutRule[]>();
206
- for (const rule of matchingRules) {
207
- if (!rulesByPriority.has(rule.priority)) {
208
- rulesByPriority.set(rule.priority, []);
209
- }
210
- rulesByPriority.get(rule.priority)!.push(rule);
211
- }
212
-
213
- const priorities = Array.from(rulesByPriority.keys()).sort((a, b) => b - a);
214
- const highestPriorityRules = rulesByPriority.get(priorities[0])!;
215
-
216
- if (highestPriorityRules.length === 1) {
217
- return highestPriorityRules[0].apply;
218
- }
219
-
220
- return this.resolveEqualPriorityConflicts(highestPriorityRules);
221
- }
222
-
223
- private resolveEqualPriorityConflicts(rules: LayoutRule[]): boolean {
224
- const applyCount = rules.filter(rule => rule.apply).length;
225
- const skipCount = rules.filter(rule => !rule.apply).length;
226
-
227
- if (skipCount > applyCount) {
228
- if (this.developmentMode) console.log(`[LayoutMatcher] Conflict resolution: SKIP`);
229
- return false;
230
- } else if (applyCount > skipCount) {
231
- if (this.developmentMode) console.log(`[LayoutMatcher] Conflict resolution: APPLY`);
232
- return true;
233
- } else {
234
- if (this.developmentMode) console.log(`[LayoutMatcher] Conflict resolution: SKIP (tie-breaker)`);
235
- return false;
236
- }
237
- }
238
-
239
- /**
240
- * Create a custom rule
241
- * Requirements: 4.3
242
- */
243
- static createCustomRule(matcher: (route: RouteInfo) => boolean, apply: boolean, priority: number = 10): LayoutRule {
244
- return { matches: matcher, apply, priority };
245
- }
246
-
247
- static createPathRule(pathPattern: string | RegExp, apply: boolean, priority: number = 10): LayoutRule {
248
- const matches =
249
- typeof pathPattern === 'string'
250
- ? (route: RouteInfo) => route.path.includes(pathPattern)
251
- : (route: RouteInfo) => pathPattern.test(route.path);
252
- return { matches, apply, priority };
253
- }
254
-
255
- static createHeaderRule(
256
- headerName: string,
257
- headerValue: string | RegExp,
258
- apply: boolean,
259
- priority: number = 10,
260
- ): LayoutRule {
261
- return {
262
- matches: (route: RouteInfo) => {
263
- const val = route.headers.get(headerName.toLowerCase());
264
- if (!val) return false;
265
- return typeof headerValue === 'string' ? val === headerValue : headerValue.test(val);
266
- },
267
- apply,
268
- priority,
269
- };
270
- }
271
-
272
- static createMethodRule(methods: string | string[], apply: boolean, priority: number = 10): LayoutRule {
273
- const normalizedMethods = new Set((Array.isArray(methods) ? methods : [methods]).map(m => m.toUpperCase()));
274
- return {
275
- matches: (route: RouteInfo) => normalizedMethods.has(route.method.toUpperCase()),
276
- apply,
277
- priority,
278
- };
279
- }
280
-
281
- getDebugInfo(
282
- layoutPath: string,
283
- route: RouteInfo,
284
- ): {
285
- totalRules: number;
286
- matchingRules: Array<{ priority: number; apply: boolean }>;
287
- finalDecision: boolean;
288
- conflictResolution?: string;
289
- } {
290
- const matchingRules = this.getMatchingRules(route, layoutPath);
291
- const finalDecision = this.shouldApplyLayout(layoutPath, route);
292
- return {
293
- totalRules: this.rules.length,
294
- matchingRules: matchingRules.map(rule => ({ priority: rule.priority, apply: rule.apply })),
295
- finalDecision,
296
- conflictResolution: matchingRules.length > 1 ? 'priority-based' : 'single-rule',
297
- };
298
- }
299
- }
@@ -1,110 +0,0 @@
1
- /**
2
- * Shared layout type definitions for the core/layout module.
3
- *
4
- * These are pure TypeScript interfaces with NO zod dependency,
5
- * intentionally kept separate from schemas/layout.ts to avoid
6
- * importing zod at cold start time.
7
- */
8
-
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- export type ComponentType<P = any> = ((props: P) => any) | (new (props: P) => any);
11
-
12
- export interface LayoutContext {
13
- request: Request;
14
- params: Record<string, string>;
15
- query: URLSearchParams;
16
- state: Map<string, unknown>;
17
- middlewareContext?: unknown;
18
- }
19
-
20
- export type LayoutData = Record<string, unknown>;
21
-
22
- export type LayoutLoader = (ctx: LayoutContext) => Promise<LayoutData>;
23
-
24
- export interface LayoutProps {
25
- children: import('preact').ComponentChildren;
26
- data: LayoutData;
27
- frontmatter?: Record<string, unknown>;
28
- route: {
29
- path: string;
30
- params: Record<string, string>;
31
- query: URLSearchParams;
32
- };
33
- }
34
-
35
- export interface LayoutHandler {
36
- component: ComponentType<LayoutProps>;
37
- loader?: LayoutLoader;
38
- path: string;
39
- priority: number;
40
- }
41
-
42
- export interface LayoutRoute {
43
- pattern: URLPattern;
44
- layoutPath: string;
45
- priority: number;
46
- type: 'root' | 'nested';
47
- depth: number;
48
- }
49
-
50
- export interface LayoutDiscoveryOptions {
51
- baseDirectory: string;
52
- filePattern?: string;
53
- excludeDirectories?: string[];
54
- enableWatching?: boolean;
55
- developmentMode?: boolean;
56
- }
57
-
58
- export interface LayoutConfig {
59
- skipLayouts?: string[];
60
- replaceLayout?: boolean;
61
- onlyLayouts?: string[];
62
- customLayout?: string;
63
- }
64
-
65
- export interface RouteInfo {
66
- path: string;
67
- params: Record<string, string>;
68
- method: string;
69
- headers: Headers;
70
- }
71
-
72
- export interface LayoutRule {
73
- matches: (route: RouteInfo, layoutPath?: string) => boolean;
74
- apply: boolean;
75
- priority: number;
76
- }
77
-
78
- export interface LayoutErrorInfo {
79
- layoutPath: string;
80
- errorType: 'component' | 'loader' | 'rendering' | 'island';
81
- timestamp: number;
82
- componentStack?: string;
83
- errorBoundary?: string;
84
- }
85
-
86
- export interface ResolvedLayout {
87
- handlers: LayoutHandler[];
88
- dataLoaders: LayoutLoader[];
89
- errorBoundaries: unknown[];
90
- streamingComponents: unknown[];
91
- metadata: {
92
- totalLayouts: number;
93
- resolutionTime: number;
94
- cacheHit: boolean;
95
- };
96
- }
97
-
98
- export interface LayoutCache {
99
- resolved: Map<string, ResolvedLayout>;
100
- handlers: Map<string, LayoutHandler>;
101
- data: Map<string, LayoutData>;
102
- ttl: Map<string, number>;
103
- }
104
-
105
- export interface PageModule {
106
- default: ComponentType<Record<string, unknown>>;
107
- layoutConfig?: LayoutConfig;
108
- loader?: LayoutLoader;
109
- frontmatter?: Record<string, unknown>;
110
- }