@stacksjs/bun-router 0.0.17 → 0.0.18

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 (146) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/container/service-provider.d.ts +3 -3
  3. package/dist/development/performance-profiler.d.ts +3 -3
  4. package/dist/development/route-debugger.d.ts +1 -1
  5. package/dist/development/route-inspector.d.ts +2 -2
  6. package/dist/observability/integration.d.ts +1 -1
  7. package/dist/response/macros.d.ts +1 -1
  8. package/dist/router/index.d.ts +7 -7
  9. package/dist/router/router.d.ts +0 -32
  10. package/dist/testing/performance-testing.d.ts +2 -2
  11. package/dist/testing/test-client.d.ts +1 -1
  12. package/dist/testing/websocket-testing.d.ts +1 -1
  13. package/package.json +2 -3
  14. package/src/auth.ts +0 -489
  15. package/src/cache/lru-cache.ts +0 -457
  16. package/src/cache/middleware-memoization.ts +0 -531
  17. package/src/cache/route-cache-warmer.ts +0 -486
  18. package/src/cache/sqlite-cache.ts +0 -783
  19. package/src/cache/streaming-cache.ts +0 -572
  20. package/src/cli/colors.ts +0 -29
  21. package/src/cli/index.ts +0 -287
  22. package/src/cli/middleware.ts +0 -291
  23. package/src/cli/openapi.ts +0 -407
  24. package/src/cli/router.ts +0 -188
  25. package/src/cli/routes.ts +0 -265
  26. package/src/cli/utils.ts +0 -531
  27. package/src/cli.ts +0 -5
  28. package/src/config.ts +0 -322
  29. package/src/container/container.ts +0 -786
  30. package/src/container/contextual-binding.ts +0 -606
  31. package/src/container/decorators.ts +0 -368
  32. package/src/container/index.ts +0 -49
  33. package/src/container/service-provider.ts +0 -596
  34. package/src/development/hot-reload.ts +0 -673
  35. package/src/development/index.ts +0 -499
  36. package/src/development/performance-profiler.ts +0 -717
  37. package/src/development/route-debugger.ts +0 -527
  38. package/src/development/route-inspector.ts +0 -749
  39. package/src/development/typescript-utilities.ts +0 -682
  40. package/src/docs.ts +0 -397
  41. package/src/errors/circuit-breaker.ts +0 -733
  42. package/src/errors/error-handler.ts +0 -569
  43. package/src/errors/error-reporting.ts +0 -672
  44. package/src/errors/exceptions.ts +0 -536
  45. package/src/errors/graceful-degradation.ts +0 -623
  46. package/src/errors/index.ts +0 -21
  47. package/src/errors/router-errors.ts +0 -632
  48. package/src/file-serving/static-files.ts +0 -718
  49. package/src/index.ts +0 -66
  50. package/src/middleware/auth.ts +0 -220
  51. package/src/middleware/content_security_policy.ts +0 -215
  52. package/src/middleware/cors.ts +0 -142
  53. package/src/middleware/csrf.ts +0 -166
  54. package/src/middleware/ddos_protection.ts +0 -279
  55. package/src/middleware/file_security.ts +0 -191
  56. package/src/middleware/file_upload.ts +0 -275
  57. package/src/middleware/helmet.ts +0 -268
  58. package/src/middleware/index.ts +0 -117
  59. package/src/middleware/input_validation.ts +0 -453
  60. package/src/middleware/json_body.ts +0 -37
  61. package/src/middleware/performance_alerting.ts +0 -540
  62. package/src/middleware/performance_dashboard.ts +0 -661
  63. package/src/middleware/performance_monitor.ts +0 -945
  64. package/src/middleware/pipeline.ts +0 -489
  65. package/src/middleware/rate_limit.ts +0 -251
  66. package/src/middleware/request_id.ts +0 -36
  67. package/src/middleware/request_signing.ts +0 -638
  68. package/src/middleware/request_tracer.ts +0 -653
  69. package/src/middleware/response_cache.ts +0 -778
  70. package/src/middleware/security.ts +0 -482
  71. package/src/middleware/security_suite.ts +0 -257
  72. package/src/middleware/session.ts +0 -91
  73. package/src/model-binding/index.ts +0 -18
  74. package/src/model-binding/model-middleware.ts +0 -425
  75. package/src/model-binding/model-registry.ts +0 -550
  76. package/src/model-binding.ts +0 -370
  77. package/src/model-resolver-factory.ts +0 -106
  78. package/src/observability/correlation.ts +0 -691
  79. package/src/observability/health-checks.ts +0 -729
  80. package/src/observability/index.ts +0 -184
  81. package/src/observability/integration.ts +0 -548
  82. package/src/observability/metrics.ts +0 -753
  83. package/src/observability/tracing.ts +0 -638
  84. package/src/optimization/bun-utilities.ts +0 -778
  85. package/src/query-builder-integration.ts +0 -137
  86. package/src/request/context.ts +0 -109
  87. package/src/request/enhanced-request.ts +0 -876
  88. package/src/request/macros.ts +0 -822
  89. package/src/response/macros.ts +0 -665
  90. package/src/response/response-factory.ts +0 -596
  91. package/src/router/api-routes.ts +0 -243
  92. package/src/router/file-based-routing.ts +0 -697
  93. package/src/router/file-streaming.ts +0 -383
  94. package/src/router/fluent-routing.ts +0 -866
  95. package/src/router/group-organization.ts +0 -229
  96. package/src/router/handler-resolver.ts +0 -327
  97. package/src/router/http-methods.ts +0 -407
  98. package/src/router/index.ts +0 -193
  99. package/src/router/middleware-groups.ts +0 -222
  100. package/src/router/middleware-integration.ts +0 -399
  101. package/src/router/middleware.ts +0 -234
  102. package/src/router/model-binding.ts +0 -215
  103. package/src/router/optimized-route-matching.ts +0 -302
  104. package/src/router/route-building.ts +0 -221
  105. package/src/router/route-compiler.ts +0 -691
  106. package/src/router/route-matching.ts +0 -368
  107. package/src/router/route-trie.ts +0 -450
  108. package/src/router/router.ts +0 -1616
  109. package/src/router/server.ts +0 -684
  110. package/src/router/validation-integration.ts +0 -449
  111. package/src/router/view-rendering.ts +0 -233
  112. package/src/router/websocket.ts +0 -100
  113. package/src/routing/route-caching.ts +0 -402
  114. package/src/routing/route-throttling.ts +0 -469
  115. package/src/routing/subdomain-routing.ts +0 -492
  116. package/src/session/database-store.ts +0 -109
  117. package/src/session/file-store.ts +0 -148
  118. package/src/session/index.ts +0 -244
  119. package/src/session/memory-store.ts +0 -88
  120. package/src/session/redis-store.ts +0 -93
  121. package/src/streaming/index.ts +0 -17
  122. package/src/streaming/sse-handler.ts +0 -482
  123. package/src/streaming/stream-handler.ts +0 -552
  124. package/src/testing/auth-testing.ts +0 -448
  125. package/src/testing/file-upload-testing.ts +0 -543
  126. package/src/testing/index.ts +0 -10
  127. package/src/testing/middleware-testing.ts +0 -322
  128. package/src/testing/model-binding-testing.ts +0 -645
  129. package/src/testing/performance-testing.ts +0 -738
  130. package/src/testing/test-client.ts +0 -310
  131. package/src/testing/test-request.ts +0 -315
  132. package/src/testing/test-response.ts +0 -332
  133. package/src/testing/types.ts +0 -224
  134. package/src/testing/websocket-testing.ts +0 -590
  135. package/src/types/controller-types.ts +0 -385
  136. package/src/types/core.ts +0 -683
  137. package/src/types/middleware-types.ts +0 -422
  138. package/src/types/request-response-augmentation.ts +0 -489
  139. package/src/types/route-inference.ts +0 -372
  140. package/src/types.ts +0 -2123
  141. package/src/url.ts +0 -131
  142. package/src/utils/index.ts +0 -1
  143. package/src/utils/query-preservation.ts +0 -201
  144. package/src/utils.ts +0 -395
  145. package/src/validation/validator.ts +0 -688
  146. package/src/websocket/clustering.ts +0 -769
@@ -1,866 +0,0 @@
1
- import type { BunQueryBuilderModel } from '../model-binding'
2
- import type { RouteCacheConfig } from '../routing/route-caching'
3
- import type { ThrottleConfig } from '../routing/route-throttling'
4
- import type { EnhancedRequest, MiddlewareHandler, NextFunction, RouteHandler, ThrottlePattern } from '../types'
5
- import { createModelBindingMiddleware } from '../model-binding'
6
- import { createRouteCacheMiddleware, RouteCacheFactory } from '../routing/route-caching'
7
- import { createRateLimitMiddleware, parseThrottleString, ThrottleFactory } from '../routing/route-throttling'
8
- import { DomainGroup, DomainMatcher, SubdomainRouter } from '../routing/subdomain-routing'
9
- import { getNamedRoutePath, registerNamedRoute } from '../url'
10
- import { matchPath } from '../utils'
11
-
12
- /**
13
- * A route registered on a {@link FluentRouter}
14
- */
15
- export interface RegisteredFluentRoute {
16
- method: string
17
- path: string
18
- handler: RouteHandler
19
- middleware: MiddlewareHandler[]
20
- name?: string
21
- }
22
-
23
- /**
24
- * Laravel-style resource controller shape accepted by
25
- * {@link FluentRouter.resource}. Every action is optional — only the
26
- * actions present (and allowed by `only`/`except`) are registered.
27
- */
28
- export interface FluentResourceController {
29
- index?: RouteHandler
30
- create?: RouteHandler
31
- store?: RouteHandler
32
- show?: RouteHandler
33
- edit?: RouteHandler
34
- update?: RouteHandler
35
- destroy?: RouteHandler
36
- }
37
-
38
- /**
39
- * Fluent route builder with chainable API
40
- */
41
- export class FluentRouteBuilder {
42
- private middleware: MiddlewareHandler[] = []
43
- private cacheConfig?: RouteCacheConfig
44
- private throttleConfig?: ThrottleConfig
45
- private routeName?: string
46
- private modelBindings: Record<string, BunQueryBuilderModel> = {}
47
-
48
- constructor(
49
- private method: string,
50
- private path: string,
51
- private handler: RouteHandler,
52
- ) {}
53
-
54
- /**
55
- * Add middleware to route
56
- */
57
- addMiddleware(middleware: MiddlewareHandler | MiddlewareHandler[]): this {
58
- if (Array.isArray(middleware)) {
59
- this.middleware.push(...middleware)
60
- }
61
- else {
62
- this.middleware.push(middleware)
63
- }
64
- return this
65
- }
66
-
67
- /**
68
- * Add route caching with tags
69
- */
70
- cached(tags: string[] = [], config?: Partial<RouteCacheConfig>): this {
71
- this.cacheConfig = {
72
- ...RouteCacheFactory.api(tags),
73
- ...config,
74
- }
75
- return this
76
- }
77
-
78
- /**
79
- * Add route throttling with narrow type checking
80
- */
81
- throttle(limit: ThrottlePattern | ThrottleConfig, _name?: string): this {
82
- if (typeof limit === 'string') {
83
- const parsed = parseThrottleString(limit)
84
- this.throttleConfig = {
85
- maxAttempts: parsed.maxAttempts ?? 60,
86
- windowMs: parsed.windowMs,
87
- keyGenerator: (req: EnhancedRequest) => req.headers.get('x-forwarded-for') || 'anonymous',
88
- }
89
- }
90
- else {
91
- this.throttleConfig = limit
92
- }
93
- return this
94
- }
95
-
96
- /**
97
- * Set route name
98
- */
99
- name(name: string): this {
100
- this.routeName = name
101
- return this
102
- }
103
-
104
- /**
105
- * Bind models to route parameters
106
- */
107
- model(parameter: string, model: BunQueryBuilderModel): this {
108
- this.modelBindings[parameter] = model
109
- return this
110
- }
111
-
112
- /**
113
- * Build the final route configuration
114
- */
115
- build(): {
116
- method: string
117
- path: string
118
- handler: RouteHandler
119
- middleware: MiddlewareHandler[]
120
- name?: string
121
- } {
122
- const finalMiddleware: MiddlewareHandler[] = []
123
-
124
- // Add model binding middleware
125
- if (Object.keys(this.modelBindings).length > 0) {
126
- const parameters = Object.keys(this.modelBindings).map(name => ({ name }))
127
- const modelBindingMiddleware = createModelBindingMiddleware(parameters, this.modelBindings)
128
- // The model-binding middleware's `next` is `() => Promise<Response>`;
129
- // the fluent executor always provides one, so the adapter is safe
130
- finalMiddleware.push(modelBindingMiddleware as unknown as MiddlewareHandler)
131
- }
132
-
133
- // Add throttling middleware
134
- if (this.throttleConfig) {
135
- const throttleMiddleware = createRateLimitMiddleware(this.throttleConfig)
136
- finalMiddleware.push(throttleMiddleware as MiddlewareHandler)
137
- }
138
-
139
- // Add caching middleware
140
- if (this.cacheConfig) {
141
- const cacheMiddleware = createRouteCacheMiddleware(this.cacheConfig)
142
- finalMiddleware.push(cacheMiddleware as MiddlewareHandler)
143
- }
144
-
145
- // Add custom middleware
146
- finalMiddleware.push(...this.middleware)
147
-
148
- return {
149
- method: this.method,
150
- path: this.path,
151
- handler: this.handler,
152
- middleware: finalMiddleware,
153
- name: this.routeName,
154
- }
155
- }
156
- }
157
-
158
- /**
159
- * Middleware condition function type
160
- */
161
- export type MiddlewareCondition = (_request: EnhancedRequest) => boolean | Promise<boolean>
162
-
163
- /**
164
- * Conditional middleware wrapper
165
- */
166
- export interface ConditionalMiddleware {
167
- condition: MiddlewareCondition
168
- middleware: MiddlewareHandler[]
169
- }
170
-
171
- /**
172
- * Builder for conditional middleware
173
- */
174
- export class FluentConditionalBuilder {
175
- constructor(
176
- private router: FluentRouter,
177
- private condition: MiddlewareCondition,
178
- ) {}
179
-
180
- middleware(middlewareName: string): this {
181
- const [name, params] = middlewareName.split(':')
182
- const middlewareFactory = this.router.resolveNamedMiddleware(name)
183
-
184
- if (!middlewareFactory) {
185
- throw new Error(`Unknown middleware: ${name}`)
186
- }
187
-
188
- this.router.addConditionalMiddleware({
189
- condition: this.condition,
190
- middleware: [middlewareFactory(params)],
191
- })
192
-
193
- return this
194
- }
195
- }
196
-
197
- /**
198
- * Builder for middleware with parameters
199
- */
200
- export class FluentMiddlewareBuilder {
201
- constructor(
202
- private router: FluentRouter,
203
- private middleware: MiddlewareHandler[],
204
- ) {}
205
-
206
- get(path: string, handler: RouteHandler): FluentRouteBuilder {
207
- return new FluentRouteBuilder('GET', path, handler).addMiddleware(this.middleware)
208
- }
209
-
210
- post(path: string, handler: RouteHandler): FluentRouteBuilder {
211
- return new FluentRouteBuilder('POST', path, handler).addMiddleware(this.middleware)
212
- }
213
-
214
- put(path: string, handler: RouteHandler): FluentRouteBuilder {
215
- return new FluentRouteBuilder('PUT', path, handler).addMiddleware(this.middleware)
216
- }
217
-
218
- delete(path: string, handler: RouteHandler): FluentRouteBuilder {
219
- return new FluentRouteBuilder('DELETE', path, handler).addMiddleware(this.middleware)
220
- }
221
-
222
- patch(path: string, handler: RouteHandler): FluentRouteBuilder {
223
- return new FluentRouteBuilder('PATCH', path, handler).addMiddleware(this.middleware)
224
- }
225
- }
226
-
227
- /**
228
- * Builder for route groups with middleware
229
- */
230
- export class FluentRouteGroupBuilder {
231
- constructor(
232
- private router: FluentRouter,
233
- private groupMiddleware: MiddlewareHandler[],
234
- ) {}
235
-
236
- get(path: string, handler: RouteHandler): FluentRouteBuilder {
237
- return new FluentRouteBuilder('GET', path, handler).addMiddleware(this.groupMiddleware)
238
- }
239
-
240
- post(path: string, handler: RouteHandler): FluentRouteBuilder {
241
- return new FluentRouteBuilder('POST', path, handler).addMiddleware(this.groupMiddleware)
242
- }
243
-
244
- put(path: string, handler: RouteHandler): FluentRouteBuilder {
245
- return new FluentRouteBuilder('PUT', path, handler).addMiddleware(this.groupMiddleware)
246
- }
247
-
248
- delete(path: string, handler: RouteHandler): FluentRouteBuilder {
249
- return new FluentRouteBuilder('DELETE', path, handler).addMiddleware(this.groupMiddleware)
250
- }
251
-
252
- patch(path: string, handler: RouteHandler): FluentRouteBuilder {
253
- return new FluentRouteBuilder('PATCH', path, handler).addMiddleware(this.groupMiddleware)
254
- }
255
- }
256
-
257
- /**
258
- * Fluent router with chainable API and advanced features
259
- */
260
- export class FluentRouter {
261
- private routes: RegisteredFluentRoute[] = []
262
-
263
- private globalMiddleware: MiddlewareHandler[] = []
264
- private subdomainRouter = new SubdomainRouter()
265
- private routeGroups: Array<{
266
- prefix?: string
267
- middleware?: MiddlewareHandler[]
268
- name?: string
269
- domain?: string
270
- }> = []
271
-
272
- // Middleware groups registry
273
- private middlewareGroups = new Map<string, MiddlewareHandler[]>()
274
-
275
- // Named middleware registry with parameter support
276
- private namedMiddleware = new Map<string, (params?: string) => MiddlewareHandler>()
277
-
278
- // Conditional middleware
279
- private conditionalMiddleware: ConditionalMiddleware[] = []
280
-
281
- constructor() {
282
- this.setupDefaultMiddleware()
283
- }
284
-
285
- /**
286
- * Setup default named middleware with parameter support
287
- */
288
- private setupDefaultMiddleware(): void {
289
- // Throttle middleware with parameters
290
- this.namedMiddleware.set('throttle', (params?: string) => {
291
- const parsed = params ? parseThrottleString(params as ThrottlePattern) : { maxAttempts: 60, windowMs: 60000 }
292
- const handler = createRateLimitMiddleware({
293
- maxAttempts: parsed.maxAttempts,
294
- windowMs: parsed.windowMs,
295
- keyGenerator: (req: EnhancedRequest) => req.headers.get('x-forwarded-for') || 'anonymous',
296
- })
297
-
298
- const adapted: MiddlewareHandler = async (req: EnhancedRequest, next: NextFunction) => {
299
- const adaptedNext = async () => {
300
- const res = await next()
301
- return res ?? new Response(null)
302
- }
303
- return await handler(req, adaptedNext)
304
- }
305
-
306
- return adapted
307
- })
308
-
309
- // Auth middleware
310
- this.namedMiddleware.set('auth', () => {
311
- return async (req: EnhancedRequest, next: NextFunction) => {
312
- const token = req.headers.get('Authorization')?.replace('Bearer ', '')
313
- if (!token) {
314
- return new Response('Unauthorized', { status: 401 })
315
- }
316
- // Mock JWT verification
317
- ;(req as any).user = { id: '123', roles: ['user'] }
318
- return await next()
319
- }
320
- })
321
-
322
- // CORS middleware
323
- this.namedMiddleware.set('cors', () => {
324
- return async (_req: EnhancedRequest, next: NextFunction) => {
325
- const response = await next()
326
- if (response) {
327
- const headers = new Headers(response.headers)
328
- headers.set('Access-Control-Allow-Origin', '*')
329
- headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
330
- headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization')
331
- return new Response(response.body, {
332
- status: response.status,
333
- statusText: response.statusText,
334
- headers,
335
- })
336
- }
337
- return response
338
- }
339
- })
340
- }
341
-
342
- /**
343
- * Register a middleware group
344
- */
345
- middlewareGroup(name: string, middlewareNames: string[]): this {
346
- const middlewareHandlers: MiddlewareHandler[] = []
347
-
348
- for (const middlewareName of middlewareNames) {
349
- const [name, params] = middlewareName.split(':')
350
- const middlewareFactory = this.namedMiddleware.get(name)
351
- if (middlewareFactory) {
352
- middlewareHandlers.push(middlewareFactory(params))
353
- }
354
- }
355
-
356
- this.middlewareGroups.set(name, middlewareHandlers)
357
- return this
358
- }
359
-
360
- /**
361
- * Apply middleware group to routes
362
- */
363
- middlewareGroupRoutes(name: string): FluentRouteGroupBuilder {
364
- const middlewareHandlers = this.middlewareGroups.get(name) || []
365
- return new FluentRouteGroupBuilder(this, middlewareHandlers)
366
- }
367
-
368
- /**
369
- * Conditional middleware execution
370
- */
371
- when(condition: MiddlewareCondition): FluentConditionalBuilder {
372
- return new FluentConditionalBuilder(this, condition)
373
- }
374
-
375
- /**
376
- * Look up a named middleware factory.
377
- * @internal Used by the fluent builders — avoids `as any` reach-ins.
378
- */
379
- resolveNamedMiddleware(name: string): ((params?: string) => MiddlewareHandler) | undefined {
380
- return this.namedMiddleware.get(name)
381
- }
382
-
383
- /**
384
- * Register conditional middleware, evaluated per request in {@link handle}.
385
- * @internal Used by the fluent builders.
386
- */
387
- addConditionalMiddleware(conditional: ConditionalMiddleware): void {
388
- this.conditionalMiddleware.push(conditional)
389
- }
390
-
391
- /**
392
- * Apply middleware with parameters
393
- */
394
- middleware(middlewareName: string): FluentMiddlewareBuilder {
395
- const [name, params] = middlewareName.split(':')
396
- const middlewareFactory = this.namedMiddleware.get(name)
397
-
398
- if (!middlewareFactory) {
399
- throw new Error(`Unknown middleware: ${name}`)
400
- }
401
-
402
- const middlewareHandler = middlewareFactory(params)
403
- return new FluentMiddlewareBuilder(this, [middlewareHandler])
404
- }
405
-
406
- /**
407
- * Add global middleware
408
- */
409
- use(middleware: MiddlewareHandler): this {
410
- this.globalMiddleware.push(middleware)
411
- return this
412
- }
413
-
414
- /**
415
- * Create a GET route
416
- */
417
- get(path: string, handler: RouteHandler): FluentRouteBuilder {
418
- return new FluentRouteBuilder('GET', path, handler)
419
- }
420
-
421
- /**
422
- * Create a POST route
423
- */
424
- post(path: string, handler: RouteHandler): FluentRouteBuilder {
425
- return new FluentRouteBuilder('POST', path, handler)
426
- }
427
-
428
- /**
429
- * Create a PUT route
430
- */
431
- put(path: string, handler: RouteHandler): FluentRouteBuilder {
432
- return new FluentRouteBuilder('PUT', path, handler)
433
- }
434
-
435
- /**
436
- * Create a DELETE route
437
- */
438
- delete(path: string, handler: RouteHandler): FluentRouteBuilder {
439
- return new FluentRouteBuilder('DELETE', path, handler)
440
- }
441
-
442
- /**
443
- * Create a PATCH route
444
- */
445
- patch(path: string, handler: RouteHandler): FluentRouteBuilder {
446
- return new FluentRouteBuilder('PATCH', path, handler)
447
- }
448
-
449
- /**
450
- * Register a built route
451
- */
452
- register(builder: FluentRouteBuilder): this {
453
- const route = builder.build()
454
- this.routes.push(route)
455
- if (route.name)
456
- registerNamedRoute(route.name, route.path)
457
- return this
458
- }
459
-
460
- /**
461
- * Create a cached route
462
- */
463
- cached(tags: string[] = [], config?: Partial<RouteCacheConfig>): {
464
- get: (path: string, handler: RouteHandler) => FluentRouteBuilder
465
- post: (path: string, handler: RouteHandler) => FluentRouteBuilder
466
- put: (path: string, handler: RouteHandler) => FluentRouteBuilder
467
- delete: (path: string, handler: RouteHandler) => FluentRouteBuilder
468
- patch: (path: string, handler: RouteHandler) => FluentRouteBuilder
469
- } {
470
- return {
471
- get: (path: string, handler: RouteHandler) =>
472
- this.get(path, handler).cached(tags, config),
473
- post: (path: string, handler: RouteHandler) =>
474
- this.post(path, handler).cached(tags, config),
475
- put: (path: string, handler: RouteHandler) =>
476
- this.put(path, handler).cached(tags, config),
477
- delete: (path: string, handler: RouteHandler) =>
478
- this.delete(path, handler).cached(tags, config),
479
- patch: (path: string, handler: RouteHandler) =>
480
- this.patch(path, handler).cached(tags, config),
481
- }
482
- }
483
-
484
- /**
485
- * Create a throttled route
486
- */
487
- throttled(limit: ThrottlePattern | ThrottleConfig): {
488
- get: (path: string, handler: RouteHandler) => FluentRouteBuilder
489
- post: (path: string, handler: RouteHandler) => FluentRouteBuilder
490
- put: (path: string, handler: RouteHandler) => FluentRouteBuilder
491
- delete: (path: string, handler: RouteHandler) => FluentRouteBuilder
492
- patch: (path: string, handler: RouteHandler) => FluentRouteBuilder
493
- } {
494
- return {
495
- get: (path: string, handler: RouteHandler) =>
496
- this.get(path, handler).throttle(limit),
497
- post: (path: string, handler: RouteHandler) =>
498
- this.post(path, handler).throttle(limit),
499
- put: (path: string, handler: RouteHandler) =>
500
- this.put(path, handler).throttle(limit),
501
- delete: (path: string, handler: RouteHandler) =>
502
- this.delete(path, handler).throttle(limit),
503
- patch: (path: string, handler: RouteHandler) =>
504
- this.patch(path, handler).throttle(limit),
505
- }
506
- }
507
-
508
- /**
509
- * Create route group with shared attributes
510
- */
511
- group(
512
- config: {
513
- prefix?: string
514
- middleware?: MiddlewareHandler[]
515
- name?: string
516
- domain?: string
517
- },
518
- callback: (router: FluentRouter) => void,
519
- ): this {
520
- const groupRouter = new FluentRouter()
521
- callback(groupRouter)
522
-
523
- for (const route of groupRouter.routes) {
524
- const groupedRoute = {
525
- ...route,
526
- path: config.prefix ? `${config.prefix}${route.path}` : route.path,
527
- middleware: [
528
- ...(config.middleware || []),
529
- ...route.middleware,
530
- ],
531
- name: config.name && route.name ? `${config.name}.${route.name}` : route.name,
532
- }
533
-
534
- if (config.domain) {
535
- const domainPattern = DomainMatcher.parseDomainPattern(config.domain)
536
- const domainGroup = new DomainGroup(domainPattern, { domain: config.domain })
537
- domainGroup.addRoute(
538
- groupedRoute.method,
539
- groupedRoute.path,
540
- groupedRoute.handler,
541
- groupedRoute.middleware,
542
- groupedRoute.name,
543
- )
544
- this.subdomainRouter.addDomainGroup(domainGroup)
545
- }
546
- else {
547
- this.routes.push(groupedRoute)
548
- if (groupedRoute.name)
549
- registerNamedRoute(groupedRoute.name, groupedRoute.path)
550
- }
551
- }
552
-
553
- return this
554
- }
555
-
556
- /**
557
- * Create subdomain routing group
558
- */
559
- domain(pattern: string): {
560
- routes: (callback: (router: FluentRouter) => void) => FluentRouter
561
- } {
562
- return {
563
- routes: (callback: (router: FluentRouter) => void): FluentRouter => {
564
- const domainRouter = new FluentRouter()
565
- callback(domainRouter)
566
-
567
- const domainPattern = DomainMatcher.parseDomainPattern(pattern)
568
- const domainGroup = new DomainGroup(domainPattern, { domain: pattern })
569
- for (const route of domainRouter.routes) {
570
- domainGroup.addRoute(
571
- route.method,
572
- route.path,
573
- route.handler,
574
- route.middleware,
575
- route.name,
576
- )
577
- }
578
- this.subdomainRouter.addDomainGroup(domainGroup)
579
-
580
- return this
581
- },
582
- }
583
- }
584
-
585
- /**
586
- * Create resource routes (Laravel-style)
587
- */
588
- resource(name: string, controller: FluentResourceController, options: {
589
- only?: string[]
590
- except?: string[]
591
- model?: BunQueryBuilderModel
592
- } = {}): this {
593
- const actions = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy']
594
- const allowedActions = options.only || actions.filter(action => !options.except?.includes(action))
595
-
596
- const routes = [
597
- { action: 'index', method: 'GET', path: `/${name}`, handler: controller.index },
598
- { action: 'create', method: 'GET', path: `/${name}/create`, handler: controller.create },
599
- { action: 'store', method: 'POST', path: `/${name}`, handler: controller.store },
600
- { action: 'show', method: 'GET', path: `/${name}/{${name.slice(0, -1)}}`, handler: controller.show },
601
- { action: 'edit', method: 'GET', path: `/${name}/{${name.slice(0, -1)}}/edit`, handler: controller.edit },
602
- { action: 'update', method: 'PUT', path: `/${name}/{${name.slice(0, -1)}}`, handler: controller.update },
603
- { action: 'destroy', method: 'DELETE', path: `/${name}/{${name.slice(0, -1)}}`, handler: controller.destroy },
604
- ]
605
-
606
- for (const route of routes) {
607
- if (allowedActions.includes(route.action) && route.handler) {
608
- const builder = new FluentRouteBuilder(route.method, route.path, route.handler)
609
- .name(`${name}.${route.action}`)
610
-
611
- if (options.model && ['show', 'edit', 'update', 'destroy'].includes(route.action)) {
612
- builder.model(name.slice(0, -1), options.model)
613
- }
614
-
615
- this.register(builder)
616
- }
617
- }
618
-
619
- return this
620
- }
621
-
622
- /**
623
- * Get all registered routes
624
- */
625
- getRoutes(): RegisteredFluentRoute[] {
626
- return [...this.routes, ...this.subdomainRouter.getAllRoutes()]
627
- }
628
-
629
- /**
630
- * Handle incoming request
631
- */
632
- async handle(request: EnhancedRequest): Promise<Response | null> {
633
- const url = new URL(request.url)
634
-
635
- // Try subdomain routing first
636
- const domain = url.hostname
637
- const match = this.subdomainRouter.findDomainGroup(domain)
638
- if (match) {
639
- // Add domain parameters to request
640
- const enhancedReq = request as EnhancedRequest & { domainParams?: Record<string, string> }
641
- enhancedReq.domainParams = match.parameters
642
-
643
- // Find matching route in domain group
644
- for (const route of match.group.getRoutes()) {
645
- const params = this.matchRouteParams(route, request.method, url.pathname)
646
- if (params) {
647
- return await this.executeRoute(route, enhancedReq, params)
648
- }
649
- }
650
- }
651
-
652
- // Find matching route
653
- for (const route of this.routes) {
654
- const params = this.matchRouteParams(route, request.method, url.pathname)
655
- if (params) {
656
- return await this.executeRoute(route, request, params)
657
- }
658
- }
659
-
660
- return null
661
- }
662
-
663
- /**
664
- * Match a route against the request, extracting path parameters.
665
- *
666
- * Delegates to the same `matchPath` used by the main `Router`, so the
667
- * fluent API has identical semantics for `{param}`, optional `{param?}`,
668
- * and wildcard segments (the previous ad-hoc regex neither escaped
669
- * static text nor extracted params at all).
670
- *
671
- * @returns the extracted params, or `null` when the route doesn't match
672
- */
673
- private matchRouteParams(
674
- route: RegisteredFluentRoute,
675
- method: string,
676
- pathname: string,
677
- ): Record<string, string> | null {
678
- if (route.method !== method) {
679
- return null
680
- }
681
-
682
- const params: Record<string, string> = {}
683
- return matchPath(route.path, pathname, params) ? params : null
684
- }
685
-
686
- /**
687
- * Execute route with middleware (global → conditional → route-specific)
688
- */
689
- private async executeRoute(
690
- route: RegisteredFluentRoute,
691
- request: EnhancedRequest,
692
- params: Record<string, string>,
693
- ): Promise<Response> {
694
- // Expose extracted path parameters to middleware and the handler
695
- ;(request as { params?: Record<string, string> }).params = params
696
-
697
- const allMiddleware = [...this.globalMiddleware]
698
-
699
- // Conditional middleware (registered via `when(...)`) runs when its
700
- // condition holds for this request — previously it was collected but
701
- // never consulted
702
- for (const conditional of this.conditionalMiddleware) {
703
- if (await conditional.condition(request)) {
704
- allMiddleware.push(...conditional.middleware)
705
- }
706
- }
707
-
708
- allMiddleware.push(...route.middleware)
709
-
710
- let index = 0
711
- const next = async (): Promise<Response> => {
712
- if (index < allMiddleware.length) {
713
- const middleware = allMiddleware[index++]
714
- const result = await middleware(request, next)
715
- return result ?? new Response(null)
716
- }
717
- return await route.handler(request)
718
- }
719
-
720
- return await next()
721
- }
722
- }
723
-
724
- /** API route options */
725
- export interface ApiRouteOptions {
726
- method?: string
727
- cache?: string[]
728
- throttle?: string
729
- }
730
-
731
- /** Protected route options */
732
- export interface ProtectedRouteOptions {
733
- method?: string
734
- roles?: string[]
735
- }
736
-
737
- /** Upload route options */
738
- export interface UploadRouteOptions {
739
- maxSize?: number
740
- allowedTypes?: string[]
741
- }
742
-
743
- /**
744
- * Factory functions for common route patterns
745
- */
746
- export const RouteFactory: {
747
- api: (path: string, handler: RouteHandler, options?: ApiRouteOptions) => FluentRouteBuilder
748
- protected: (path: string, handler: RouteHandler, options?: ProtectedRouteOptions) => FluentRouteBuilder
749
- upload: (path: string, handler: RouteHandler, options?: UploadRouteOptions) => FluentRouteBuilder
750
- } = {
751
- /**
752
- * API route with caching and throttling
753
- */
754
- api: (path: string, handler: RouteHandler, options: ApiRouteOptions = {}): FluentRouteBuilder => {
755
- const method = options.method || 'GET'
756
- const builder = new FluentRouteBuilder(method, path, handler)
757
-
758
- if (options.cache) {
759
- builder.cached(options.cache)
760
- }
761
-
762
- if (options.throttle) {
763
- builder.throttle(options.throttle as ThrottlePattern)
764
- }
765
-
766
- return builder
767
- },
768
-
769
- /**
770
- * Protected route with authentication
771
- */
772
- protected: (path: string, handler: RouteHandler, options: ProtectedRouteOptions = {}): FluentRouteBuilder => {
773
- const method = options.method || 'GET'
774
- const builder = new FluentRouteBuilder(method, path, handler)
775
-
776
- // Add auth middleware (would need to be implemented)
777
- builder.addMiddleware(async (req: EnhancedRequest, next: NextFunction) => {
778
- // Mock auth check
779
- if (!req.headers.get('Authorization')) {
780
- return new Response('Unauthorized', { status: 401 })
781
- }
782
- return await next()
783
- })
784
-
785
- return builder
786
- },
787
-
788
- /**
789
- * File upload route
790
- */
791
- upload: (path: string, handler: RouteHandler, _options: UploadRouteOptions = {}): FluentRouteBuilder => {
792
- const builder = new FluentRouteBuilder('POST', path, handler)
793
-
794
- // Add file upload middleware (would need to be implemented)
795
- builder.addMiddleware(async (_req: EnhancedRequest, next: NextFunction) => {
796
- // Mock file upload handling
797
- return await next()
798
- })
799
-
800
- return builder
801
- },
802
- }
803
-
804
- /**
805
- * Utility functions
806
- */
807
- export const RouterUtils = {
808
- /**
809
- * Generate a URL for a named route. Resolves the path from the shared
810
- * named-route registry (the same one `router.route()`/`url()` use);
811
- * falls back to `/<name>` when the name was never registered.
812
- */
813
- route: (name: string, params: Record<string, string> = {}, query: Record<string, string> = {}): string => {
814
- let url = getNamedRoutePath(name) ?? `/${name}`
815
-
816
- for (const [key, value] of Object.entries(params)) {
817
- const encoded = encodeURIComponent(value)
818
- url = url.replace(`{${key}}`, encoded)
819
- url = url.replace(`{${key}?}`, encoded)
820
- }
821
-
822
- // Drop unfilled optional placeholders and tidy the slashes they leave
823
- if (url.includes('?}')) {
824
- url = url.replace(/\{[^}]+\?\}/g, '').replace(/\/{2,}/g, '/')
825
- if (url.length > 1 && url.endsWith('/'))
826
- url = url.slice(0, -1)
827
- }
828
-
829
- const queryString = new URLSearchParams(query).toString()
830
- if (queryString) {
831
- url += `?${queryString}`
832
- }
833
-
834
- return url
835
- },
836
-
837
- /**
838
- * Redirect response
839
- */
840
- redirect: (url: string, status: number = 302): Response => {
841
- return new Response(null, {
842
- status,
843
- headers: { Location: url },
844
- })
845
- },
846
-
847
- /**
848
- * JSON response
849
- */
850
- json: (data: unknown, status: number = 200): Response => {
851
- return new Response(JSON.stringify(data), {
852
- status,
853
- headers: { 'Content-Type': 'application/json' },
854
- })
855
- },
856
- }
857
-
858
- /**
859
- * Global router instance
860
- */
861
- export const router: FluentRouter = new FluentRouter()
862
-
863
- /**
864
- * Export commonly used factory functions
865
- */
866
- export { RouteCacheFactory, ThrottleFactory }