@stacksjs/bun-router 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +261 -584
  2. package/bin/cli.ts +9 -0
  3. package/dist/auth.d.ts +134 -0
  4. package/dist/cache/lru-cache.d.ts +139 -0
  5. package/dist/cache/middleware-memoization.d.ts +151 -0
  6. package/dist/cache/route-cache-warmer.d.ts +161 -0
  7. package/dist/cache/sqlite-cache.d.ts +209 -0
  8. package/dist/cache/streaming-cache.d.ts +132 -0
  9. package/dist/chunk-j0e7z7hd.js +18053 -0
  10. package/dist/cli/colors.d.ts +15 -0
  11. package/dist/cli/index.d.ts +10 -0
  12. package/dist/cli/middleware.d.ts +31 -0
  13. package/dist/cli/openapi.d.ts +17 -0
  14. package/dist/cli/router.d.ts +15 -0
  15. package/dist/cli/routes.d.ts +29 -0
  16. package/dist/cli/utils.d.ts +74 -0
  17. package/dist/cli.d.ts +1 -0
  18. package/dist/cli.js +3360 -0
  19. package/dist/config.d.ts +7 -0
  20. package/dist/container/container.d.ts +273 -0
  21. package/dist/container/contextual-binding.d.ts +240 -0
  22. package/dist/container/decorators.d.ts +141 -0
  23. package/dist/container/service-provider.d.ts +285 -0
  24. package/dist/development/hot-reload.d.ts +187 -0
  25. package/dist/development/index.d.ts +167 -0
  26. package/dist/development/performance-profiler.d.ts +217 -0
  27. package/dist/development/route-debugger.d.ts +154 -0
  28. package/dist/development/route-inspector.d.ts +211 -0
  29. package/dist/development/typescript-utilities.d.ts +209 -0
  30. package/dist/docs.d.ts +11 -0
  31. package/dist/errors/circuit-breaker.d.ts +195 -0
  32. package/dist/errors/error-handler.d.ts +88 -0
  33. package/dist/errors/error-reporting.d.ts +144 -0
  34. package/dist/errors/exceptions.d.ts +184 -0
  35. package/dist/errors/graceful-degradation.d.ts +154 -0
  36. package/dist/errors/index.d.ts +9 -0
  37. package/dist/errors/router-errors.d.ts +110 -0
  38. package/dist/file-serving/static-files.d.ts +143 -0
  39. package/dist/index.d.ts +14 -0
  40. package/dist/index.js +13968 -0
  41. package/dist/middleware/auth.d.ts +63 -0
  42. package/dist/middleware/content_security_policy.d.ts +53 -0
  43. package/dist/middleware/cors.d.ts +5 -0
  44. package/dist/middleware/csrf.d.ts +7 -0
  45. package/dist/middleware/ddos_protection.d.ts +39 -0
  46. package/dist/middleware/file_security.d.ts +26 -0
  47. package/dist/middleware/file_upload.d.ts +38 -0
  48. package/dist/middleware/helmet.d.ts +54 -0
  49. package/dist/middleware/index.d.ts +44 -0
  50. package/dist/middleware/input_validation.d.ts +45 -0
  51. package/dist/middleware/json_body.d.ts +4 -0
  52. package/dist/middleware/performance_alerting.d.ts +87 -0
  53. package/dist/middleware/performance_dashboard.d.ts +87 -0
  54. package/dist/middleware/performance_monitor.d.ts +209 -0
  55. package/dist/middleware/pipeline.d.ts +131 -0
  56. package/dist/middleware/rate_limit.d.ts +36 -0
  57. package/dist/middleware/request_id.d.ts +4 -0
  58. package/dist/middleware/request_signing.d.ts +153 -0
  59. package/dist/middleware/request_tracer.d.ts +72 -0
  60. package/dist/middleware/response_cache.d.ts +97 -0
  61. package/dist/middleware/security.d.ts +69 -0
  62. package/dist/middleware/security_suite.d.ts +46 -0
  63. package/dist/middleware/session.d.ts +23 -0
  64. package/dist/model-binding/index.d.ts +2 -0
  65. package/dist/model-binding/model-middleware.d.ts +118 -0
  66. package/dist/model-binding/model-registry.d.ts +164 -0
  67. package/dist/model-binding.d.ts +185 -0
  68. package/dist/model-resolver-factory.d.ts +31 -0
  69. package/dist/observability/correlation.d.ts +187 -0
  70. package/dist/observability/health-checks.d.ts +186 -0
  71. package/dist/observability/index.d.ts +60 -0
  72. package/dist/observability/integration.d.ts +147 -0
  73. package/dist/observability/metrics.d.ts +184 -0
  74. package/dist/observability/tracing.d.ts +187 -0
  75. package/dist/optimization/bun-utilities.d.ts +224 -0
  76. package/dist/query-builder-integration.d.ts +36 -0
  77. package/dist/request/context.d.ts +34 -0
  78. package/dist/request/enhanced-request.d.ts +213 -0
  79. package/dist/request/macros.d.ts +300 -0
  80. package/dist/response/macros.d.ts +256 -0
  81. package/dist/response/response-factory.d.ts +133 -0
  82. package/dist/router/api-routes.d.ts +5 -0
  83. package/dist/router/file-based-routing.d.ts +118 -0
  84. package/dist/router/file-streaming.d.ts +5 -0
  85. package/dist/router/fluent-router.d.ts +315 -0
  86. package/dist/router/fluent-routing.d.ts +271 -0
  87. package/dist/router/group-organization.d.ts +5 -0
  88. package/dist/router/handler-resolver.d.ts +24 -0
  89. package/dist/router/http-methods.d.ts +10 -0
  90. package/dist/router/index.d.ts +114 -0
  91. package/dist/router/middleware-groups.d.ts +94 -0
  92. package/dist/router/middleware-integration.d.ts +112 -0
  93. package/dist/router/middleware.d.ts +5 -0
  94. package/dist/router/model-binding.d.ts +5 -0
  95. package/dist/router/optimized-route-matching.d.ts +6 -0
  96. package/dist/router/route-building.d.ts +5 -0
  97. package/dist/router/route-compiler.d.ts +153 -0
  98. package/dist/router/route-matching.d.ts +5 -0
  99. package/dist/router/route-trie.d.ts +114 -0
  100. package/dist/router/router.d.ts +317 -0
  101. package/dist/router/server.d.ts +5 -0
  102. package/dist/router/validation-integration.d.ts +170 -0
  103. package/dist/router/view-rendering.d.ts +5 -0
  104. package/dist/router/websocket.d.ts +5 -0
  105. package/dist/routing/route-caching.d.ts +129 -0
  106. package/dist/routing/route-throttling.d.ts +150 -0
  107. package/dist/routing/subdomain-routing.d.ts +206 -0
  108. package/dist/session/database-store.d.ts +51 -0
  109. package/dist/session/file-store.d.ts +22 -0
  110. package/dist/session/index.d.ts +106 -0
  111. package/dist/session/memory-store.d.ts +21 -0
  112. package/dist/session/redis-store.d.ts +31 -0
  113. package/dist/streaming/index.d.ts +3 -0
  114. package/dist/streaming/sse-handler.d.ts +138 -0
  115. package/dist/streaming/stream-handler.d.ts +114 -0
  116. package/dist/testing/auth-testing.d.ts +156 -0
  117. package/dist/testing/file-upload-testing.d.ts +190 -0
  118. package/dist/testing/index.d.ts +10 -0
  119. package/dist/testing/middleware-testing.d.ts +138 -0
  120. package/dist/testing/model-binding-testing.d.ts +186 -0
  121. package/dist/testing/performance-testing.d.ts +235 -0
  122. package/dist/testing/test-client.d.ts +117 -0
  123. package/dist/testing/test-request.d.ts +85 -0
  124. package/dist/testing/test-response.d.ts +90 -0
  125. package/dist/testing/types.d.ts +207 -0
  126. package/dist/testing/websocket-testing.d.ts +227 -0
  127. package/dist/types/controller-types.d.ts +208 -0
  128. package/dist/types/core.d.ts +543 -0
  129. package/dist/types/middleware-types.d.ts +227 -0
  130. package/dist/types/request-response-augmentation.d.ts +261 -0
  131. package/dist/types/route-inference.d.ts +168 -0
  132. package/dist/types.d.ts +1794 -0
  133. package/dist/url.d.ts +56 -0
  134. package/dist/utils/index.d.ts +1 -0
  135. package/dist/utils/query-preservation.d.ts +48 -0
  136. package/dist/utils.d.ts +69 -0
  137. package/dist/validation/validator.d.ts +140 -0
  138. package/dist/websocket/clustering.d.ts +185 -0
  139. package/package.json +45 -44
  140. package/src/auth.ts +469 -0
  141. package/src/cache/lru-cache.ts +457 -0
  142. package/src/cache/middleware-memoization.ts +531 -0
  143. package/src/cache/route-cache-warmer.ts +486 -0
  144. package/src/cache/sqlite-cache.ts +783 -0
  145. package/src/cache/streaming-cache.ts +572 -0
  146. package/src/cli/colors.ts +29 -0
  147. package/src/cli/index.ts +287 -0
  148. package/src/cli/middleware.ts +291 -0
  149. package/src/cli/openapi.ts +407 -0
  150. package/src/cli/router.ts +188 -0
  151. package/src/cli/routes.ts +265 -0
  152. package/src/cli/utils.ts +531 -0
  153. package/src/cli.ts +5 -0
  154. package/src/config.ts +322 -0
  155. package/src/container/container.ts +740 -0
  156. package/src/container/contextual-binding.ts +603 -0
  157. package/src/container/decorators.ts +359 -0
  158. package/src/container/service-provider.ts +596 -0
  159. package/src/development/hot-reload.ts +673 -0
  160. package/src/development/index.ts +499 -0
  161. package/src/development/performance-profiler.ts +717 -0
  162. package/src/development/route-debugger.ts +527 -0
  163. package/src/development/route-inspector.ts +749 -0
  164. package/src/development/typescript-utilities.ts +682 -0
  165. package/src/docs.ts +397 -0
  166. package/src/errors/circuit-breaker.ts +732 -0
  167. package/src/errors/error-handler.ts +569 -0
  168. package/src/errors/error-reporting.ts +672 -0
  169. package/src/errors/exceptions.ts +536 -0
  170. package/src/errors/graceful-degradation.ts +621 -0
  171. package/src/errors/index.ts +21 -0
  172. package/src/errors/router-errors.ts +632 -0
  173. package/src/file-serving/static-files.ts +581 -0
  174. package/src/index.ts +14 -0
  175. package/src/middleware/auth.ts +220 -0
  176. package/src/middleware/content_security_policy.ts +215 -0
  177. package/src/middleware/cors.ts +74 -0
  178. package/src/middleware/csrf.ts +108 -0
  179. package/src/middleware/ddos_protection.ts +255 -0
  180. package/src/middleware/file_security.ts +191 -0
  181. package/src/middleware/file_upload.ts +275 -0
  182. package/src/middleware/helmet.ts +268 -0
  183. package/src/middleware/index.ts +117 -0
  184. package/src/middleware/input_validation.ts +449 -0
  185. package/src/middleware/json_body.ts +37 -0
  186. package/src/middleware/performance_alerting.ts +538 -0
  187. package/src/middleware/performance_dashboard.ts +661 -0
  188. package/src/middleware/performance_monitor.ts +943 -0
  189. package/src/middleware/pipeline.ts +489 -0
  190. package/src/middleware/rate_limit.ts +245 -0
  191. package/src/middleware/request_id.ts +36 -0
  192. package/src/middleware/request_signing.ts +636 -0
  193. package/src/middleware/request_tracer.ts +636 -0
  194. package/src/middleware/response_cache.ts +743 -0
  195. package/src/middleware/security.ts +482 -0
  196. package/src/middleware/security_suite.ts +257 -0
  197. package/src/middleware/session.ts +91 -0
  198. package/src/model-binding/index.ts +18 -0
  199. package/src/model-binding/model-middleware.ts +425 -0
  200. package/src/model-binding/model-registry.ts +550 -0
  201. package/src/model-binding.ts +370 -0
  202. package/src/model-resolver-factory.ts +106 -0
  203. package/src/observability/correlation.ts +691 -0
  204. package/src/observability/health-checks.ts +729 -0
  205. package/src/observability/index.ts +184 -0
  206. package/src/observability/integration.ts +548 -0
  207. package/src/observability/metrics.ts +753 -0
  208. package/src/observability/tracing.ts +638 -0
  209. package/src/optimization/bun-utilities.ts +778 -0
  210. package/src/query-builder-integration.ts +137 -0
  211. package/src/request/context.ts +62 -0
  212. package/src/request/enhanced-request.ts +857 -0
  213. package/src/request/macros.ts +688 -0
  214. package/src/response/macros.ts +665 -0
  215. package/src/response/response-factory.ts +596 -0
  216. package/src/router/api-routes.ts +243 -0
  217. package/src/router/file-based-routing.ts +690 -0
  218. package/src/router/file-streaming.ts +383 -0
  219. package/src/router/fluent-router.ts +927 -0
  220. package/src/router/fluent-routing.ts +797 -0
  221. package/src/router/group-organization.ts +213 -0
  222. package/src/router/handler-resolver.ts +291 -0
  223. package/src/router/http-methods.ts +377 -0
  224. package/src/router/index.ts +187 -0
  225. package/src/router/middleware-groups.ts +222 -0
  226. package/src/router/middleware-integration.ts +399 -0
  227. package/src/router/middleware.ts +231 -0
  228. package/src/router/model-binding.ts +215 -0
  229. package/src/router/optimized-route-matching.ts +253 -0
  230. package/src/router/route-building.ts +221 -0
  231. package/src/router/route-compiler.ts +702 -0
  232. package/src/router/route-matching.ts +349 -0
  233. package/src/router/route-trie.ts +464 -0
  234. package/src/router/router.ts +1604 -0
  235. package/src/router/server.ts +462 -0
  236. package/src/router/validation-integration.ts +443 -0
  237. package/src/router/view-rendering.ts +233 -0
  238. package/src/router/websocket.ts +100 -0
  239. package/src/routing/route-caching.ts +402 -0
  240. package/src/routing/route-throttling.ts +469 -0
  241. package/src/routing/subdomain-routing.ts +492 -0
  242. package/src/session/database-store.ts +109 -0
  243. package/src/session/file-store.ts +148 -0
  244. package/src/session/index.ts +244 -0
  245. package/src/session/memory-store.ts +88 -0
  246. package/src/session/redis-store.ts +93 -0
  247. package/src/streaming/index.ts +17 -0
  248. package/src/streaming/sse-handler.ts +482 -0
  249. package/src/streaming/stream-handler.ts +552 -0
  250. package/src/testing/auth-testing.ts +446 -0
  251. package/src/testing/file-upload-testing.ts +543 -0
  252. package/src/testing/index.ts +10 -0
  253. package/src/testing/middleware-testing.ts +322 -0
  254. package/src/testing/model-binding-testing.ts +645 -0
  255. package/src/testing/performance-testing.ts +738 -0
  256. package/src/testing/test-client.ts +310 -0
  257. package/src/testing/test-request.ts +315 -0
  258. package/src/testing/test-response.ts +332 -0
  259. package/src/testing/types.ts +224 -0
  260. package/src/testing/websocket-testing.ts +590 -0
  261. package/src/types/controller-types.ts +385 -0
  262. package/src/types/core.ts +683 -0
  263. package/src/types/middleware-types.ts +419 -0
  264. package/src/types/request-response-augmentation.ts +489 -0
  265. package/src/types/route-inference.ts +357 -0
  266. package/src/types.ts +2067 -0
  267. package/src/url.ts +126 -0
  268. package/src/utils/index.ts +1 -0
  269. package/src/utils/query-preservation.ts +201 -0
  270. package/src/utils.ts +327 -0
  271. package/src/validation/validator.ts +685 -0
  272. package/src/websocket/clustering.ts +762 -0
  273. package/CHANGELOG.md +0 -770
@@ -0,0 +1,702 @@
1
+ import type { HTTPMethod, MatchResult, PatternMatchResult, Route } from '../types'
2
+ import type { CompiledRoute } from './route-trie'
3
+ import { RouteTrie } from './route-trie'
4
+
5
+ /**
6
+ * Route compilation options
7
+ */
8
+ export interface RouteCompilerOptions {
9
+ enableCaching: boolean
10
+ enableMethodGrouping: boolean
11
+ enablePriorityOptimization: boolean
12
+ cacheSize: number
13
+ precompilePatterns: boolean
14
+ }
15
+
16
+ /**
17
+ * Route matching statistics for performance monitoring
18
+ */
19
+ export interface RouteMatchStats {
20
+ totalMatches: number
21
+ cacheHits: number
22
+ cacheMisses: number
23
+ averageMatchTime: number
24
+ trieDepth: number
25
+ methodDistribution: Record<string, number>
26
+ }
27
+
28
+ /**
29
+ * High-performance route compiler with pre-compilation and optimization
30
+ */
31
+ export class RouteCompiler {
32
+ private trie: RouteTrie
33
+ private matchCache: Map<string, MatchResult | null> = new Map()
34
+ private cacheKeys: string[] = [] // For LRU tracking
35
+ private stats: RouteMatchStats = {
36
+ totalMatches: 0,
37
+ cacheHits: 0,
38
+ cacheMisses: 0,
39
+ averageMatchTime: 0,
40
+ trieDepth: 0,
41
+ methodDistribution: {},
42
+ }
43
+
44
+ private options: RouteCompilerOptions
45
+
46
+ constructor(options: Partial<RouteCompilerOptions> = {}) {
47
+ this.options = {
48
+ enableCaching: true,
49
+ enableMethodGrouping: true,
50
+ enablePriorityOptimization: true,
51
+ cacheSize: 1000,
52
+ precompilePatterns: true,
53
+ ...options,
54
+ }
55
+
56
+ this.trie = new RouteTrie()
57
+ }
58
+
59
+ /**
60
+ * Add a route to the compiler
61
+ * @returns true if route was added, false if it's a duplicate
62
+ */
63
+ addRoute(route: Route): boolean {
64
+ // Pre-compile the route pattern if enabled
65
+ if (this.options.precompilePatterns) {
66
+ this.precompileRoutePattern(route)
67
+ }
68
+
69
+ // Check for exact duplicates before adding
70
+ const isDuplicate = this.checkForDuplicateRoute(route)
71
+ if (isDuplicate) {
72
+ return false
73
+ }
74
+
75
+ // Add to trie for fast matching
76
+ this.trie.addRoute(route)
77
+
78
+ // Clear cache when routes change
79
+ if (this.options.enableCaching) {
80
+ this.matchCache.clear()
81
+ }
82
+
83
+ // Update method distribution stats
84
+ const method = route.method
85
+ this.stats.methodDistribution[method] = (this.stats.methodDistribution[method] || 0) + 1
86
+
87
+ return true
88
+ }
89
+
90
+ /**
91
+ * Check if a route is an exact duplicate of an existing route
92
+ */
93
+ private checkForDuplicateRoute(route: Route): boolean {
94
+ const routes = this.trie.getAllRoutes()
95
+
96
+ for (const existingRoute of routes) {
97
+ if (
98
+ existingRoute.route.path === route.path
99
+ && existingRoute.route.method === route.method
100
+ ) {
101
+ return true
102
+ }
103
+ }
104
+
105
+ return false
106
+ }
107
+
108
+ /**
109
+ * Pre-compile route patterns for faster matching
110
+ */
111
+ // Pattern cache for memory optimization
112
+ private patternCache: Map<string, { exec: (url: URL) => PatternMatchResult | null }> = new Map()
113
+
114
+ /**
115
+ * Extract parameter names from a route path
116
+ */
117
+ private extractParamNames(path: string): string[] {
118
+ const paramNames: string[] = []
119
+ const regex = /\{([^}:]+)(?::[^}]+)?\}/g
120
+
121
+ // Using a safe approach to avoid lint error with assignment in while condition
122
+ let tempMatch: RegExpExecArray | null
123
+ // eslint-disable-next-line no-cond-assign
124
+ while (tempMatch = regex.exec(path)) {
125
+ paramNames.push(tempMatch[1].replace('?', ''))
126
+ }
127
+
128
+ return paramNames
129
+ }
130
+
131
+ /**
132
+ * Pre-compile route patterns for faster matching
133
+ */
134
+ private precompileRoutePattern(route: Route): void {
135
+ if (!route.pattern && route.path.includes('{')) {
136
+ // Generate a cache key that includes constraints if present
137
+ let cacheKey = route.path
138
+ if (route.constraints && !Array.isArray(route.constraints)) {
139
+ // Sort constraint keys for consistent cache key generation
140
+ // Type assertion to handle the Record<string, string> case
141
+ const constraintsRecord = route.constraints as Record<string, string>
142
+ const constraintKeys = Object.keys(constraintsRecord).sort()
143
+ const constraintString = constraintKeys
144
+ .map(key => `${key}:${constraintsRecord[key]}`)
145
+ .join('|')
146
+ cacheKey = `${route.path}#${constraintString}`
147
+ }
148
+
149
+ // Check if we already have this pattern cached
150
+ if (this.patternCache.has(cacheKey)) {
151
+ route.pattern = this.patternCache.get(cacheKey)!
152
+ return
153
+ }
154
+
155
+ // Convert Laravel-style parameters to URLPattern with constraints
156
+ const pattern = this.convertToURLPattern(
157
+ route.path,
158
+ route.constraints && !Array.isArray(route.constraints) ? route.constraints : undefined,
159
+ )
160
+
161
+ // Create a URL pattern adapter that matches the expected interface
162
+ const urlPatternAdapter = {
163
+ exec: (url: URL): PatternMatchResult | null => {
164
+ const pathname = url.pathname
165
+ const result = pattern.exec(pathname)
166
+ if (!result)
167
+ return null
168
+
169
+ return {
170
+ pathname: {
171
+ groups: result.groups || {},
172
+ },
173
+ }
174
+ },
175
+ }
176
+
177
+ // Cache the compiled pattern
178
+ this.patternCache.set(cacheKey, urlPatternAdapter)
179
+
180
+ // Assign the pattern to the route
181
+ route.pattern = urlPatternAdapter
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Convert Laravel-style route to a simple regex pattern
187
+ * @param path The route path with Laravel-style parameters
188
+ * @param constraints Optional constraints for route parameters
189
+ */
190
+ private convertToURLPattern(
191
+ path: string,
192
+ constraints?: Record<string, string>,
193
+ ): { exec: (pathname: string) => { groups?: Record<string, string> } | null } {
194
+ // Convert {param} to named capture groups and {param:pattern} to constrained capture groups
195
+ let regexPattern = path.replace(/\{([^}:]+)(?::([^}]+))?\}/g, (_match, name, ..._args) => {
196
+ const pattern = _args[0]
197
+ // If there's a constraint for this parameter, use it instead of the default pattern
198
+ if (constraints && constraints[name]) {
199
+ return `(?<${name}>${constraints[name]})`
200
+ }
201
+ // If there's an inline pattern in the route path, use it
202
+ else if (pattern) {
203
+ return `(?<${name}>${pattern})`
204
+ }
205
+ // Default pattern for parameters without constraints
206
+ return `(?<${name}>[^/]+)`
207
+ })
208
+
209
+ // Escape forward slashes and add anchors
210
+ regexPattern = `^${regexPattern.replace(/\//g, '\\/')}$`
211
+ const regex = new RegExp(regexPattern)
212
+
213
+ return {
214
+ exec: (pathname: string) => {
215
+ const match = pathname.match(regex)
216
+ if (!match)
217
+ return null
218
+
219
+ return {
220
+ groups: match.groups || {},
221
+ }
222
+ },
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Match a request path and method to a route
228
+ */
229
+ match(path: string, method: HTTPMethod): MatchResult | null {
230
+ const startTime = performance.now()
231
+ this.stats.totalMatches++
232
+
233
+ // Check cache first if enabled
234
+ if (this.options.enableCaching) {
235
+ const cacheKey = `${method}:${path}`
236
+ if (this.matchCache.has(cacheKey)) {
237
+ this.stats.cacheHits++
238
+
239
+ // Update LRU order - move this key to the end (most recently used)
240
+ this.updateCacheLRU(cacheKey)
241
+
242
+ const cached = this.matchCache.get(cacheKey)
243
+ this.updateMatchTime(startTime)
244
+ return cached || null
245
+ }
246
+ this.stats.cacheMisses++
247
+ }
248
+
249
+ // Use trie for fast matching
250
+ const trieMatch = this.trie.match(path, method)
251
+ let result: MatchResult | null = null
252
+
253
+ if (trieMatch) {
254
+ result = {
255
+ route: trieMatch.route,
256
+ params: trieMatch.params,
257
+ }
258
+ }
259
+
260
+ // Cache the result if enabled
261
+ if (this.options.enableCaching) {
262
+ const cacheKey = `${method}:${path}`
263
+ this.addToCache(cacheKey, result)
264
+ }
265
+
266
+ this.updateMatchTime(startTime)
267
+ return result
268
+ }
269
+
270
+ /**
271
+ * Add an entry to the cache with LRU eviction if needed
272
+ */
273
+ private addToCache(key: string, value: MatchResult | null): void {
274
+ // If cache is full, evict least recently used item
275
+ if (this.matchCache.size >= this.options.cacheSize && !this.matchCache.has(key)) {
276
+ // Remove least recently used item (first in array)
277
+ if (this.cacheKeys.length > 0) {
278
+ const lruKey = this.cacheKeys.shift()!
279
+ this.matchCache.delete(lruKey)
280
+ }
281
+ }
282
+
283
+ // Add new item to cache
284
+ this.matchCache.set(key, value)
285
+
286
+ // Update LRU tracking
287
+ this.updateCacheLRU(key)
288
+ }
289
+
290
+ /**
291
+ * Update LRU tracking for a cache key
292
+ */
293
+ private updateCacheLRU(key: string): void {
294
+ // Remove key from current position if it exists
295
+ const index = this.cacheKeys.indexOf(key)
296
+ if (index !== -1) {
297
+ this.cacheKeys.splice(index, 1)
298
+ }
299
+
300
+ // Add key to end of array (most recently used)
301
+ this.cacheKeys.push(key)
302
+ }
303
+
304
+ /**
305
+ * Update average match time statistics
306
+ */
307
+ private updateMatchTime(startTime: number): void {
308
+ const matchTime = performance.now() - startTime
309
+ this.stats.averageMatchTime = (
310
+ (this.stats.averageMatchTime * (this.stats.totalMatches - 1) + matchTime)
311
+ / this.stats.totalMatches
312
+ )
313
+ }
314
+
315
+ /**
316
+ * Get all compiled routes sorted by priority
317
+ */
318
+ getCompiledRoutes(): CompiledRoute[] {
319
+ const routes = this.trie.getAllRoutes()
320
+
321
+ if (this.options.enablePriorityOptimization) {
322
+ return routes.sort((a, b) => b.priority - a.priority)
323
+ }
324
+
325
+ return routes
326
+ }
327
+
328
+ /**
329
+ * Get routes grouped by HTTP method
330
+ */
331
+ getRoutesByMethod(): Map<HTTPMethod, CompiledRoute[]> {
332
+ const routesByMethod = new Map<HTTPMethod, CompiledRoute[]>()
333
+
334
+ for (const compiled of this.trie.getAllRoutes()) {
335
+ const method = compiled.route.method as HTTPMethod
336
+ if (!routesByMethod.has(method)) {
337
+ routesByMethod.set(method, [])
338
+ }
339
+ routesByMethod.get(method)!.push(compiled)
340
+ }
341
+
342
+ // Sort each method's routes by priority
343
+ if (this.options.enablePriorityOptimization) {
344
+ for (const [method, routes] of routesByMethod) {
345
+ routes.sort((a, b) => b.priority - a.priority)
346
+ routesByMethod.set(method, routes)
347
+ }
348
+ }
349
+
350
+ return routesByMethod
351
+ }
352
+
353
+ /**
354
+ * Warm up the cache with common routes
355
+ * @param commonPaths Array of paths and methods to pre-cache
356
+ * @param options Optional warming options
357
+ * @param options.force If true, enables caching temporarily even if disabled in compiler options
358
+ * @param options.precompile If true, performs a second match to ensure cache hits are recorded
359
+ * @returns Statistics about the warming operation
360
+ */
361
+ warmCache(
362
+ commonPaths: Array<{ path: string, method: HTTPMethod }>,
363
+ options: { force?: boolean, precompile?: boolean } = {},
364
+ ): { warmedPaths: number, cacheSize: number, hitRate: number } {
365
+ if (!this.options.enableCaching && !options.force) {
366
+ return { warmedPaths: 0, cacheSize: 0, hitRate: 0 }
367
+ }
368
+
369
+ // Enable caching temporarily if force is true
370
+ const originalCachingState = this.options.enableCaching
371
+ if (options.force && !this.options.enableCaching) {
372
+ this.options.enableCaching = true
373
+ }
374
+
375
+ // Clear existing cache entries for these paths to ensure fresh warming
376
+ if (options.precompile) {
377
+ for (const { path, method } of commonPaths) {
378
+ const cacheKey = `${method}:${path}`
379
+ this.matchCache.delete(cacheKey)
380
+ }
381
+ }
382
+
383
+ // Warm the cache by matching each path
384
+ const initialCacheSize = this.matchCache.size
385
+ const initialHits = this.stats.cacheHits
386
+ const initialTotal = this.stats.totalMatches
387
+
388
+ for (const { path, method } of commonPaths) {
389
+ // First match will cache the result
390
+ this.match(path, method)
391
+
392
+ // Second match should be a cache hit
393
+ if (options.precompile) {
394
+ this.match(path, method)
395
+ }
396
+ }
397
+
398
+ // Calculate statistics
399
+ const newCacheEntries = this.matchCache.size - initialCacheSize
400
+ const newHits = this.stats.cacheHits - initialHits
401
+ const newTotal = this.stats.totalMatches - initialTotal
402
+ const hitRate = newTotal > 0 ? newHits / newTotal : 0
403
+
404
+ // Restore original caching state if needed
405
+ if (options.force && !originalCachingState) {
406
+ this.options.enableCaching = originalCachingState
407
+ }
408
+
409
+ return {
410
+ warmedPaths: newCacheEntries,
411
+ cacheSize: this.matchCache.size,
412
+ hitRate,
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Clear all caches and reset statistics
418
+ */
419
+ clear(): void {
420
+ this.trie.clear()
421
+ this.matchCache.clear()
422
+ this.cacheKeys = []
423
+ this.stats = {
424
+ totalMatches: 0,
425
+ cacheHits: 0,
426
+ cacheMisses: 0,
427
+ averageMatchTime: 0,
428
+ trieDepth: 0,
429
+ methodDistribution: {},
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Get performance statistics
435
+ */
436
+ getStats(): RouteMatchStats {
437
+ const trieStats = this.trie.getStats()
438
+ return {
439
+ ...this.stats,
440
+ trieDepth: trieStats.averageDepth,
441
+ methodDistribution: trieStats.methodDistribution,
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Get cache statistics
447
+ */
448
+ getCacheStats(): {
449
+ size: number
450
+ maxSize: number
451
+ hitRate: number
452
+ enabled: boolean
453
+ } {
454
+ const hitRate = this.stats.totalMatches > 0
455
+ ? this.stats.cacheHits / this.stats.totalMatches
456
+ : 0
457
+
458
+ return {
459
+ size: this.matchCache.size,
460
+ maxSize: this.options.cacheSize,
461
+ hitRate,
462
+ enabled: this.options.enableCaching,
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Optimize route order based on usage patterns
468
+ */
469
+ optimizeRouteOrder(_usageStats: Record<string, number>): void {
470
+ if (!this.options.enablePriorityOptimization)
471
+ return
472
+
473
+ // This would reorder routes based on actual usage patterns
474
+ // For now, we'll just clear the cache to force recompilation
475
+ this.matchCache.clear()
476
+ }
477
+
478
+ /**
479
+ * Get route conflicts (overlapping patterns)
480
+ */
481
+ getRouteConflicts(): Array<{
482
+ routes: CompiledRoute[]
483
+ conflictType: 'overlap' | 'duplicate' | 'ambiguous'
484
+ severity: 'low' | 'medium' | 'high'
485
+ }> {
486
+ const conflicts: Array<{
487
+ routes: CompiledRoute[]
488
+ conflictType: 'overlap' | 'duplicate' | 'ambiguous'
489
+ severity: 'low' | 'medium' | 'high'
490
+ }> = []
491
+
492
+ const routes = this.trie.getAllRoutes()
493
+ const routesByMethod = new Map<string, CompiledRoute[]>()
494
+
495
+ // Group routes by method
496
+ for (const route of routes) {
497
+ const method = route.route.method
498
+ if (!routesByMethod.has(method)) {
499
+ routesByMethod.set(method, [])
500
+ }
501
+ routesByMethod.get(method)!.push(route)
502
+ }
503
+
504
+ // Check for conflicts within each method
505
+ for (const [_method, methodRoutes] of routesByMethod) {
506
+ for (let i = 0; i < methodRoutes.length; i++) {
507
+ for (let j = i + 1; j < methodRoutes.length; j++) {
508
+ const route1 = methodRoutes[i]
509
+ const route2 = methodRoutes[j]
510
+
511
+ if (this.routesConflict(route1, route2)) {
512
+ const severity = this.getConflictSeverity(route1, route2)
513
+ const conflictType = this.getConflictType(route1, route2)
514
+
515
+ conflicts.push({
516
+ routes: [route1, route2],
517
+ conflictType,
518
+ severity,
519
+ })
520
+ }
521
+ }
522
+ }
523
+ }
524
+
525
+ return conflicts
526
+ }
527
+
528
+ /**
529
+ * Check if two routes conflict
530
+ */
531
+ private routesConflict(route1: CompiledRoute, route2: CompiledRoute): boolean {
532
+ // Exact path match
533
+ if (route1.route.path === route2.route.path) {
534
+ return true
535
+ }
536
+
537
+ // If routes have different methods, they don't conflict
538
+ if (route1.route.method !== route2.route.method) {
539
+ return false
540
+ }
541
+
542
+ // Check if patterns could match the same paths
543
+ return this.patternsOverlap(route1.segments, route2.segments)
544
+ }
545
+
546
+ /**
547
+ * Check if two route patterns overlap
548
+ */
549
+ private patternsOverlap(segments1: any[], segments2: any[]): boolean {
550
+ // If segment lengths differ and neither has optional parameters or wildcards,
551
+ // they can't overlap
552
+ const hasOptionalOrWildcard1 = segments1.some(seg => seg.optional || seg.type === 'wildcard')
553
+ const hasOptionalOrWildcard2 = segments2.some(seg => seg.optional || seg.type === 'wildcard')
554
+
555
+ if (segments1.length !== segments2.length && !hasOptionalOrWildcard1 && !hasOptionalOrWildcard2) {
556
+ return false
557
+ }
558
+
559
+ const maxLength = Math.max(segments1.length, segments2.length)
560
+ let potentialOverlap = true
561
+
562
+ for (let i = 0; i < maxLength; i++) {
563
+ const seg1 = i < segments1.length ? segments1[i] : null
564
+ const seg2 = i < segments2.length ? segments2[i] : null
565
+
566
+ // If one segment is missing and the other isn't optional, they can't overlap
567
+ if (!seg1 && seg2 && !seg2.optional) {
568
+ return false
569
+ }
570
+ if (!seg2 && seg1 && !seg1.optional) {
571
+ return false
572
+ }
573
+
574
+ // If both segments are missing, continue
575
+ if (!seg1 || !seg2) {
576
+ continue
577
+ }
578
+
579
+ // Static segments must match exactly
580
+ if (seg1.type === 'static' && seg2.type === 'static') {
581
+ if (seg1.value !== seg2.value) {
582
+ return false
583
+ }
584
+ }
585
+ // Check for parameter constraints
586
+ else if (seg1.type === 'parameter' && seg2.type === 'parameter') {
587
+ // If both have patterns/constraints, check if they're mutually exclusive
588
+ if (seg1.pattern && seg2.pattern) {
589
+ // Simple check: if patterns are different, assume they might overlap
590
+ // A more sophisticated check would analyze the regex patterns
591
+ if (seg1.pattern.toString() === seg2.pattern.toString()) {
592
+ potentialOverlap = true
593
+ }
594
+ }
595
+
596
+ // If parameter names are the same, they likely represent the same entity
597
+ // This is a heuristic that helps identify conflicts
598
+ if (seg1.paramName === seg2.paramName) {
599
+ potentialOverlap = true
600
+ }
601
+ }
602
+ // One static, one parameter
603
+ else if (
604
+ (seg1.type === 'static' && seg2.type === 'parameter')
605
+ || (seg1.type === 'parameter' && seg2.type === 'static')
606
+ ) {
607
+ const staticSeg = seg1.type === 'static' ? seg1 : seg2
608
+ const paramSeg = seg1.type === 'parameter' ? seg1 : seg2
609
+
610
+ // If parameter has a pattern, check if static value matches the pattern
611
+ if (paramSeg.pattern) {
612
+ if (!paramSeg.pattern.test(staticSeg.value)) {
613
+ return false
614
+ }
615
+ }
616
+ }
617
+ // Wildcard matches anything
618
+ else if (seg1.type === 'wildcard' || seg2.type === 'wildcard') {
619
+ return true
620
+ }
621
+ }
622
+
623
+ return potentialOverlap
624
+ }
625
+
626
+ /**
627
+ * Get conflict severity
628
+ */
629
+ private getConflictSeverity(route1: CompiledRoute, route2: CompiledRoute): 'low' | 'medium' | 'high' {
630
+ if (route1.route.path === route2.route.path) {
631
+ return 'high' // Exact duplicate
632
+ }
633
+
634
+ if (route1.staticScore === route2.staticScore) {
635
+ return 'medium' // Same specificity
636
+ }
637
+
638
+ return 'low' // Different specificity, priority will resolve
639
+ }
640
+
641
+ /**
642
+ * Get conflict type
643
+ */
644
+ private getConflictType(route1: CompiledRoute, route2: CompiledRoute): 'overlap' | 'duplicate' | 'ambiguous' {
645
+ if (route1.route.path === route2.route.path) {
646
+ return 'duplicate'
647
+ }
648
+
649
+ // Check for ambiguous parameter patterns
650
+ const hasAmbiguousParams = this.hasAmbiguousParameters(route1, route2)
651
+ if (hasAmbiguousParams) {
652
+ return 'ambiguous'
653
+ }
654
+
655
+ // Check for similar priority
656
+ if (Math.abs(route1.priority - route2.priority) < 10) {
657
+ return 'ambiguous'
658
+ }
659
+
660
+ return 'overlap'
661
+ }
662
+
663
+ /**
664
+ * Check if routes have ambiguous parameters
665
+ */
666
+ private hasAmbiguousParameters(route1: CompiledRoute, route2: CompiledRoute): boolean {
667
+ // If routes have different segment counts, they're less likely to be ambiguous
668
+ if (Math.abs(route1.segments.length - route2.segments.length) > 1) {
669
+ return false
670
+ }
671
+
672
+ // Count parameter segments in each route
673
+ const paramCount1 = route1.segments.filter(s => s.type === 'parameter').length
674
+ const paramCount2 = route2.segments.filter(s => s.type === 'parameter').length
675
+
676
+ // If both routes have parameters in the same positions, they might be ambiguous
677
+ if (paramCount1 > 0 && paramCount2 > 0) {
678
+ const minLength = Math.min(route1.segments.length, route2.segments.length)
679
+ let samePositionParams = 0
680
+
681
+ for (let i = 0; i < minLength; i++) {
682
+ if (route1.segments[i].type === 'parameter' && route2.segments[i].type === 'parameter') {
683
+ samePositionParams++
684
+
685
+ // If parameters have different constraints, they might still be distinct
686
+ const hasPattern1 = !!route1.segments[i].pattern
687
+ const hasPattern2 = !!route2.segments[i].pattern
688
+
689
+ if (hasPattern1 !== hasPattern2) {
690
+ return true // One has constraint, one doesn't = ambiguous
691
+ }
692
+ }
693
+ }
694
+
695
+ // If more than half of parameters are in the same positions, likely ambiguous
696
+ return samePositionParams > 0
697
+ && samePositionParams >= Math.min(paramCount1, paramCount2) / 2
698
+ }
699
+
700
+ return false
701
+ }
702
+ }