@stacksjs/bun-router 0.0.17 → 0.0.19

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 (153) hide show
  1. package/dist/chunk-2mx7fq49.js +4 -0
  2. package/dist/chunk-a8p83068.js +103 -0
  3. package/dist/{chunk-cgptvjdf.js → chunk-y3xq6wq6.js} +1 -1
  4. package/dist/cli.js +4 -3
  5. package/dist/container/index.js +1149 -211
  6. package/dist/container/service-provider.d.ts +3 -3
  7. package/dist/development/performance-profiler.d.ts +3 -3
  8. package/dist/development/route-debugger.d.ts +1 -1
  9. package/dist/development/route-inspector.d.ts +2 -2
  10. package/dist/index.js +369 -2209
  11. package/dist/observability/integration.d.ts +1 -1
  12. package/dist/response/macros.d.ts +1 -1
  13. package/dist/router/index.d.ts +7 -7
  14. package/dist/router/router.d.ts +0 -32
  15. package/dist/testing/performance-testing.d.ts +2 -2
  16. package/dist/testing/test-client.d.ts +1 -1
  17. package/dist/testing/websocket-testing.d.ts +1 -1
  18. package/package.json +7 -7
  19. package/dist/chunk-1ahs68ys.js +0 -18
  20. package/dist/chunk-g3ybefhg.js +0 -1923
  21. package/src/auth.ts +0 -489
  22. package/src/cache/lru-cache.ts +0 -457
  23. package/src/cache/middleware-memoization.ts +0 -531
  24. package/src/cache/route-cache-warmer.ts +0 -486
  25. package/src/cache/sqlite-cache.ts +0 -783
  26. package/src/cache/streaming-cache.ts +0 -572
  27. package/src/cli/colors.ts +0 -29
  28. package/src/cli/index.ts +0 -287
  29. package/src/cli/middleware.ts +0 -291
  30. package/src/cli/openapi.ts +0 -407
  31. package/src/cli/router.ts +0 -188
  32. package/src/cli/routes.ts +0 -265
  33. package/src/cli/utils.ts +0 -531
  34. package/src/cli.ts +0 -5
  35. package/src/config.ts +0 -322
  36. package/src/container/container.ts +0 -786
  37. package/src/container/contextual-binding.ts +0 -606
  38. package/src/container/decorators.ts +0 -368
  39. package/src/container/index.ts +0 -49
  40. package/src/container/service-provider.ts +0 -596
  41. package/src/development/hot-reload.ts +0 -673
  42. package/src/development/index.ts +0 -499
  43. package/src/development/performance-profiler.ts +0 -717
  44. package/src/development/route-debugger.ts +0 -527
  45. package/src/development/route-inspector.ts +0 -749
  46. package/src/development/typescript-utilities.ts +0 -682
  47. package/src/docs.ts +0 -397
  48. package/src/errors/circuit-breaker.ts +0 -733
  49. package/src/errors/error-handler.ts +0 -569
  50. package/src/errors/error-reporting.ts +0 -672
  51. package/src/errors/exceptions.ts +0 -536
  52. package/src/errors/graceful-degradation.ts +0 -623
  53. package/src/errors/index.ts +0 -21
  54. package/src/errors/router-errors.ts +0 -632
  55. package/src/file-serving/static-files.ts +0 -718
  56. package/src/index.ts +0 -66
  57. package/src/middleware/auth.ts +0 -220
  58. package/src/middleware/content_security_policy.ts +0 -215
  59. package/src/middleware/cors.ts +0 -142
  60. package/src/middleware/csrf.ts +0 -166
  61. package/src/middleware/ddos_protection.ts +0 -279
  62. package/src/middleware/file_security.ts +0 -191
  63. package/src/middleware/file_upload.ts +0 -275
  64. package/src/middleware/helmet.ts +0 -268
  65. package/src/middleware/index.ts +0 -117
  66. package/src/middleware/input_validation.ts +0 -453
  67. package/src/middleware/json_body.ts +0 -37
  68. package/src/middleware/performance_alerting.ts +0 -540
  69. package/src/middleware/performance_dashboard.ts +0 -661
  70. package/src/middleware/performance_monitor.ts +0 -945
  71. package/src/middleware/pipeline.ts +0 -489
  72. package/src/middleware/rate_limit.ts +0 -251
  73. package/src/middleware/request_id.ts +0 -36
  74. package/src/middleware/request_signing.ts +0 -638
  75. package/src/middleware/request_tracer.ts +0 -653
  76. package/src/middleware/response_cache.ts +0 -778
  77. package/src/middleware/security.ts +0 -482
  78. package/src/middleware/security_suite.ts +0 -257
  79. package/src/middleware/session.ts +0 -91
  80. package/src/model-binding/index.ts +0 -18
  81. package/src/model-binding/model-middleware.ts +0 -425
  82. package/src/model-binding/model-registry.ts +0 -550
  83. package/src/model-binding.ts +0 -370
  84. package/src/model-resolver-factory.ts +0 -106
  85. package/src/observability/correlation.ts +0 -691
  86. package/src/observability/health-checks.ts +0 -729
  87. package/src/observability/index.ts +0 -184
  88. package/src/observability/integration.ts +0 -548
  89. package/src/observability/metrics.ts +0 -753
  90. package/src/observability/tracing.ts +0 -638
  91. package/src/optimization/bun-utilities.ts +0 -778
  92. package/src/query-builder-integration.ts +0 -137
  93. package/src/request/context.ts +0 -109
  94. package/src/request/enhanced-request.ts +0 -876
  95. package/src/request/macros.ts +0 -822
  96. package/src/response/macros.ts +0 -665
  97. package/src/response/response-factory.ts +0 -596
  98. package/src/router/api-routes.ts +0 -243
  99. package/src/router/file-based-routing.ts +0 -697
  100. package/src/router/file-streaming.ts +0 -383
  101. package/src/router/fluent-routing.ts +0 -866
  102. package/src/router/group-organization.ts +0 -229
  103. package/src/router/handler-resolver.ts +0 -327
  104. package/src/router/http-methods.ts +0 -407
  105. package/src/router/index.ts +0 -193
  106. package/src/router/middleware-groups.ts +0 -222
  107. package/src/router/middleware-integration.ts +0 -399
  108. package/src/router/middleware.ts +0 -234
  109. package/src/router/model-binding.ts +0 -215
  110. package/src/router/optimized-route-matching.ts +0 -302
  111. package/src/router/route-building.ts +0 -221
  112. package/src/router/route-compiler.ts +0 -691
  113. package/src/router/route-matching.ts +0 -368
  114. package/src/router/route-trie.ts +0 -450
  115. package/src/router/router.ts +0 -1616
  116. package/src/router/server.ts +0 -684
  117. package/src/router/validation-integration.ts +0 -449
  118. package/src/router/view-rendering.ts +0 -233
  119. package/src/router/websocket.ts +0 -100
  120. package/src/routing/route-caching.ts +0 -402
  121. package/src/routing/route-throttling.ts +0 -469
  122. package/src/routing/subdomain-routing.ts +0 -492
  123. package/src/session/database-store.ts +0 -109
  124. package/src/session/file-store.ts +0 -148
  125. package/src/session/index.ts +0 -244
  126. package/src/session/memory-store.ts +0 -88
  127. package/src/session/redis-store.ts +0 -93
  128. package/src/streaming/index.ts +0 -17
  129. package/src/streaming/sse-handler.ts +0 -482
  130. package/src/streaming/stream-handler.ts +0 -552
  131. package/src/testing/auth-testing.ts +0 -448
  132. package/src/testing/file-upload-testing.ts +0 -543
  133. package/src/testing/index.ts +0 -10
  134. package/src/testing/middleware-testing.ts +0 -322
  135. package/src/testing/model-binding-testing.ts +0 -645
  136. package/src/testing/performance-testing.ts +0 -738
  137. package/src/testing/test-client.ts +0 -310
  138. package/src/testing/test-request.ts +0 -315
  139. package/src/testing/test-response.ts +0 -332
  140. package/src/testing/types.ts +0 -224
  141. package/src/testing/websocket-testing.ts +0 -590
  142. package/src/types/controller-types.ts +0 -385
  143. package/src/types/core.ts +0 -683
  144. package/src/types/middleware-types.ts +0 -422
  145. package/src/types/request-response-augmentation.ts +0 -489
  146. package/src/types/route-inference.ts +0 -372
  147. package/src/types.ts +0 -2123
  148. package/src/url.ts +0 -131
  149. package/src/utils/index.ts +0 -1
  150. package/src/utils/query-preservation.ts +0 -201
  151. package/src/utils.ts +0 -395
  152. package/src/validation/validator.ts +0 -688
  153. 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 }