@stacksjs/bun-router 0.0.12 → 0.0.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 (273) hide show
  1. package/CHANGELOG.md +770 -0
  2. package/README.md +584 -261
  3. package/package.json +43 -48
  4. package/bin/cli.ts +0 -9
  5. package/dist/auth.d.ts +0 -134
  6. package/dist/cache/lru-cache.d.ts +0 -139
  7. package/dist/cache/middleware-memoization.d.ts +0 -151
  8. package/dist/cache/route-cache-warmer.d.ts +0 -161
  9. package/dist/cache/sqlite-cache.d.ts +0 -209
  10. package/dist/cache/streaming-cache.d.ts +0 -132
  11. package/dist/chunk-1mvtv6jw.js +0 -13012
  12. package/dist/cli/colors.d.ts +0 -15
  13. package/dist/cli/index.d.ts +0 -10
  14. package/dist/cli/middleware.d.ts +0 -31
  15. package/dist/cli/openapi.d.ts +0 -17
  16. package/dist/cli/router.d.ts +0 -15
  17. package/dist/cli/routes.d.ts +0 -29
  18. package/dist/cli/utils.d.ts +0 -74
  19. package/dist/cli.d.ts +0 -1
  20. package/dist/cli.js +0 -3195
  21. package/dist/config.d.ts +0 -7
  22. package/dist/container/container.d.ts +0 -273
  23. package/dist/container/contextual-binding.d.ts +0 -240
  24. package/dist/container/decorators.d.ts +0 -141
  25. package/dist/container/service-provider.d.ts +0 -285
  26. package/dist/development/hot-reload.d.ts +0 -187
  27. package/dist/development/index.d.ts +0 -167
  28. package/dist/development/performance-profiler.d.ts +0 -217
  29. package/dist/development/route-debugger.d.ts +0 -154
  30. package/dist/development/route-inspector.d.ts +0 -211
  31. package/dist/development/typescript-utilities.d.ts +0 -209
  32. package/dist/docs.d.ts +0 -11
  33. package/dist/errors/circuit-breaker.d.ts +0 -195
  34. package/dist/errors/error-handler.d.ts +0 -88
  35. package/dist/errors/error-reporting.d.ts +0 -144
  36. package/dist/errors/exceptions.d.ts +0 -184
  37. package/dist/errors/graceful-degradation.d.ts +0 -154
  38. package/dist/errors/index.d.ts +0 -9
  39. package/dist/errors/router-errors.d.ts +0 -110
  40. package/dist/file-serving/static-files.d.ts +0 -143
  41. package/dist/index.d.ts +0 -14
  42. package/dist/index.js +0 -13933
  43. package/dist/middleware/auth.d.ts +0 -63
  44. package/dist/middleware/content_security_policy.d.ts +0 -53
  45. package/dist/middleware/cors.d.ts +0 -5
  46. package/dist/middleware/csrf.d.ts +0 -7
  47. package/dist/middleware/ddos_protection.d.ts +0 -39
  48. package/dist/middleware/file_security.d.ts +0 -26
  49. package/dist/middleware/file_upload.d.ts +0 -38
  50. package/dist/middleware/helmet.d.ts +0 -54
  51. package/dist/middleware/index.d.ts +0 -44
  52. package/dist/middleware/input_validation.d.ts +0 -45
  53. package/dist/middleware/json_body.d.ts +0 -4
  54. package/dist/middleware/performance_alerting.d.ts +0 -87
  55. package/dist/middleware/performance_dashboard.d.ts +0 -87
  56. package/dist/middleware/performance_monitor.d.ts +0 -209
  57. package/dist/middleware/pipeline.d.ts +0 -131
  58. package/dist/middleware/rate_limit.d.ts +0 -36
  59. package/dist/middleware/request_id.d.ts +0 -4
  60. package/dist/middleware/request_signing.d.ts +0 -153
  61. package/dist/middleware/request_tracer.d.ts +0 -72
  62. package/dist/middleware/response_cache.d.ts +0 -97
  63. package/dist/middleware/security.d.ts +0 -69
  64. package/dist/middleware/security_suite.d.ts +0 -46
  65. package/dist/middleware/session.d.ts +0 -23
  66. package/dist/model-binding/index.d.ts +0 -2
  67. package/dist/model-binding/model-middleware.d.ts +0 -118
  68. package/dist/model-binding/model-registry.d.ts +0 -164
  69. package/dist/model-binding.d.ts +0 -185
  70. package/dist/model-resolver-factory.d.ts +0 -31
  71. package/dist/observability/correlation.d.ts +0 -187
  72. package/dist/observability/health-checks.d.ts +0 -186
  73. package/dist/observability/index.d.ts +0 -60
  74. package/dist/observability/integration.d.ts +0 -147
  75. package/dist/observability/metrics.d.ts +0 -184
  76. package/dist/observability/tracing.d.ts +0 -187
  77. package/dist/optimization/bun-utilities.d.ts +0 -224
  78. package/dist/query-builder-integration.d.ts +0 -36
  79. package/dist/request/context.d.ts +0 -34
  80. package/dist/request/enhanced-request.d.ts +0 -213
  81. package/dist/request/macros.d.ts +0 -300
  82. package/dist/response/macros.d.ts +0 -245
  83. package/dist/response/response-factory.d.ts +0 -133
  84. package/dist/router/api-routes.d.ts +0 -5
  85. package/dist/router/file-based-routing.d.ts +0 -118
  86. package/dist/router/file-streaming.d.ts +0 -5
  87. package/dist/router/fluent-router.d.ts +0 -315
  88. package/dist/router/fluent-routing.d.ts +0 -271
  89. package/dist/router/group-organization.d.ts +0 -5
  90. package/dist/router/handler-resolver.d.ts +0 -24
  91. package/dist/router/http-methods.d.ts +0 -10
  92. package/dist/router/index.d.ts +0 -114
  93. package/dist/router/middleware-groups.d.ts +0 -94
  94. package/dist/router/middleware-integration.d.ts +0 -112
  95. package/dist/router/middleware.d.ts +0 -5
  96. package/dist/router/model-binding.d.ts +0 -5
  97. package/dist/router/optimized-route-matching.d.ts +0 -6
  98. package/dist/router/route-building.d.ts +0 -5
  99. package/dist/router/route-compiler.d.ts +0 -153
  100. package/dist/router/route-matching.d.ts +0 -5
  101. package/dist/router/route-trie.d.ts +0 -114
  102. package/dist/router/router.d.ts +0 -294
  103. package/dist/router/server.d.ts +0 -5
  104. package/dist/router/validation-integration.d.ts +0 -170
  105. package/dist/router/view-rendering.d.ts +0 -5
  106. package/dist/router/websocket.d.ts +0 -5
  107. package/dist/routing/route-caching.d.ts +0 -129
  108. package/dist/routing/route-throttling.d.ts +0 -150
  109. package/dist/routing/subdomain-routing.d.ts +0 -206
  110. package/dist/session/database-store.d.ts +0 -51
  111. package/dist/session/file-store.d.ts +0 -22
  112. package/dist/session/index.d.ts +0 -106
  113. package/dist/session/memory-store.d.ts +0 -21
  114. package/dist/session/redis-store.d.ts +0 -31
  115. package/dist/streaming/index.d.ts +0 -3
  116. package/dist/streaming/sse-handler.d.ts +0 -138
  117. package/dist/streaming/stream-handler.d.ts +0 -114
  118. package/dist/testing/auth-testing.d.ts +0 -156
  119. package/dist/testing/file-upload-testing.d.ts +0 -190
  120. package/dist/testing/index.d.ts +0 -10
  121. package/dist/testing/middleware-testing.d.ts +0 -138
  122. package/dist/testing/model-binding-testing.d.ts +0 -186
  123. package/dist/testing/performance-testing.d.ts +0 -235
  124. package/dist/testing/test-client.d.ts +0 -117
  125. package/dist/testing/test-request.d.ts +0 -85
  126. package/dist/testing/test-response.d.ts +0 -90
  127. package/dist/testing/types.d.ts +0 -207
  128. package/dist/testing/websocket-testing.d.ts +0 -227
  129. package/dist/types/controller-types.d.ts +0 -208
  130. package/dist/types/core.d.ts +0 -543
  131. package/dist/types/middleware-types.d.ts +0 -227
  132. package/dist/types/request-response-augmentation.d.ts +0 -261
  133. package/dist/types/route-inference.d.ts +0 -168
  134. package/dist/types.d.ts +0 -1782
  135. package/dist/url.d.ts +0 -56
  136. package/dist/utils/index.d.ts +0 -1
  137. package/dist/utils/query-preservation.d.ts +0 -48
  138. package/dist/utils.d.ts +0 -69
  139. package/dist/validation/validator.d.ts +0 -140
  140. package/dist/websocket/clustering.d.ts +0 -185
  141. package/src/auth.ts +0 -469
  142. package/src/cache/lru-cache.ts +0 -457
  143. package/src/cache/middleware-memoization.ts +0 -531
  144. package/src/cache/route-cache-warmer.ts +0 -486
  145. package/src/cache/sqlite-cache.ts +0 -783
  146. package/src/cache/streaming-cache.ts +0 -572
  147. package/src/cli/colors.ts +0 -29
  148. package/src/cli/index.ts +0 -287
  149. package/src/cli/middleware.ts +0 -291
  150. package/src/cli/openapi.ts +0 -407
  151. package/src/cli/router.ts +0 -188
  152. package/src/cli/routes.ts +0 -265
  153. package/src/cli/utils.ts +0 -531
  154. package/src/cli.ts +0 -5
  155. package/src/config.ts +0 -322
  156. package/src/container/container.ts +0 -740
  157. package/src/container/contextual-binding.ts +0 -603
  158. package/src/container/decorators.ts +0 -359
  159. package/src/container/service-provider.ts +0 -596
  160. package/src/development/hot-reload.ts +0 -673
  161. package/src/development/index.ts +0 -499
  162. package/src/development/performance-profiler.ts +0 -717
  163. package/src/development/route-debugger.ts +0 -527
  164. package/src/development/route-inspector.ts +0 -749
  165. package/src/development/typescript-utilities.ts +0 -682
  166. package/src/docs.ts +0 -397
  167. package/src/errors/circuit-breaker.ts +0 -732
  168. package/src/errors/error-handler.ts +0 -569
  169. package/src/errors/error-reporting.ts +0 -672
  170. package/src/errors/exceptions.ts +0 -536
  171. package/src/errors/graceful-degradation.ts +0 -621
  172. package/src/errors/index.ts +0 -21
  173. package/src/errors/router-errors.ts +0 -632
  174. package/src/file-serving/static-files.ts +0 -581
  175. package/src/index.ts +0 -14
  176. package/src/middleware/auth.ts +0 -219
  177. package/src/middleware/content_security_policy.ts +0 -215
  178. package/src/middleware/cors.ts +0 -74
  179. package/src/middleware/csrf.ts +0 -108
  180. package/src/middleware/ddos_protection.ts +0 -255
  181. package/src/middleware/file_security.ts +0 -191
  182. package/src/middleware/file_upload.ts +0 -264
  183. package/src/middleware/helmet.ts +0 -268
  184. package/src/middleware/index.ts +0 -117
  185. package/src/middleware/input_validation.ts +0 -449
  186. package/src/middleware/json_body.ts +0 -37
  187. package/src/middleware/performance_alerting.ts +0 -538
  188. package/src/middleware/performance_dashboard.ts +0 -661
  189. package/src/middleware/performance_monitor.ts +0 -943
  190. package/src/middleware/pipeline.ts +0 -489
  191. package/src/middleware/rate_limit.ts +0 -245
  192. package/src/middleware/request_id.ts +0 -36
  193. package/src/middleware/request_signing.ts +0 -636
  194. package/src/middleware/request_tracer.ts +0 -636
  195. package/src/middleware/response_cache.ts +0 -743
  196. package/src/middleware/security.ts +0 -482
  197. package/src/middleware/security_suite.ts +0 -257
  198. package/src/middleware/session.ts +0 -91
  199. package/src/model-binding/index.ts +0 -18
  200. package/src/model-binding/model-middleware.ts +0 -425
  201. package/src/model-binding/model-registry.ts +0 -550
  202. package/src/model-binding.ts +0 -370
  203. package/src/model-resolver-factory.ts +0 -106
  204. package/src/observability/correlation.ts +0 -691
  205. package/src/observability/health-checks.ts +0 -729
  206. package/src/observability/index.ts +0 -184
  207. package/src/observability/integration.ts +0 -548
  208. package/src/observability/metrics.ts +0 -753
  209. package/src/observability/tracing.ts +0 -638
  210. package/src/optimization/bun-utilities.ts +0 -778
  211. package/src/query-builder-integration.ts +0 -137
  212. package/src/request/context.ts +0 -62
  213. package/src/request/enhanced-request.ts +0 -857
  214. package/src/request/macros.ts +0 -688
  215. package/src/response/macros.ts +0 -612
  216. package/src/response/response-factory.ts +0 -596
  217. package/src/router/api-routes.ts +0 -243
  218. package/src/router/file-based-routing.ts +0 -670
  219. package/src/router/file-streaming.ts +0 -372
  220. package/src/router/fluent-router.ts +0 -927
  221. package/src/router/fluent-routing.ts +0 -797
  222. package/src/router/group-organization.ts +0 -213
  223. package/src/router/handler-resolver.ts +0 -291
  224. package/src/router/http-methods.ts +0 -377
  225. package/src/router/index.ts +0 -187
  226. package/src/router/middleware-groups.ts +0 -222
  227. package/src/router/middleware-integration.ts +0 -399
  228. package/src/router/middleware.ts +0 -231
  229. package/src/router/model-binding.ts +0 -215
  230. package/src/router/optimized-route-matching.ts +0 -253
  231. package/src/router/route-building.ts +0 -212
  232. package/src/router/route-compiler.ts +0 -702
  233. package/src/router/route-matching.ts +0 -349
  234. package/src/router/route-trie.ts +0 -464
  235. package/src/router/router.ts +0 -1581
  236. package/src/router/server.ts +0 -462
  237. package/src/router/validation-integration.ts +0 -443
  238. package/src/router/view-rendering.ts +0 -233
  239. package/src/router/websocket.ts +0 -100
  240. package/src/routing/route-caching.ts +0 -402
  241. package/src/routing/route-throttling.ts +0 -469
  242. package/src/routing/subdomain-routing.ts +0 -492
  243. package/src/session/database-store.ts +0 -109
  244. package/src/session/file-store.ts +0 -148
  245. package/src/session/index.ts +0 -244
  246. package/src/session/memory-store.ts +0 -88
  247. package/src/session/redis-store.ts +0 -93
  248. package/src/streaming/index.ts +0 -17
  249. package/src/streaming/sse-handler.ts +0 -482
  250. package/src/streaming/stream-handler.ts +0 -552
  251. package/src/testing/auth-testing.ts +0 -446
  252. package/src/testing/file-upload-testing.ts +0 -543
  253. package/src/testing/index.ts +0 -10
  254. package/src/testing/middleware-testing.ts +0 -322
  255. package/src/testing/model-binding-testing.ts +0 -645
  256. package/src/testing/performance-testing.ts +0 -738
  257. package/src/testing/test-client.ts +0 -310
  258. package/src/testing/test-request.ts +0 -315
  259. package/src/testing/test-response.ts +0 -332
  260. package/src/testing/types.ts +0 -224
  261. package/src/testing/websocket-testing.ts +0 -590
  262. package/src/types/controller-types.ts +0 -385
  263. package/src/types/core.ts +0 -683
  264. package/src/types/middleware-types.ts +0 -419
  265. package/src/types/request-response-augmentation.ts +0 -489
  266. package/src/types/route-inference.ts +0 -357
  267. package/src/types.ts +0 -2055
  268. package/src/url.ts +0 -126
  269. package/src/utils/index.ts +0 -1
  270. package/src/utils/query-preservation.ts +0 -201
  271. package/src/utils.ts +0 -327
  272. package/src/validation/validator.ts +0 -685
  273. package/src/websocket/clustering.ts +0 -762
@@ -1,670 +0,0 @@
1
- import type { ActionHandler, EnhancedRequest, QueryPreservationConfig } from '../types'
2
- import type { Router } from './router'
3
- import { join, relative, resolve, basename, dirname } from 'node:path'
4
- import { readdirSync, statSync, existsSync } from 'node:fs'
5
- import { injectQueryPreservationScript } from '../utils/query-preservation'
6
-
7
- /**
8
- * File-based routing configuration
9
- */
10
- export interface FileBasedRoutingConfig {
11
- /**
12
- * Whether automatic file-based routing is enabled
13
- * Default: true (auto-enabled when views directory is detected)
14
- */
15
- enabled?: boolean
16
-
17
- /**
18
- * Directory containing view files for automatic routing
19
- * Default: auto-detected from 'src/views', 'views', 'resources/views'
20
- */
21
- viewsPath?: string
22
-
23
- /**
24
- * File extensions to treat as routable pages
25
- * Default: ['.stx', '.html']
26
- */
27
- extensions?: string[]
28
-
29
- /**
30
- * Files/directories to exclude from routing
31
- * Default: ['_', 'components', 'layouts', 'partials', 'scripts', 'styles']
32
- */
33
- exclude?: string[]
34
-
35
- /**
36
- * Directory containing component files for STX rendering
37
- * Default: join(viewsPath, 'components')
38
- */
39
- componentsDir?: string
40
-
41
- /**
42
- * Directory containing layout files for STX rendering
43
- * Default: join(viewsPath, 'layouts')
44
- */
45
- layoutsDir?: string
46
-
47
- /**
48
- * Directory containing partial files for STX rendering
49
- * Default: join(viewsPath, 'partials')
50
- */
51
- partialsDir?: string
52
-
53
- /**
54
- * Custom render function for view files
55
- * If not provided, attempts to use STX renderer or serves raw content
56
- */
57
- render?: (filePath: string, data: Record<string, unknown>, request: EnhancedRequest) => Promise<Response>
58
- }
59
-
60
- interface DiscoveredRoute {
61
- filePath: string
62
- routePath: string
63
- params: string[]
64
- isIndex: boolean
65
- isDynamic: boolean
66
- }
67
-
68
- /**
69
- * Default directories to scan for views (in order of priority)
70
- */
71
- const DEFAULT_VIEW_DIRECTORIES: string[] = [
72
- 'src/views',
73
- 'views',
74
- 'resources/views',
75
- 'app/views',
76
- ]
77
-
78
- /**
79
- * Default exclusion patterns for non-routable files/directories
80
- */
81
- const DEFAULT_EXCLUDES: string[] = [
82
- '_', // Underscore-prefixed files are private
83
- 'components', // Component partials
84
- 'layouts', // Layout templates
85
- 'partials', // Partial templates
86
- 'scripts', // Script files
87
- 'styles', // Style files
88
- ]
89
-
90
- /**
91
- * Default routable file extensions
92
- */
93
- const DEFAULT_EXTENSIONS: string[] = ['.stx', '.html']
94
-
95
- /**
96
- * Auto-detect the views directory from common conventions
97
- */
98
- function detectViewsDirectory(cwd: string = process.cwd()): string | null {
99
- for (const dir of DEFAULT_VIEW_DIRECTORIES) {
100
- const fullPath = resolve(cwd, dir)
101
- if (existsSync(fullPath) && statSync(fullPath).isDirectory()) {
102
- return fullPath
103
- }
104
- }
105
- return null
106
- }
107
-
108
- /**
109
- * Check if a directory contains any routable files
110
- */
111
- function hasRoutableFiles(dir: string, extensions: string[], exclude: string[]): boolean {
112
- if (!existsSync(dir)) return false
113
-
114
- try {
115
- const entries = readdirSync(dir, { withFileTypes: true })
116
- for (const entry of entries) {
117
- if (exclude.some(pattern => entry.name.startsWith(pattern) || entry.name === pattern)) {
118
- continue
119
- }
120
-
121
- if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
122
- return true
123
- }
124
-
125
- if (entry.isDirectory()) {
126
- const subDir = join(dir, entry.name)
127
- if (hasRoutableFiles(subDir, extensions, exclude)) {
128
- return true
129
- }
130
- }
131
- }
132
- }
133
- catch {
134
- // Directory not readable
135
- }
136
-
137
- return false
138
- }
139
-
140
- /**
141
- * Converts a file path to a route path
142
- * Examples:
143
- * views/index.stx -> /
144
- * views/about.stx -> /about
145
- * views/dashboard/index.stx -> /dashboard
146
- * views/dashboard/errors.stx -> /dashboard/errors
147
- * views/users/[id].stx -> /users/{id}
148
- * views/posts/[...slug].stx -> /posts/{slug}*
149
- */
150
- function filePathToRoutePath(filePath: string, viewsDir: string, extensions: string[]): { routePath: string; params: string[] } {
151
- // Get relative path from views directory
152
- let routePath = relative(viewsDir, filePath)
153
-
154
- // Remove file extension
155
- for (const ext of extensions) {
156
- if (routePath.endsWith(ext)) {
157
- routePath = routePath.slice(0, -ext.length)
158
- break
159
- }
160
- }
161
-
162
- // Convert directory separators to forward slashes
163
- routePath = routePath.replace(/\\/g, '/')
164
-
165
- // Handle index files
166
- if (routePath === 'index' || routePath.endsWith('/index')) {
167
- routePath = routePath.replace(/\/?index$/, '')
168
- }
169
-
170
- // Extract dynamic parameters
171
- const params: string[] = []
172
-
173
- // Convert [...param] to {param}* (catch-all) syntax
174
- routePath = routePath.replace(/\[\.\.\.([^\]]+)\]/g, (_match, param) => {
175
- params.push(param)
176
- return `{${param}}*`
177
- })
178
-
179
- // Convert [param] to {param} syntax
180
- routePath = routePath.replace(/\[([^\]]+)\]/g, (_match, param) => {
181
- params.push(param)
182
- return `{${param}}`
183
- })
184
-
185
- // Ensure leading slash
186
- if (!routePath.startsWith('/')) {
187
- routePath = `/${routePath}`
188
- }
189
-
190
- // Handle root path
191
- if (routePath === '/') {
192
- return { routePath: '/', params }
193
- }
194
-
195
- return { routePath, params }
196
- }
197
-
198
- /**
199
- * Recursively discovers all routable files in a directory
200
- */
201
- function discoverRoutes(
202
- dir: string,
203
- viewsDir: string,
204
- extensions: string[],
205
- exclude: string[],
206
- ): DiscoveredRoute[] {
207
- const routes: DiscoveredRoute[] = []
208
-
209
- if (!existsSync(dir)) {
210
- return routes
211
- }
212
-
213
- const entries = readdirSync(dir, { withFileTypes: true })
214
-
215
- for (const entry of entries) {
216
- const fullPath = join(dir, entry.name)
217
-
218
- // Skip excluded files/directories
219
- if (exclude.some(pattern => entry.name.startsWith(pattern) || entry.name === pattern)) {
220
- continue
221
- }
222
-
223
- if (entry.isDirectory()) {
224
- // Recursively discover routes in subdirectories
225
- routes.push(...discoverRoutes(fullPath, viewsDir, extensions, exclude))
226
- }
227
- else if (entry.isFile()) {
228
- // Check if file has a routable extension
229
- const hasRoutableExtension = extensions.some(ext => entry.name.endsWith(ext))
230
-
231
- if (hasRoutableExtension) {
232
- const { routePath, params } = filePathToRoutePath(fullPath, viewsDir, extensions)
233
-
234
- routes.push({
235
- filePath: fullPath,
236
- routePath,
237
- params,
238
- isIndex: entry.name.startsWith('index.'),
239
- isDynamic: params.length > 0,
240
- })
241
- }
242
- }
243
- }
244
-
245
- // Sort routes: static routes before dynamic, more specific before less specific
246
- routes.sort((a, b) => {
247
- // Static routes come first
248
- if (a.isDynamic !== b.isDynamic) {
249
- return a.isDynamic ? 1 : -1
250
- }
251
-
252
- // More segments = more specific
253
- const aSegments = a.routePath.split('/').length
254
- const bSegments = b.routePath.split('/').length
255
- if (aSegments !== bSegments) {
256
- return bSegments - aSegments
257
- }
258
-
259
- // Alphabetical for consistency
260
- return a.routePath.localeCompare(b.routePath)
261
- })
262
-
263
- // Check for duplicate routes and warn
264
- const seen = new Map<string, DiscoveredRoute>()
265
- for (const route of routes) {
266
- const existing = seen.get(route.routePath)
267
- if (existing) {
268
- console.warn(
269
- `[bun-router] Warning: Duplicate route "${route.routePath}" found:\n` +
270
- ` - ${existing.filePath}\n` +
271
- ` - ${route.filePath}\n` +
272
- ` Using: ${existing.filePath}`
273
- )
274
- }
275
- else {
276
- seen.set(route.routePath, route)
277
- }
278
- }
279
-
280
- return Array.from(seen.values())
281
- }
282
-
283
- /**
284
- * Check for pre-built HTML version of a view file
285
- * Looks in dist/views/ for production builds
286
- */
287
- function findPrebuiltView(stxFilePath: string, viewsDir: string): string | null {
288
- // Get relative path from views directory
289
- const relativePath = relative(viewsDir, stxFilePath)
290
-
291
- // Check common pre-built locations
292
- const prebuiltLocations = [
293
- resolve(process.cwd(), 'dist/views', relativePath.replace(/\.stx$/, '.html')),
294
- resolve(process.cwd(), 'views', relativePath.replace(/\.stx$/, '.html')),
295
- resolve('/var/task/views', relativePath.replace(/\.stx$/, '.html')), // Lambda
296
- ]
297
-
298
- for (const location of prebuiltLocations) {
299
- if (existsSync(location)) {
300
- return location
301
- }
302
- }
303
-
304
- return null
305
- }
306
-
307
- /**
308
- * Render an STX file using the @stacksjs/stx library
309
- */
310
- async function renderStxFile(
311
- filePath: string,
312
- viewsDir: string,
313
- data: Record<string, unknown>,
314
- routingConfig?: FileBasedRoutingConfig,
315
- ): Promise<string> {
316
- // Dynamic import to avoid build-time resolution
317
- const stxModule = '@stacksjs/stx'
318
- const stx = await import(/* @vite-ignore */ stxModule)
319
-
320
- if (!stx.processDirectives || !stx.extractVariables) {
321
- throw new Error('STX library not properly loaded. Install with: bun add @stacksjs/stx')
322
- }
323
-
324
- const content = await Bun.file(filePath).text()
325
-
326
- // Extract script content and template
327
- const scriptMatch = content.match(/<script\s+server\s*>([\s\S]*?)<\/script>/i)
328
- const scriptContent = scriptMatch ? scriptMatch[1] : ''
329
- let templateContent = scriptMatch
330
- ? content.replace(/<script\s+server\s*>[\s\S]*?<\/script>/i, '')
331
- : content
332
-
333
- // Replace <script client> with regular <script>
334
- templateContent = templateContent.replace(/<script\s+client\s*>/gi, '<script>')
335
-
336
- // Build context with data
337
- const context: Record<string, unknown> = {
338
- __filename: filePath,
339
- __dirname: dirname(filePath),
340
- props: data,
341
- ...data,
342
- }
343
-
344
- // Extract variables from server script
345
- if (scriptContent) {
346
- await stx.extractVariables(scriptContent, context, filePath)
347
- }
348
-
349
- // Resolve componentsDir: user config > viewsDir/components fallback
350
- const resolvedComponentsDir = routingConfig?.componentsDir
351
- ? resolve(routingConfig.componentsDir)
352
- : join(viewsDir, 'components')
353
-
354
- const resolvedLayoutsDir = routingConfig?.layoutsDir
355
- ? resolve(routingConfig.layoutsDir)
356
- : join(viewsDir, 'layouts')
357
-
358
- const resolvedPartialsDir = routingConfig?.partialsDir
359
- ? resolve(routingConfig.partialsDir)
360
- : join(viewsDir, 'partials')
361
-
362
- // Configure STX with proper paths
363
- const config = {
364
- ...stx.defaultConfig,
365
- componentsDir: resolvedComponentsDir,
366
- layoutsDir: resolvedLayoutsDir,
367
- partialsDir: resolvedPartialsDir,
368
- }
369
-
370
- return stx.processDirectives(templateContent, context, filePath, config, new Set())
371
- }
372
-
373
- /**
374
- * Creates a handler for rendering a view file
375
- */
376
- function createViewHandler(
377
- filePath: string,
378
- viewsDir: string,
379
- config: FileBasedRoutingConfig,
380
- queryPreservationConfig?: QueryPreservationConfig,
381
- ): ActionHandler {
382
- return async (req: EnhancedRequest): Promise<Response> => {
383
- const url = new URL(req.url)
384
-
385
- // Build request data for template context
386
- const data = {
387
- params: req.params || {},
388
- query: Object.fromEntries(url.searchParams),
389
- url: req.url,
390
- path: url.pathname,
391
- method: req.method,
392
- headers: Object.fromEntries(req.headers.entries()),
393
- }
394
-
395
- /**
396
- * Apply query preservation script to HTML if configured
397
- */
398
- function applyQueryPreservation(html: string): string {
399
- if (queryPreservationConfig?.enabled !== false && queryPreservationConfig?.preserve?.length) {
400
- return injectQueryPreservationScript(html, queryPreservationConfig)
401
- }
402
- return html
403
- }
404
-
405
- try {
406
- // 1. Check for pre-built HTML (production optimization)
407
- const prebuiltPath = findPrebuiltView(filePath, viewsDir)
408
- if (prebuiltPath) {
409
- let html = await Bun.file(prebuiltPath).text()
410
- html = applyQueryPreservation(html)
411
- return new Response(html, {
412
- status: 200,
413
- headers: { 'Content-Type': 'text/html; charset=utf-8' },
414
- })
415
- }
416
-
417
- // 2. Render STX file
418
- if (filePath.endsWith('.stx')) {
419
- let html = await renderStxFile(filePath, viewsDir, data, config)
420
- html = applyQueryPreservation(html)
421
- return new Response(html, {
422
- status: 200,
423
- headers: { 'Content-Type': 'text/html; charset=utf-8' },
424
- })
425
- }
426
-
427
- // 3. Serve raw HTML
428
- let content = await Bun.file(filePath).text()
429
- content = applyQueryPreservation(content)
430
- return new Response(content, {
431
- status: 200,
432
- headers: { 'Content-Type': 'text/html; charset=utf-8' },
433
- })
434
- }
435
- catch (error) {
436
- console.error(`[bun-router] Error rendering ${filePath}:`, error)
437
- return new Response('Internal Server Error', { status: 500 })
438
- }
439
- }
440
- }
441
-
442
- /**
443
- * Registers file-based routing extension on the Router class
444
- */
445
- export function registerFileBasedRouting(RouterClass: typeof Router): void {
446
- Object.defineProperties(RouterClass.prototype, {
447
- /**
448
- * Internal: detected views directory
449
- */
450
- _viewsDir: {
451
- value: null as string | null,
452
- writable: true,
453
- configurable: true,
454
- },
455
-
456
- /**
457
- * Internal: file-based routing config
458
- */
459
- _fileRoutingConfig: {
460
- value: null as FileBasedRoutingConfig | null,
461
- writable: true,
462
- configurable: true,
463
- },
464
-
465
- /**
466
- * Internal: discovered file-based routes
467
- */
468
- _fileBasedRoutes: {
469
- value: [] as DiscoveredRoute[],
470
- writable: true,
471
- configurable: true,
472
- },
473
-
474
- /**
475
- * Internal: whether file routes have been initialized
476
- */
477
- _fileRoutesInitialized: {
478
- value: false,
479
- writable: true,
480
- configurable: true,
481
- },
482
-
483
- /**
484
- * Initialize automatic file-based routing
485
- * Called automatically when serve() is invoked
486
- */
487
- _initFileRoutes: {
488
- async value(): Promise<void> {
489
- if (this._fileRoutesInitialized) return
490
-
491
- // Auto-detect views directory if not configured
492
- const viewsDir = this._viewsDir || detectViewsDirectory()
493
- if (!viewsDir) {
494
- this._fileRoutesInitialized = true
495
- return
496
- }
497
-
498
- const config = this._fileRoutingConfig || {}
499
- const extensions = config.extensions || DEFAULT_EXTENSIONS
500
- const exclude = config.exclude || DEFAULT_EXCLUDES
501
-
502
- // Check if there are any routable files
503
- if (!hasRoutableFiles(viewsDir, extensions, exclude)) {
504
- this._fileRoutesInitialized = true
505
- return
506
- }
507
-
508
- // Discover and register routes
509
- const routes = discoverRoutes(viewsDir, viewsDir, extensions, exclude)
510
-
511
- if (this.config.verbose) {
512
- console.log(`[bun-router] Auto-discovered ${routes.length} file-based routes from ${viewsDir}:`)
513
- for (const route of routes) {
514
- console.log(` ${route.routePath} -> ${relative(viewsDir, route.filePath)}`)
515
- }
516
- }
517
-
518
- // Store viewsDir for use in handlers
519
- this._viewsDir = viewsDir
520
-
521
- // Register each discovered route
522
- for (const route of routes) {
523
- const handler = createViewHandler(route.filePath, viewsDir, config, this.config.queryPreservation)
524
-
525
- // Only register if no explicit route exists for this path
526
- const existingRoute = this.routes.find(
527
- (r: { path: string; method: string }) => r.path === route.routePath && r.method === 'GET'
528
- )
529
-
530
- if (!existingRoute) {
531
- this.get(route.routePath, handler, 'web')
532
- this._fileBasedRoutes.push(route)
533
- }
534
- }
535
-
536
- this._fileRoutesInitialized = true
537
- },
538
- writable: true,
539
- configurable: true,
540
- },
541
-
542
- /**
543
- * Configure file-based routing
544
- * Call this before serve() to customize behavior
545
- */
546
- views: {
547
- value(config: FileBasedRoutingConfig | string): Router {
548
- if (typeof config === 'string') {
549
- // Just a path
550
- this._viewsDir = resolve(config)
551
- this._fileRoutingConfig = {}
552
- }
553
- else {
554
- // Full config
555
- if (config.viewsPath) {
556
- this._viewsDir = resolve(config.viewsPath)
557
- }
558
- this._fileRoutingConfig = config
559
- }
560
- return this
561
- },
562
- writable: true,
563
- configurable: true,
564
- },
565
-
566
- /**
567
- * Explicitly disable file-based routing
568
- */
569
- disableFileRouting: {
570
- value(): Router {
571
- this._fileRoutingConfig = { enabled: false }
572
- return this
573
- },
574
- writable: true,
575
- configurable: true,
576
- },
577
-
578
- /**
579
- * Get list of discovered file-based routes
580
- */
581
- getFileRoutes: {
582
- value(): DiscoveredRoute[] {
583
- return this._fileBasedRoutes || []
584
- },
585
- writable: true,
586
- configurable: true,
587
- },
588
-
589
- /**
590
- * Legacy method for manual file routes loading
591
- * Kept for backwards compatibility
592
- */
593
- fileRoutes: {
594
- async value(viewsPath?: string, options?: Omit<FileBasedRoutingConfig, 'viewsPath'>): Promise<Router> {
595
- if (viewsPath) {
596
- this._viewsDir = resolve(viewsPath)
597
- }
598
- if (options) {
599
- this._fileRoutingConfig = { ...this._fileRoutingConfig, ...options }
600
- }
601
- // Force re-initialization
602
- this._fileRoutesInitialized = false
603
- await this._initFileRoutes()
604
- return this
605
- },
606
- writable: true,
607
- configurable: true,
608
- },
609
-
610
- /**
611
- * Legacy method alias
612
- */
613
- loadFileRoutes: {
614
- async value(config: FileBasedRoutingConfig = {}): Promise<Router> {
615
- return this.fileRoutes(config.viewsPath, config)
616
- },
617
- writable: true,
618
- configurable: true,
619
- },
620
- })
621
- }
622
-
623
- // Type augmentation for Router
624
- declare module './router' {
625
- interface Router {
626
- _viewsDir?: string | null
627
- _fileRoutingConfig?: FileBasedRoutingConfig | null
628
- _fileBasedRoutes?: DiscoveredRoute[]
629
- _fileRoutesInitialized?: boolean
630
-
631
- /**
632
- * Internal: Initialize file-based routing (called by serve())
633
- */
634
- _initFileRoutes(): Promise<void>
635
-
636
- /**
637
- * Configure file-based routing
638
- * @param config - Path to views directory or full configuration object
639
- * @example
640
- * router.views('src/views')
641
- * router.views({ viewsPath: 'src/views', extensions: ['.stx'] })
642
- */
643
- views(config: FileBasedRoutingConfig | string): Router
644
-
645
- /**
646
- * Disable automatic file-based routing
647
- */
648
- disableFileRouting(): Router
649
-
650
- /**
651
- * Get the list of discovered file-based routes
652
- */
653
- getFileRoutes(): DiscoveredRoute[]
654
-
655
- /**
656
- * Manually trigger file-based route discovery
657
- * @param viewsPath - Optional path to views directory
658
- * @param options - Additional configuration options
659
- */
660
- fileRoutes(viewsPath?: string, options?: Omit<FileBasedRoutingConfig, 'viewsPath'>): Promise<Router>
661
-
662
- /**
663
- * Legacy: Load file-based routes with configuration
664
- */
665
- loadFileRoutes(config?: FileBasedRoutingConfig): Promise<Router>
666
- }
667
- }
668
-
669
- export type { DiscoveredRoute }
670
- export { detectViewsDirectory, discoverRoutes, DEFAULT_VIEW_DIRECTORIES, DEFAULT_EXCLUDES, DEFAULT_EXTENSIONS }