@useavalon/avalon 0.1.11 → 0.1.12

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 (250) hide show
  1. package/README.md +54 -54
  2. package/dist/mod.js +1 -0
  3. package/dist/src/build/integration-bundler-plugin.js +1 -0
  4. package/dist/src/build/integration-config.js +1 -0
  5. package/dist/src/build/integration-detection-plugin.js +1 -0
  6. package/dist/src/build/integration-resolver-plugin.js +1 -0
  7. package/dist/src/build/island-manifest.js +1 -0
  8. package/dist/src/build/island-types-generator.js +5 -0
  9. package/dist/src/build/mdx-island-transform.js +2 -0
  10. package/dist/src/build/mdx-plugin.js +1 -0
  11. package/dist/src/build/page-island-transform.js +3 -0
  12. package/dist/src/build/prop-extractors/index.js +1 -0
  13. package/dist/src/build/prop-extractors/lit.js +1 -0
  14. package/dist/src/build/prop-extractors/qwik.js +1 -0
  15. package/dist/src/build/prop-extractors/solid.js +1 -0
  16. package/dist/src/build/prop-extractors/svelte.js +1 -0
  17. package/dist/src/build/prop-extractors/vue.js +1 -0
  18. package/dist/src/build/sidecar-file-manager.js +1 -0
  19. package/dist/src/build/sidecar-renderer.js +6 -0
  20. package/dist/src/client/adapters/index.js +1 -0
  21. package/dist/src/client/components.js +1 -0
  22. package/dist/src/client/css-hmr-handler.js +1 -0
  23. package/dist/src/client/framework-adapter.js +13 -0
  24. package/dist/src/client/hmr-coordinator.js +1 -0
  25. package/dist/src/client/hmr-error-overlay.js +214 -0
  26. package/dist/src/client/main.js +39 -0
  27. package/{src → dist/src}/client/types/framework-runtime.d.ts +68 -68
  28. package/{src → dist/src}/client/types/vite-hmr.d.ts +46 -46
  29. package/dist/src/client/types/vite-virtual-modules.d.ts +70 -0
  30. package/dist/src/components/Image.js +1 -0
  31. package/dist/src/components/IslandErrorBoundary.js +1 -0
  32. package/dist/src/components/LayoutDataErrorBoundary.js +1 -0
  33. package/dist/src/components/LayoutErrorBoundary.js +1 -0
  34. package/dist/src/components/PersistentIsland.js +1 -0
  35. package/dist/src/components/StreamingErrorBoundary.js +1 -0
  36. package/dist/src/components/StreamingLayout.js +29 -0
  37. package/dist/src/core/components/component-analyzer.js +1 -0
  38. package/dist/src/core/components/component-detection.js +5 -0
  39. package/dist/src/core/components/enhanced-framework-detector.js +1 -0
  40. package/dist/src/core/components/framework-registry.js +1 -0
  41. package/dist/src/core/content/mdx-processor.js +1 -0
  42. package/dist/src/core/integrations/index.js +1 -0
  43. package/dist/src/core/integrations/loader.js +1 -0
  44. package/dist/src/core/integrations/registry.js +1 -0
  45. package/dist/src/core/islands/island-persistence.js +1 -0
  46. package/dist/src/core/islands/island-state-serializer.js +1 -0
  47. package/dist/src/core/islands/persistent-island-context.js +1 -0
  48. package/dist/src/core/islands/use-persistent-state.js +1 -0
  49. package/dist/src/core/layout/enhanced-layout-resolver.js +1 -0
  50. package/dist/src/core/layout/layout-cache-manager.js +1 -0
  51. package/dist/src/core/layout/layout-composer.js +1 -0
  52. package/dist/src/core/layout/layout-data-loader.js +1 -0
  53. package/dist/src/core/layout/layout-discovery.js +1 -0
  54. package/dist/src/core/layout/layout-matcher.js +1 -0
  55. package/dist/src/core/layout/layout-types.js +1 -0
  56. package/dist/src/core/modules/framework-module-resolver.js +1 -0
  57. package/dist/src/islands/component-analysis.js +1 -0
  58. package/dist/src/islands/css-utils.js +17 -0
  59. package/dist/src/islands/discovery/index.js +1 -0
  60. package/dist/src/islands/discovery/registry.js +1 -0
  61. package/dist/src/islands/discovery/resolver.js +2 -0
  62. package/dist/src/islands/discovery/scanner.js +1 -0
  63. package/dist/src/islands/discovery/types.js +1 -0
  64. package/dist/src/islands/discovery/validator.js +18 -0
  65. package/dist/src/islands/discovery/watcher.js +1 -0
  66. package/dist/src/islands/framework-detection.js +1 -0
  67. package/dist/src/islands/integration-loader.js +1 -0
  68. package/dist/src/islands/island.js +1 -0
  69. package/dist/src/islands/render-cache.js +1 -0
  70. package/dist/src/islands/types.js +1 -0
  71. package/dist/src/islands/universal-css-collector.js +5 -0
  72. package/dist/src/islands/universal-head-collector.js +2 -0
  73. package/{src → dist/src}/layout-system.d.ts +592 -592
  74. package/dist/src/layout-system.js +1 -0
  75. package/dist/src/middleware/discovery.js +1 -0
  76. package/dist/src/middleware/executor.js +1 -0
  77. package/dist/src/middleware/index.js +1 -0
  78. package/dist/src/middleware/types.js +1 -0
  79. package/dist/src/nitro/build-config.js +1 -0
  80. package/dist/src/nitro/config.js +1 -0
  81. package/dist/src/nitro/error-handler.js +198 -0
  82. package/dist/src/nitro/index.js +1 -0
  83. package/dist/src/nitro/island-manifest.js +2 -0
  84. package/dist/src/nitro/middleware-adapter.js +1 -0
  85. package/dist/src/nitro/renderer.js +183 -0
  86. package/dist/src/nitro/route-discovery.js +1 -0
  87. package/dist/src/nitro/types.js +1 -0
  88. package/dist/src/render/collect-css.js +3 -0
  89. package/{src/render/error-pages.ts → dist/src/render/error-pages.js} +7 -38
  90. package/dist/src/render/isolated-ssr-renderer.js +1 -0
  91. package/dist/src/render/ssr.js +90 -0
  92. package/dist/src/schemas/api.js +1 -0
  93. package/dist/src/schemas/core.js +1 -0
  94. package/dist/src/schemas/index.js +1 -0
  95. package/dist/src/schemas/layout.js +1 -0
  96. package/dist/src/schemas/routing/index.js +1 -0
  97. package/dist/src/schemas/routing.js +1 -0
  98. package/dist/src/types/as-island.js +1 -0
  99. package/{src → dist/src}/types/image.d.ts +106 -106
  100. package/{src → dist/src}/types/index.d.ts +22 -22
  101. package/{src → dist/src}/types/island-jsx.d.ts +33 -33
  102. package/{src → dist/src}/types/island-prop.d.ts +20 -20
  103. package/dist/src/types/layout.js +1 -0
  104. package/{src → dist/src}/types/mdx.d.ts +6 -6
  105. package/dist/src/types/routing.js +1 -0
  106. package/dist/src/types/types.js +1 -0
  107. package/{src → dist/src}/types/urlpattern.d.ts +49 -49
  108. package/{src → dist/src}/types/vite-env.d.ts +11 -11
  109. package/dist/src/utils/dev-logger.js +12 -0
  110. package/dist/src/utils/fs.js +1 -0
  111. package/dist/src/vite-plugin/auto-discover.js +1 -0
  112. package/dist/src/vite-plugin/config.js +1 -0
  113. package/dist/src/vite-plugin/errors.js +1 -0
  114. package/dist/src/vite-plugin/image-optimization.js +45 -0
  115. package/dist/src/vite-plugin/integration-activator.js +1 -0
  116. package/dist/src/vite-plugin/island-sidecar-plugin.js +1 -0
  117. package/dist/src/vite-plugin/module-discovery.js +1 -0
  118. package/dist/src/vite-plugin/nitro-integration.js +42 -0
  119. package/dist/src/vite-plugin/plugin.js +1 -0
  120. package/dist/src/vite-plugin/types.js +1 -0
  121. package/dist/src/vite-plugin/validation.js +2 -0
  122. package/package.json +57 -26
  123. package/mod.ts +0 -302
  124. package/src/build/integration-bundler-plugin.ts +0 -116
  125. package/src/build/integration-config.ts +0 -168
  126. package/src/build/integration-detection-plugin.ts +0 -117
  127. package/src/build/integration-resolver-plugin.ts +0 -90
  128. package/src/build/island-manifest.ts +0 -269
  129. package/src/build/island-types-generator.ts +0 -476
  130. package/src/build/mdx-island-transform.ts +0 -464
  131. package/src/build/mdx-plugin.ts +0 -98
  132. package/src/build/page-island-transform.ts +0 -598
  133. package/src/build/prop-extractors/index.ts +0 -21
  134. package/src/build/prop-extractors/lit.ts +0 -140
  135. package/src/build/prop-extractors/qwik.ts +0 -16
  136. package/src/build/prop-extractors/solid.ts +0 -125
  137. package/src/build/prop-extractors/svelte.ts +0 -194
  138. package/src/build/prop-extractors/vue.ts +0 -111
  139. package/src/build/sidecar-file-manager.ts +0 -104
  140. package/src/build/sidecar-renderer.ts +0 -30
  141. package/src/client/adapters/index.js +0 -12
  142. package/src/client/adapters/index.ts +0 -13
  143. package/src/client/adapters/lit-adapter.js +0 -467
  144. package/src/client/adapters/lit-adapter.ts +0 -654
  145. package/src/client/adapters/preact-adapter.js +0 -223
  146. package/src/client/adapters/preact-adapter.ts +0 -331
  147. package/src/client/adapters/qwik-adapter.js +0 -259
  148. package/src/client/adapters/qwik-adapter.ts +0 -345
  149. package/src/client/adapters/react-adapter.js +0 -220
  150. package/src/client/adapters/react-adapter.ts +0 -353
  151. package/src/client/adapters/solid-adapter.js +0 -295
  152. package/src/client/adapters/solid-adapter.ts +0 -451
  153. package/src/client/adapters/svelte-adapter.js +0 -368
  154. package/src/client/adapters/svelte-adapter.ts +0 -524
  155. package/src/client/adapters/vue-adapter.js +0 -278
  156. package/src/client/adapters/vue-adapter.ts +0 -467
  157. package/src/client/components.js +0 -23
  158. package/src/client/components.ts +0 -35
  159. package/src/client/css-hmr-handler.js +0 -263
  160. package/src/client/css-hmr-handler.ts +0 -344
  161. package/src/client/framework-adapter.js +0 -283
  162. package/src/client/framework-adapter.ts +0 -462
  163. package/src/client/hmr-coordinator.js +0 -274
  164. package/src/client/hmr-coordinator.ts +0 -396
  165. package/src/client/hmr-error-overlay.js +0 -533
  166. package/src/client/main.js +0 -816
  167. package/src/client/types/vite-virtual-modules.d.ts +0 -60
  168. package/src/components/Image.tsx +0 -123
  169. package/src/components/IslandErrorBoundary.tsx +0 -145
  170. package/src/components/LayoutDataErrorBoundary.tsx +0 -141
  171. package/src/components/LayoutErrorBoundary.tsx +0 -127
  172. package/src/components/PersistentIsland.tsx +0 -52
  173. package/src/components/StreamingErrorBoundary.tsx +0 -233
  174. package/src/components/StreamingLayout.tsx +0 -538
  175. package/src/core/components/component-analyzer.ts +0 -192
  176. package/src/core/components/component-detection.ts +0 -508
  177. package/src/core/components/enhanced-framework-detector.ts +0 -500
  178. package/src/core/components/framework-registry.ts +0 -563
  179. package/src/core/content/mdx-processor.ts +0 -46
  180. package/src/core/integrations/index.ts +0 -19
  181. package/src/core/integrations/loader.ts +0 -125
  182. package/src/core/integrations/registry.ts +0 -175
  183. package/src/core/islands/island-persistence.ts +0 -325
  184. package/src/core/islands/island-state-serializer.ts +0 -258
  185. package/src/core/islands/persistent-island-context.tsx +0 -80
  186. package/src/core/islands/use-persistent-state.ts +0 -68
  187. package/src/core/layout/enhanced-layout-resolver.ts +0 -322
  188. package/src/core/layout/layout-cache-manager.ts +0 -485
  189. package/src/core/layout/layout-composer.ts +0 -357
  190. package/src/core/layout/layout-data-loader.ts +0 -516
  191. package/src/core/layout/layout-discovery.ts +0 -243
  192. package/src/core/layout/layout-matcher.ts +0 -299
  193. package/src/core/layout/layout-types.ts +0 -110
  194. package/src/core/modules/framework-module-resolver.ts +0 -273
  195. package/src/islands/component-analysis.ts +0 -213
  196. package/src/islands/css-utils.ts +0 -565
  197. package/src/islands/discovery/index.ts +0 -80
  198. package/src/islands/discovery/registry.ts +0 -340
  199. package/src/islands/discovery/resolver.ts +0 -477
  200. package/src/islands/discovery/scanner.ts +0 -386
  201. package/src/islands/discovery/types.ts +0 -117
  202. package/src/islands/discovery/validator.ts +0 -544
  203. package/src/islands/discovery/watcher.ts +0 -368
  204. package/src/islands/framework-detection.ts +0 -428
  205. package/src/islands/integration-loader.ts +0 -490
  206. package/src/islands/island.tsx +0 -565
  207. package/src/islands/render-cache.ts +0 -550
  208. package/src/islands/types.ts +0 -80
  209. package/src/islands/universal-css-collector.ts +0 -157
  210. package/src/islands/universal-head-collector.ts +0 -137
  211. package/src/layout-system.ts +0 -218
  212. package/src/middleware/discovery.ts +0 -268
  213. package/src/middleware/executor.ts +0 -315
  214. package/src/middleware/index.ts +0 -76
  215. package/src/middleware/types.ts +0 -99
  216. package/src/nitro/build-config.ts +0 -576
  217. package/src/nitro/config.ts +0 -483
  218. package/src/nitro/error-handler.ts +0 -636
  219. package/src/nitro/index.ts +0 -173
  220. package/src/nitro/island-manifest.ts +0 -584
  221. package/src/nitro/middleware-adapter.ts +0 -260
  222. package/src/nitro/renderer.ts +0 -1471
  223. package/src/nitro/route-discovery.ts +0 -439
  224. package/src/nitro/types.ts +0 -321
  225. package/src/render/collect-css.ts +0 -198
  226. package/src/render/isolated-ssr-renderer.ts +0 -654
  227. package/src/render/ssr.ts +0 -1030
  228. package/src/schemas/api.ts +0 -30
  229. package/src/schemas/core.ts +0 -64
  230. package/src/schemas/index.ts +0 -212
  231. package/src/schemas/layout.ts +0 -279
  232. package/src/schemas/routing/index.ts +0 -38
  233. package/src/schemas/routing.ts +0 -376
  234. package/src/types/as-island.ts +0 -20
  235. package/src/types/layout.ts +0 -285
  236. package/src/types/routing.ts +0 -555
  237. package/src/types/types.ts +0 -5
  238. package/src/utils/dev-logger.ts +0 -299
  239. package/src/utils/fs.ts +0 -151
  240. package/src/vite-plugin/auto-discover.ts +0 -551
  241. package/src/vite-plugin/config.ts +0 -266
  242. package/src/vite-plugin/errors.ts +0 -127
  243. package/src/vite-plugin/image-optimization.ts +0 -156
  244. package/src/vite-plugin/integration-activator.ts +0 -126
  245. package/src/vite-plugin/island-sidecar-plugin.ts +0 -176
  246. package/src/vite-plugin/module-discovery.ts +0 -189
  247. package/src/vite-plugin/nitro-integration.ts +0 -1354
  248. package/src/vite-plugin/plugin.ts +0 -409
  249. package/src/vite-plugin/types.ts +0 -327
  250. package/src/vite-plugin/validation.ts +0 -228
@@ -1,439 +0,0 @@
1
- /**
2
- * Route Discovery Module for Nitro
3
- *
4
- * This module provides minimal route discovery for Avalon's SSR pages.
5
- *
6
- * IMPORTANT: This module is simplified to complement Nitro's native routing:
7
- * - API routes: Handled by Nitro's auto-discovery from `api/` directory
8
- * - Page routes: Discovered here for SSR rendering (pages are components, not h3 handlers)
9
- * - Middleware: Handled by Nitro's auto-discovery from `middleware/` directory
10
- *
11
- * The page discovery is needed because Avalon pages are React/Vue/Svelte components
12
- * that require SSR rendering, which is different from Nitro's h3 route handlers.
13
- *
14
- * @module nitro/route-discovery
15
- */
16
-
17
- import { stat } from "node:fs/promises";
18
- import { basename, dirname, relative } from "node:path";
19
- import { walk } from "../utils/fs.ts";
20
- import type { DiscoveredRoute } from "./types.ts";
21
-
22
- /**
23
- * Supported page file extensions
24
- */
25
- export const PAGE_EXTENSIONS = [
26
- ".tsx",
27
- ".ts",
28
- ".jsx",
29
- ".js",
30
- ".vue",
31
- ".svelte",
32
- ".md",
33
- ".mdx",
34
- ];
35
-
36
- /**
37
- * Options for page route discovery
38
- */
39
- export interface PageDiscoveryOptions {
40
- /** Pages directory path (absolute or relative to project root) */
41
- pagesDir: string;
42
- /** Enable development mode logging */
43
- developmentMode?: boolean;
44
- /** Directories to exclude from scanning */
45
- excludeDirectories?: string[];
46
- }
47
-
48
- /**
49
- * Result of file path to pattern conversion
50
- */
51
- export interface FilePathPatternResult {
52
- /** Route pattern (e.g., /users/:id) */
53
- pattern: string;
54
- /** Extracted parameter names */
55
- params: string[];
56
- }
57
-
58
- /**
59
- * Discovers page routes from multiple directories with route prefixes
60
- *
61
- * This supports modular architecture where pages can be in different modules,
62
- * each with their own route prefix.
63
- *
64
- * @param pageDirs - Array of page directories with their route prefixes
65
- * @param options - Discovery options
66
- * @returns Array of discovered page routes
67
- */
68
- export async function discoverPageRoutesFromMultipleDirs(
69
- pageDirs: Array<{ dir: string; prefix: string }>,
70
- options?: Pick<PageDiscoveryOptions, "developmentMode" | "excludeDirectories">
71
- ): Promise<DiscoveredRoute[]> {
72
- const allRoutes: DiscoveredRoute[] = [];
73
-
74
- for (const { dir, prefix } of pageDirs) {
75
- const routes = await discoverPageRoutes(dir, options);
76
-
77
- // Apply prefix to routes (except for root prefix '/')
78
- for (const route of routes) {
79
- if (prefix !== '/') {
80
- // Combine prefix with pattern
81
- // /prefix + /path -> /prefix/path
82
- // /prefix + / -> /prefix
83
- const combinedPattern = route.pattern === '/'
84
- ? prefix
85
- : prefix + route.pattern;
86
- route.pattern = combinedPattern;
87
- }
88
- allRoutes.push(route);
89
- }
90
- }
91
-
92
- // Sort all routes by specificity
93
- return sortRoutesBySpecificity(allRoutes);
94
- }
95
-
96
- /**
97
- * Discovers page routes from the pages directory for SSR rendering
98
- *
99
- * NOTE: This is specifically for page components that need SSR rendering.
100
- * API routes should be placed in the `api/` directory and are auto-discovered
101
- * by Nitro's native file-system routing.
102
- *
103
- * @param pagesDir - Path to the pages directory
104
- * @param options - Discovery options
105
- * @returns Array of discovered page routes
106
- *
107
- * @example
108
- * ```ts
109
- * const routes = await discoverPageRoutes('src/pages', {
110
- * developmentMode: true,
111
- * });
112
- * ```
113
- */
114
- export async function discoverPageRoutes(
115
- pagesDir: string,
116
- options?: Pick<PageDiscoveryOptions, "developmentMode" | "excludeDirectories">
117
- ): Promise<DiscoveredRoute[]> {
118
- const routes: DiscoveredRoute[] = [];
119
- const excludeDirs = options?.excludeDirectories ?? ["node_modules", ".git"];
120
-
121
- try {
122
- // Check if directory exists
123
- const statResult = await stat(pagesDir);
124
- if (!statResult.isDirectory()) {
125
- if (options?.developmentMode) {
126
- console.warn(
127
- `[route-discovery] Pages path is not a directory: ${pagesDir}`
128
- );
129
- }
130
- return [];
131
- }
132
- } catch (error) {
133
- if (error instanceof Error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
134
- if (options?.developmentMode) {
135
- console.warn(
136
- `[route-discovery] Pages directory not found: ${pagesDir}`
137
- );
138
- }
139
- return [];
140
- }
141
- throw error;
142
- }
143
-
144
- // Walk through the pages directory
145
- const extensions = PAGE_EXTENSIONS.map((e) => e.slice(1)); // Remove leading dot
146
-
147
- for await (
148
- const entry of walk(pagesDir, {
149
- includeDirs: false,
150
- includeSymlinks: false,
151
- exts: extensions,
152
- })
153
- ) {
154
- if (!entry.isFile) continue;
155
-
156
- const relativePath = relative(pagesDir, entry.path);
157
-
158
- // Skip files in excluded directories
159
- if (excludeDirs.some((dir) => relativePath.includes(dir))) {
160
- continue;
161
- }
162
-
163
- // Skip private files (in folders starting with _)
164
- if (isPrivateFile(relativePath)) {
165
- continue;
166
- }
167
-
168
- // Convert file path to route pattern
169
- const { pattern, params } = filePathToPattern(relativePath);
170
-
171
- routes.push({
172
- type: "page",
173
- filePath: entry.path,
174
- pattern,
175
- params,
176
- });
177
- }
178
-
179
- // Sort routes by specificity (more specific routes first)
180
- return sortRoutesBySpecificity(routes);
181
- }
182
-
183
- /**
184
- * Converts a file path to a route pattern
185
- *
186
- * Handles:
187
- * - Index files (index.tsx -> /)
188
- * - Dynamic segments ([param] -> :param)
189
- * - Catch-all segments ([...slug] -> **)
190
- * - Route groups ((group) -> removed from path)
191
- *
192
- * @param filePath - Relative file path from pages directory
193
- * @returns Pattern and extracted parameter names
194
- *
195
- * @example
196
- * ```ts
197
- * filePathToPattern('users/[id].tsx')
198
- * // { pattern: '/users/:id', params: ['id'] }
199
- *
200
- * filePathToPattern('blog/[...slug].tsx')
201
- * // { pattern: '/blog/**', params: ['slug'] }
202
- * ```
203
- */
204
- export function filePathToPattern(filePath: string): FilePathPatternResult {
205
- const params: string[] = [];
206
-
207
- let pattern = filePath
208
- // Normalize path separators
209
- .replace(/\\/g, "/")
210
- // Remove file extension
211
- .replace(/\.(tsx|ts|jsx|js|vue|svelte|md|mdx)$/, "")
212
- // Remove route groups (parentheses)
213
- .replace(/\([^)]+\)\//g, "")
214
- .replace(/\([^)]+\)$/, "");
215
-
216
- // Handle index files
217
- if (basename(pattern) === "index") {
218
- pattern = dirname(pattern);
219
- if (pattern === ".") {
220
- pattern = "";
221
- }
222
- }
223
-
224
- // Convert dynamic segments [param] to :param
225
- // Convert catch-all segments [...slug] to **
226
- pattern = pattern.replace(/\[([^\]]+)\]/g, (_, param) => {
227
- if (param.startsWith("...")) {
228
- // Catch-all segment
229
- const paramName = param.slice(3);
230
- params.push(paramName);
231
- return "**";
232
- } else {
233
- // Dynamic segment
234
- params.push(param);
235
- return `:${param}`;
236
- }
237
- });
238
-
239
- // Ensure leading slash
240
- if (!pattern.startsWith("/")) {
241
- pattern = "/" + pattern;
242
- }
243
-
244
- // Handle root path
245
- if (pattern === "/" || pattern === "") {
246
- pattern = "/";
247
- }
248
-
249
- // Remove trailing slash (except for root)
250
- if (pattern.length > 1 && pattern.endsWith("/")) {
251
- pattern = pattern.slice(0, -1);
252
- }
253
-
254
- return { pattern, params };
255
- }
256
-
257
- /**
258
- * Checks if a file is in a private folder (starts with _)
259
- *
260
- * @param relativePath - Relative file path
261
- * @returns True if the file is private
262
- */
263
- export function isPrivateFile(relativePath: string): boolean {
264
- const pathParts = relativePath.split(/[/\\]/);
265
- return pathParts.some((part) => part.startsWith("_"));
266
- }
267
-
268
- /**
269
- * Calculates route specificity score for sorting
270
- * Lower score = more specific = higher priority
271
- *
272
- * @param route - Discovered route
273
- * @returns Specificity score
274
- */
275
- export function calculateRouteSpecificity(route: DiscoveredRoute): number {
276
- const segments = route.pattern.split("/").filter((s) => s.length > 0);
277
- let score = 0;
278
-
279
- for (const segment of segments) {
280
- if (segment === "**") {
281
- // Catch-all has lowest priority
282
- score += 1000;
283
- } else if (segment.startsWith(":")) {
284
- // Dynamic segment has medium priority
285
- score += 100;
286
- } else {
287
- // Static segment has highest priority
288
- score += 1;
289
- }
290
- }
291
-
292
- // Shorter paths are more specific (for same type of segments)
293
- score += segments.length;
294
-
295
- return score;
296
- }
297
-
298
- /**
299
- * Sorts routes by specificity (most specific first)
300
- *
301
- * @param routes - Array of discovered routes
302
- * @returns Sorted array of routes
303
- */
304
- export function sortRoutesBySpecificity(
305
- routes: DiscoveredRoute[]
306
- ): DiscoveredRoute[] {
307
- return [...routes].sort((a, b) => {
308
- const scoreA = calculateRouteSpecificity(a);
309
- const scoreB = calculateRouteSpecificity(b);
310
- return scoreA - scoreB;
311
- });
312
- }
313
-
314
- /**
315
- * Validates a route pattern for correctness
316
- *
317
- * @param pattern - Route pattern to validate
318
- * @returns Array of validation errors (empty if valid)
319
- */
320
- export function validateRoutePattern(pattern: string): string[] {
321
- const errors: string[] = [];
322
-
323
- // Check for leading slash
324
- if (!pattern.startsWith("/")) {
325
- errors.push("Route pattern must start with /");
326
- }
327
-
328
- // Check for malformed dynamic segments
329
- const malformedSegments = pattern.match(/\[[^\]]*$/g);
330
- if (malformedSegments) {
331
- errors.push(
332
- `Malformed dynamic segments: ${malformedSegments.join(", ")}. Dynamic segments must be properly closed with ]`
333
- );
334
- }
335
-
336
- // Check for empty dynamic segments
337
- const emptySegments = pattern.match(/\[\]/g);
338
- if (emptySegments) {
339
- errors.push(
340
- "Empty dynamic segments [] are not allowed. Use [param] for dynamic segments or [...rest] for catch-all"
341
- );
342
- }
343
-
344
- // Check for nested dynamic segments
345
- const nestedSegments = pattern.match(/\[[^\]]*\[[^\]]*\]/g);
346
- if (nestedSegments) {
347
- errors.push(
348
- `Nested dynamic segments are not supported: ${nestedSegments.join(", ")}`
349
- );
350
- }
351
-
352
- return errors;
353
- }
354
-
355
- /**
356
- * Extracts parameter names from a route pattern
357
- *
358
- * @param pattern - Route pattern (e.g., /users/:id/posts/:postId)
359
- * @returns Array of parameter names
360
- */
361
- export function extractParamsFromPattern(pattern: string): string[] {
362
- const params: string[] = [];
363
-
364
- // Match :param patterns
365
- const dynamicMatches = pattern.matchAll(/:([^/]+)/g);
366
- for (const match of dynamicMatches) {
367
- params.push(match[1]);
368
- }
369
-
370
- // Match ** catch-all (represented as unnamed param)
371
- if (pattern.includes("**")) {
372
- params.push("slug");
373
- }
374
-
375
- return params;
376
- }
377
-
378
- /**
379
- * Checks if a route pattern matches a given URL path
380
- *
381
- * @param pattern - Route pattern
382
- * @param path - URL path to match
383
- * @returns True if the pattern matches the path
384
- */
385
- export function matchRoutePattern(
386
- pattern: string,
387
- path: string
388
- ): { matches: boolean; params: Record<string, string> } {
389
- const params: Record<string, string> = {};
390
-
391
- // Normalize paths
392
- const normalizedPattern = pattern.replace(/\/$/, "") || "/";
393
- const normalizedPath = path.replace(/\/$/, "") || "/";
394
-
395
- const patternSegments = normalizedPattern.split("/").filter((s) => s);
396
- const pathSegments = normalizedPath.split("/").filter((s) => s);
397
-
398
- let patternIndex = 0;
399
- let pathIndex = 0;
400
-
401
- while (patternIndex < patternSegments.length) {
402
- const patternSegment = patternSegments[patternIndex];
403
-
404
- if (patternSegment === "**") {
405
- // Catch-all: match remaining path segments
406
- const remainingPath = pathSegments.slice(pathIndex).join("/");
407
- params["slug"] = remainingPath;
408
- return { matches: true, params };
409
- }
410
-
411
- if (pathIndex >= pathSegments.length) {
412
- // No more path segments but pattern has more
413
- return { matches: false, params: {} };
414
- }
415
-
416
- const pathSegment = pathSegments[pathIndex];
417
-
418
- if (patternSegment.startsWith(":")) {
419
- // Dynamic segment: extract param value
420
- const paramName = patternSegment.slice(1);
421
- params[paramName] = pathSegment;
422
- } else if (patternSegment !== pathSegment) {
423
- // Static segment: must match exactly
424
- return { matches: false, params: {} };
425
- }
426
-
427
- patternIndex++;
428
- pathIndex++;
429
- }
430
-
431
- // Check if all path segments were consumed
432
- if (pathIndex < pathSegments.length) {
433
- return { matches: false, params: {} };
434
- }
435
-
436
- return { matches: true, params };
437
- }
438
-
439
-