@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,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
-