@stacksjs/bun-router 0.0.16 → 0.0.17

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 (83) hide show
  1. package/dist/auth.d.ts +22 -8
  2. package/dist/chunk-1ahs68ys.js +18 -0
  3. package/dist/{chunk-0145g1me.js → chunk-cgptvjdf.js} +147 -75
  4. package/dist/chunk-g3ybefhg.js +1923 -0
  5. package/dist/cli.js +3 -2
  6. package/dist/container/container.d.ts +1 -0
  7. package/dist/container/contextual-binding.d.ts +0 -1
  8. package/dist/container/decorators.d.ts +2 -5
  9. package/dist/container/index.d.ts +16 -0
  10. package/dist/container/index.js +322 -0
  11. package/dist/file-serving/static-files.d.ts +16 -0
  12. package/dist/index.d.ts +17 -1
  13. package/dist/index.js +1159 -1380
  14. package/dist/middleware/cors.d.ts +9 -0
  15. package/dist/middleware/csrf.d.ts +7 -0
  16. package/dist/middleware/ddos_protection.d.ts +16 -1
  17. package/dist/middleware/rate_limit.d.ts +7 -1
  18. package/dist/middleware/request_tracer.d.ts +15 -9
  19. package/dist/middleware/response_cache.d.ts +2 -1
  20. package/dist/request/context.d.ts +12 -1
  21. package/dist/request/macros.d.ts +40 -2
  22. package/dist/response/macros.d.ts +3 -3
  23. package/dist/router/fluent-routing.d.ts +50 -13
  24. package/dist/router/handler-resolver.d.ts +8 -0
  25. package/dist/router/index.d.ts +2 -0
  26. package/dist/router/route-compiler.d.ts +8 -10
  27. package/dist/router/route-trie.d.ts +7 -1
  28. package/dist/router/router.d.ts +13 -2
  29. package/dist/router/validation-integration.d.ts +10 -4
  30. package/dist/types/middleware-types.d.ts +1 -1
  31. package/dist/types/route-inference.d.ts +9 -2
  32. package/dist/types.d.ts +62 -9
  33. package/dist/utils.d.ts +1 -1
  34. package/package.json +11 -1
  35. package/src/auth.ts +32 -12
  36. package/src/container/container.ts +58 -12
  37. package/src/container/contextual-binding.ts +4 -1
  38. package/src/container/decorators.ts +16 -7
  39. package/src/container/index.ts +49 -0
  40. package/src/errors/circuit-breaker.ts +3 -2
  41. package/src/errors/graceful-degradation.ts +2 -0
  42. package/src/file-serving/static-files.ts +144 -7
  43. package/src/index.ts +53 -1
  44. package/src/middleware/cors.ts +76 -8
  45. package/src/middleware/csrf.ts +70 -22
  46. package/src/middleware/ddos_protection.ts +27 -3
  47. package/src/middleware/input_validation.ts +10 -6
  48. package/src/middleware/performance_alerting.ts +2 -0
  49. package/src/middleware/performance_monitor.ts +2 -0
  50. package/src/middleware/rate_limit.ts +7 -1
  51. package/src/middleware/request_signing.ts +2 -0
  52. package/src/middleware/request_tracer.ts +34 -17
  53. package/src/middleware/response_cache.ts +48 -13
  54. package/src/middleware/session.ts +3 -3
  55. package/src/observability/metrics.ts +1 -1
  56. package/src/request/context.ts +50 -3
  57. package/src/request/enhanced-request.ts +20 -1
  58. package/src/request/macros.ts +183 -49
  59. package/src/response/macros.ts +3 -3
  60. package/src/router/file-based-routing.ts +17 -10
  61. package/src/router/fluent-routing.ts +117 -48
  62. package/src/router/group-organization.ts +17 -1
  63. package/src/router/handler-resolver.ts +36 -0
  64. package/src/router/http-methods.ts +33 -3
  65. package/src/router/index.ts +6 -0
  66. package/src/router/middleware.ts +3 -0
  67. package/src/router/optimized-route-matching.ts +58 -9
  68. package/src/router/route-compiler.ts +62 -73
  69. package/src/router/route-matching.ts +19 -0
  70. package/src/router/route-trie.ts +34 -48
  71. package/src/router/router.ts +72 -60
  72. package/src/router/server.ts +361 -139
  73. package/src/router/validation-integration.ts +11 -5
  74. package/src/testing/auth-testing.ts +6 -4
  75. package/src/types/middleware-types.ts +7 -4
  76. package/src/types/route-inference.ts +29 -14
  77. package/src/types.ts +63 -7
  78. package/src/url.ts +7 -2
  79. package/src/utils.ts +9 -6
  80. package/src/validation/validator.ts +7 -4
  81. package/src/websocket/clustering.ts +8 -1
  82. package/dist/router/fluent-router.d.ts +0 -315
  83. package/src/router/fluent-router.ts +0 -927
@@ -4,16 +4,29 @@
4
4
  * Advanced TypeScript utilities for inferring route parameter types from URL patterns
5
5
  */
6
6
 
7
- // Base types for route parameter extraction
8
- export type ExtractRouteParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}`
9
- ? { [K in Param]: string } & ExtractRouteParams<`/${Rest}`>
10
- : T extends `${infer _Start}:${infer Param}?${infer Rest}`
11
- ? { [K in Param]?: string } & ExtractRouteParams<Rest>
12
- : T extends `${infer _Start}:${infer Param}`
13
- ? { [K in Param]: string }
14
- : T extends `${infer _Start}*${infer Rest}`
15
- ? { '*': string } & ExtractRouteParams<Rest>
16
- : Record<string, never>
7
+ /** Strip an inline constraint from a brace parameter: `id:[0-9]+` → `id` */
8
+ type StripInlineConstraint<T extends string> = T extends `${infer Name}:${string}` ? Name : T
9
+
10
+ // Base types for route parameter extraction.
11
+ //
12
+ // The router's canonical parameter syntax is `{param}` (with `{param?}` for
13
+ // optional and `{param:regex}` for constrained parameters) — that form is
14
+ // handled first. The legacy Express-style `:param` clauses are kept for
15
+ // backward compatibility with code written against the colon syntax.
16
+ export type ExtractRouteParams<T extends string> =
17
+ T extends `${string}{${infer Param}}${infer Rest}`
18
+ ? (Param extends `${infer Inner}?`
19
+ ? { [K in StripInlineConstraint<Inner>]?: string }
20
+ : { [K in StripInlineConstraint<Param>]: string }) & ExtractRouteParams<Rest>
21
+ : T extends `${infer _Start}:${infer Param}/${infer Rest}`
22
+ ? { [K in Param]: string } & ExtractRouteParams<`/${Rest}`>
23
+ : T extends `${infer _Start}:${infer Param}?${infer Rest}`
24
+ ? { [K in Param]?: string } & ExtractRouteParams<Rest>
25
+ : T extends `${infer _Start}:${infer Param}`
26
+ ? { [K in Param]: string }
27
+ : T extends `${infer _Start}*${infer Rest}`
28
+ ? { '*': string } & ExtractRouteParams<Rest>
29
+ : Record<string, never>
17
30
 
18
31
  // Advanced parameter extraction with type constraints
19
32
  export type ExtractTypedParams<T extends string> = T extends `${infer _Start}:${infer Param}<${infer Type}>${infer Rest}`
@@ -214,11 +227,13 @@ export interface RouteGroup<TPrefix extends string = '', TContext = object> {
214
227
  routes: TypedRoute<any, any, any, TContext>[]
215
228
  }
216
229
 
217
- // Middleware type (forward declaration - will be defined in middleware types)
230
+ // Middleware type (forward declaration — the canonical definition lives in
231
+ // middleware-types.ts; this one mirrors its narrow defaults so the two
232
+ // can't drift apart silently)
218
233
  export interface TypedMiddleware<
219
- TRequest = any,
220
- TResponse = any,
221
- TNext = any,
234
+ TRequest = Request,
235
+ TResponse = Response,
236
+ TNext = () => Promise<TResponse>,
222
237
  _TContext = Record<string, never>,
223
238
  > {
224
239
  (request: TRequest, next: TNext): Promise<TResponse> | TResponse
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Server } from 'bun'
2
2
  import type { Router } from './router/router'
3
+ import type { SessionManager } from './session/index'
3
4
  import type { QueryPreservationConfig } from './utils/query-preservation'
4
5
  import type {
5
6
  AuthContext,
@@ -807,6 +808,12 @@ export interface EnhancedRequest extends Request, Omit<RequestMacroMethods, 'ip'
807
808
  * Parsed JSON body (if Content-Type is application/json)
808
809
  */
809
810
  jsonBody?: Record<string, unknown>
811
+ /**
812
+ * Raw unparsed request body cache, populated on first `rawBody()` read (or up
813
+ * front by a framework body parser). Kept so webhook signature checks get the
814
+ * exact bytes the client sent.
815
+ */
816
+ _rawBody?: string
810
817
  /**
811
818
  * Form body data (if Content-Type is multipart/form-data or application/x-www-form-urlencoded)
812
819
  */
@@ -816,9 +823,11 @@ export interface EnhancedRequest extends Request, Omit<RequestMacroMethods, 'ip'
816
823
  */
817
824
  files?: UploadedFile[]
818
825
  /**
819
- * Session data (if session middleware is used)
826
+ * Session for the current request. The built-in session middleware
827
+ * attaches a `SessionManager` (with `get`/`set`/`flash`/… methods);
828
+ * testing utilities may attach plain `SessionData`.
820
829
  */
821
- session?: SessionData
830
+ session?: SessionManager | SessionData
822
831
  /**
823
832
  * User data (if authentication middleware is used)
824
833
  */
@@ -1057,6 +1066,13 @@ export interface Route {
1057
1066
  pattern?: {
1058
1067
  exec: (url: URL) => PatternMatchResult | null
1059
1068
  }
1069
+ /**
1070
+ * Set to `false` (via `router.withoutNativeDispatch()`) to keep this
1071
+ * route on the fetch-handler matcher even when the server runs with
1072
+ * `nativeRoutes: true` — e.g. when it must respect registration-order
1073
+ * precedence against an overlapping pattern.
1074
+ */
1075
+ nativeDispatch?: boolean
1060
1076
  }
1061
1077
 
1062
1078
  /**
@@ -1102,6 +1118,22 @@ export interface ServerOptions<T extends WebSocketData = WebSocketData> extends
1102
1118
  * These bypass the fetch handler entirely for zero-allocation serving (~15% faster).
1103
1119
  */
1104
1120
  static?: Record<string, Response>
1121
+ /**
1122
+ * Opt-in: register compatible routes with `Bun.serve`'s native router
1123
+ * (`routes`), skipping the fetch handler's URL parsing and route
1124
+ * matching for them. Compatible means: standard HTTP method, no
1125
+ * domain scoping, no `where()`/inline constraints, no optional
1126
+ * params, and `{param}`s spanning whole segments. Everything else —
1127
+ * incompatible routes, 404/405 handling, HEAD fallback, the generic
1128
+ * OPTIONS preflight — still flows through the fetch handler with
1129
+ * identical semantics.
1130
+ *
1131
+ * Caveat: Bun matches by specificity (exact > param > wildcard).
1132
+ * Apps that rely on registration order between same-shape overlapping
1133
+ * patterns should leave this off. Routes registered after `serve()`
1134
+ * only join the native table after `reload()`.
1135
+ */
1136
+ nativeRoutes?: boolean
1105
1137
  /**
1106
1138
  * Development mode options for Bun's dev server.
1107
1139
  * When true, enables HMR and development features.
@@ -1273,12 +1305,33 @@ export type MiddlewareWithParams<T extends string = string> =
1273
1305
  export type RouteParam<T extends string> = T extends `{${infer P}}` ? P : never
1274
1306
 
1275
1307
  /**
1276
- * Extract parameters from route path
1308
+ * Strip an inline constraint from a parameter token:
1309
+ * `id:[0-9]+` → `id`, `slug` → `slug`
1310
+ */
1311
+ export type ExtractParamName<T extends string> =
1312
+ T extends `${infer Name}:${string}` ? Name : T
1313
+
1314
+ /**
1315
+ * Extract parameters from a route path into a narrowly-typed object.
1316
+ *
1317
+ * Handles the full parameter grammar:
1318
+ * - `{id}` → `{ id: string }`
1319
+ * - `{id?}` (optional) → `{ id?: string }`
1320
+ * - `{id:[0-9]+}` (inline constraint) → `{ id: string }`
1321
+ * - trailing `*` wildcard → `{ wildcard: string }`
1322
+ *
1323
+ * @example
1324
+ * type P = ExtractRouteParams<'/users/{userId}/posts/{slug?}'>
1325
+ * // ^? { userId: string } & { slug?: string }
1277
1326
  */
1278
1327
  export type ExtractRouteParams<T extends string> =
1279
1328
  T extends `${string}{${infer Param}}${infer Rest}`
1280
- ? { [K in Param]: string } & ExtractRouteParams<Rest>
1281
- : object
1329
+ ? (Param extends `${infer Inner}?`
1330
+ ? { [K in ExtractParamName<Inner>]?: string }
1331
+ : { [K in ExtractParamName<Param>]: string }) & ExtractRouteParams<Rest>
1332
+ : T extends `${string}*${string}`
1333
+ ? { wildcard: string }
1334
+ : object
1282
1335
 
1283
1336
  /**
1284
1337
  * Route method constraints
@@ -1735,8 +1788,8 @@ export interface StreamingOptions {
1735
1788
  /**
1736
1789
  * SSE (Server-Sent Events) data structure
1737
1790
  */
1738
- export interface SSEData {
1739
- data: any
1791
+ export interface SSEData<T = unknown> {
1792
+ data: T
1740
1793
  event?: string
1741
1794
  id?: string
1742
1795
  retry?: number
@@ -1933,6 +1986,9 @@ export interface RequestMacroMethods {
1933
1986
  bearerToken: () => string | null
1934
1987
  basicAuth: () => { username: string, password: string } | null
1935
1988
 
1989
+ /** Raw unparsed request body as a string (cached). Needed for webhook signature verification (Stripe/GitHub/Slack), where a re-serialized jsonBody won't match the HMAC. */
1990
+ rawBody: () => Promise<string>
1991
+
1936
1992
  // Headers
1937
1993
  hasHeader: (name: string) => boolean
1938
1994
  header: (name: string, defaultValue?: string) => string | null
package/src/url.ts CHANGED
@@ -100,8 +100,13 @@ export function url(
100
100
  }
101
101
  }
102
102
 
103
- // Strip any unfilled optional placeholders (e.g. `/posts/{slug?}` → `/posts/`)
104
- resolved = resolved.replace(/\/\{[^}]+\?\}/g, '')
103
+ // Strip any unfilled optional placeholders and tidy the slashes they
104
+ // leave behind (`/posts/{slug?}` → `/posts`, `/a/{v?}/b` → `/a/b`)
105
+ if (resolved.includes('?}')) {
106
+ resolved = resolved.replace(/\{[^}]+\?\}/g, '').replace(/\/{2,}/g, '/')
107
+ if (resolved.length > 1 && resolved.endsWith('/'))
108
+ resolved = resolved.slice(0, -1)
109
+ }
105
110
 
106
111
  // Throw on unfilled required placeholders so the caller sees the bug.
107
112
  const missing = resolved.match(/\{([^}?]+)\}/g)
package/src/utils.ts CHANGED
@@ -319,7 +319,7 @@ export async function resolveViewPath(
319
319
  * @param template The HTML template string
320
320
  * @param data The data to inject into the template
321
321
  */
322
- export function processHtmlTemplate(template: string, data: Record<string, any>): string {
322
+ export function processHtmlTemplate(template: string, data: Record<string, unknown>): string {
323
323
  // Process general variables {{ varName }}
324
324
  let result = template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
325
325
  const trimmedKey = key.trim()
@@ -339,7 +339,7 @@ export function processHtmlTemplate(template: string, data: Record<string, any>)
339
339
  /**
340
340
  * Process conditional statements in templates
341
341
  */
342
- function processConditionals(template: string, data: Record<string, any>): string {
342
+ function processConditionals(template: string, data: Record<string, unknown>): string {
343
343
  // Match {{#if condition}} content {{/if}} or {{#if condition}} content {{else}} alternative {{/if}}
344
344
  return template.replace(
345
345
  /\{\{#if ([^}]+)\}\}([\s\S]*?)(?:\{\{else\}\}([\s\S]*?))?\{\{\/if\}\}/g,
@@ -355,7 +355,7 @@ function processConditionals(template: string, data: Record<string, any>): strin
355
355
  /**
356
356
  * Process loop statements in templates
357
357
  */
358
- function processLoops(template: string, data: Record<string, any>): string {
358
+ function processLoops(template: string, data: Record<string, unknown>): string {
359
359
  // Match {{#each items}} content {{/each}}
360
360
  return template.replace(
361
361
  /\{\{#each ([^}]+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
@@ -385,8 +385,11 @@ function processLoops(template: string, data: Record<string, any>): string {
385
385
  * @param obj The object to extract value from
386
386
  * @param path The path to the value (e.g., 'user.profile.name')
387
387
  */
388
- function getNestedValue(obj: Record<string, any>, path: string): any {
389
- return path.split('.').reduce((prev, curr) => {
390
- return prev && prev[curr] !== undefined ? prev[curr] : undefined
388
+ function getNestedValue(obj: Record<string, unknown>, path: string): unknown {
389
+ return path.split('.').reduce<unknown>((prev, curr) => {
390
+ if (prev && typeof prev === 'object' && curr in prev) {
391
+ return (prev as Record<string, unknown>)[curr]
392
+ }
393
+ return undefined
391
394
  }, obj)
392
395
  }
@@ -7,6 +7,12 @@
7
7
  import type { EnhancedRequest, NextFunction } from '../types'
8
8
  import { ValidationException } from '../errors/exceptions'
9
9
 
10
+ // Hoisted email check. Pragmatic RFC 5322 approximation: requires one `@`,
11
+ // a non-empty local part without surrounding/consecutive dots, and a domain
12
+ // with at least one label dot and a 2+ char TLD. The previous regex
13
+ // accepted addresses like `a@.com` and consecutive dots.
14
+ const EMAIL_REGEX = /^[A-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[A-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?(?:\.[A-Z0-9](?:[A-Z0-9-]*[A-Z0-9])?)*\.[A-Z]{2,}$/i
15
+
10
16
  export interface ValidationRule {
11
17
  name: string
12
18
  validate: (value: any, parameters: string[], field: string, data: Record<string, any>) => Promise<boolean> | boolean
@@ -74,10 +80,7 @@ export const BuiltInRules: Record<string, ValidationRule> = {
74
80
 
75
81
  email: {
76
82
  name: 'email',
77
- validate: (value) => {
78
- const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/
79
- return typeof value === 'string' && emailRegex.test(value)
80
- },
83
+ validate: value => typeof value === 'string' && EMAIL_REGEX.test(value),
81
84
  message: 'The :field field must be a valid email address.',
82
85
  },
83
86
 
@@ -539,8 +539,13 @@ export class WebSocketCluster {
539
539
  break
540
540
 
541
541
  case 'stats':
542
- // Handle stats updates from other workers
542
+ // Handle stats updates from other workers. Bounded: with worker
543
+ // churn the per-worker entries would otherwise accumulate forever.
543
544
  if (message.data && message.workerId) {
545
+ const workerIds = Object.keys(this.stats.workerStats)
546
+ if (workerIds.length >= 1000 && !(message.workerId in this.stats.workerStats)) {
547
+ delete this.stats.workerStats[workerIds[0]]
548
+ }
544
549
  this.stats.workerStats[message.workerId] = message.data
545
550
  }
546
551
  break
@@ -623,6 +628,8 @@ export class WebSocketCluster {
623
628
  timestamp: now,
624
629
  })
625
630
  }, this.config.heartbeatInterval)
631
+ // The heartbeat must not keep the process alive on shutdown
632
+ this.heartbeatTimer.unref?.()
626
633
  }
627
634
 
628
635
  /**
@@ -1,315 +0,0 @@
1
- /**
2
- * Fluent Routing Extensions
3
- *
4
- * Implements fluent routing patterns with full TypeScript support
5
- */
6
- import type { EnhancedRequest, MiddlewareHandler, Route, RouteHandler } from '../types';
7
- import type { Router } from './router';
8
- /**
9
- * Controller class interface
10
- */
11
- export interface ControllerClass {
12
- new (): Controller;
13
- }
14
- /**
15
- * Base controller interface
16
- */
17
- export interface Controller {
18
- [key: string]: RouteHandler | unknown;
19
- }
20
- /**
21
- * Resource controller methods
22
- */
23
- export type ResourceMethod = 'index' | 'create' | 'store' | 'show' | 'edit' | 'update' | 'destroy';
24
- /**
25
- * API resource methods (no create/edit views)
26
- */
27
- export type ApiResourceMethod = 'index' | 'store' | 'show' | 'update' | 'destroy';
28
- /**
29
- * Singleton resource methods
30
- */
31
- export type SingletonMethod = 'show' | 'edit' | 'update';
32
- /**
33
- * Resource configuration
34
- */
35
- export interface ResourceOptions {
36
- only?: ResourceMethod[];
37
- except?: ResourceMethod[];
38
- middleware?: (string | MiddlewareHandler)[];
39
- names?: Partial<Record<ResourceMethod, string>>;
40
- parameters?: Record<string, string>;
41
- shallow?: boolean;
42
- }
43
- /**
44
- * Route builder for fluent API
45
- */
46
- export interface FluentRoute {
47
- name: (name: string) => FluentRoute;
48
- middleware: (...middleware: (string | MiddlewareHandler)[]) => FluentRoute;
49
- withoutMiddleware: (...middleware: string[]) => FluentRoute;
50
- where: (param: string, pattern: string | RegExp) => FluentRoute;
51
- whereNumber: (param: string) => FluentRoute;
52
- whereAlpha: (param: string) => FluentRoute;
53
- whereAlphaNumeric: (param: string) => FluentRoute;
54
- whereUuid: (param: string) => FluentRoute;
55
- whereSlug: (param: string) => FluentRoute;
56
- can: (ability: string, model?: string) => FluentRoute;
57
- missing: (handler: (req: EnhancedRequest) => Response) => FluentRoute;
58
- withTrashed: () => FluentRoute;
59
- scopeBindings: () => FluentRoute;
60
- }
61
- /**
62
- * Group builder for fluent API
63
- */
64
- export interface GroupBuilder {
65
- prefix: (prefix: string) => GroupBuilder;
66
- name: (name: string) => GroupBuilder;
67
- middleware: (...middleware: (string | MiddlewareHandler)[]) => GroupBuilder;
68
- domain: (domain: string) => GroupBuilder;
69
- controller: (controller: ControllerClass) => GroupBuilder;
70
- routes: (callback: (router: FluentRouter) => void) => void;
71
- }
72
- /**
73
- * Fluent router with chainable API
74
- */
75
- export declare class FluentRouter {
76
- private router;
77
- private currentPrefix;
78
- private currentNamePrefix;
79
- private currentMiddleware;
80
- private currentDomain;
81
- private currentController;
82
- private excludedMiddleware;
83
- constructor(router: Router);
84
- /**
85
- * Register a GET route
86
- */
87
- get(path: string, handler: RouteHandler | string | Response): FluentRouteBuilder;
88
- /**
89
- * Register a POST route
90
- */
91
- post(path: string, handler: RouteHandler | string): FluentRouteBuilder;
92
- /**
93
- * Register a PUT route
94
- */
95
- put(path: string, handler: RouteHandler | string): FluentRouteBuilder;
96
- /**
97
- * Register a PATCH route
98
- */
99
- patch(path: string, handler: RouteHandler | string): FluentRouteBuilder;
100
- /**
101
- * Register a DELETE route
102
- */
103
- delete(path: string, handler: RouteHandler | string): FluentRouteBuilder;
104
- /**
105
- * Register an OPTIONS route
106
- */
107
- options(path: string, handler: RouteHandler | string): FluentRouteBuilder;
108
- /**
109
- * Register a route for any HTTP method
110
- */
111
- any(path: string, handler: RouteHandler | string): FluentRouteBuilder;
112
- /**
113
- * Register a route for multiple HTTP methods
114
- */
115
- match(methods: string[], path: string, handler: RouteHandler | string): FluentRouteBuilder;
116
- /**
117
- * Register a resource controller
118
- */
119
- resource(name: string, Controller: ControllerClass, options?: ResourceOptions): this;
120
- /**
121
- * Register an API resource controller (no create/edit views)
122
- */
123
- apiResource(name: string, controller: ControllerClass, options?: ResourceOptions): this;
124
- /**
125
- * Register a singleton resource (no index, create, store, destroy)
126
- */
127
- singleton(name: string, Controller: ControllerClass, options?: ResourceOptions): this;
128
- /**
129
- * Create a route group with prefix
130
- */
131
- prefix(prefix: string): GroupBuilderImpl;
132
- /**
133
- * Create a route group with name prefix
134
- */
135
- name(namePrefix: string): GroupBuilderImpl;
136
- /**
137
- * Create a route group with middleware
138
- */
139
- middleware(...middleware: (string | MiddlewareHandler)[]): GroupBuilderImpl;
140
- /**
141
- * Create a route group for a domain
142
- */
143
- domain(domain: string): GroupBuilderImpl;
144
- /**
145
- * Create a route group with a controller
146
- */
147
- controller(controller: ControllerClass): GroupBuilderImpl;
148
- /**
149
- * Create a route group
150
- */
151
- group(options: GroupOptions, callback: (router: FluentRouter) => void): this;
152
- /**
153
- * Check if a named route exists
154
- */
155
- has(name: string): boolean;
156
- /**
157
- * Get the current route (from request context)
158
- */
159
- current(): Route | null;
160
- /**
161
- * Get the current route name
162
- */
163
- currentRouteName(): string | null;
164
- /**
165
- * Get URL for a named route
166
- */
167
- route(name: string, params?: Record<string, string | number>): string;
168
- /**
169
- * Register a fallback route
170
- */
171
- fallback(handler: RouteHandler): this;
172
- private addRoute;
173
- /**
174
- * Create a pattern object for route matching
175
- */
176
- private createPattern;
177
- private buildPath;
178
- private buildMiddleware;
179
- private resolveHandler;
180
- private resolveMiddleware;
181
- private getResourceMethods;
182
- private getResourceRoute;
183
- private getSingletonRoute;
184
- }
185
- /**
186
- * Fluent route builder for chaining route configuration
187
- */
188
- export declare class FluentRouteBuilder implements FluentRoute {
189
- private router;
190
- private path;
191
- private method;
192
- private routeName;
193
- private routeMiddleware;
194
- private excludedMiddleware;
195
- private constraints;
196
- private missingHandler;
197
- private includeTrashed;
198
- private scopeBindingsEnabled;
199
- private ability;
200
- private abilityModel;
201
- constructor(router: Router, path: string, method: string);
202
- /**
203
- * Set the route name
204
- */
205
- name(name: string): this;
206
- /**
207
- * Add middleware to the route
208
- */
209
- middleware(...middleware: (string | MiddlewareHandler)[]): this;
210
- /**
211
- * Exclude middleware from the route
212
- */
213
- withoutMiddleware(...middleware: string[]): this;
214
- /**
215
- * Add a parameter constraint
216
- */
217
- where(param: string, pattern: string | RegExp): this;
218
- /**
219
- * Constrain parameter to numbers
220
- */
221
- whereNumber(param: string): this;
222
- /**
223
- * Constrain parameter to letters
224
- */
225
- whereAlpha(param: string): this;
226
- /**
227
- * Constrain parameter to alphanumeric
228
- */
229
- whereAlphaNumeric(param: string): this;
230
- /**
231
- * Constrain parameter to UUID
232
- */
233
- whereUuid(param: string): this;
234
- /**
235
- * Constrain parameter to slug format
236
- */
237
- whereSlug(param: string): this;
238
- /**
239
- * Add authorization check
240
- */
241
- can(ability: string, model?: string): this;
242
- /**
243
- * Handle missing model binding
244
- */
245
- missing(handler: (req: EnhancedRequest) => Response): this;
246
- /**
247
- * Include soft-deleted models
248
- */
249
- withTrashed(): this;
250
- /**
251
- * Enable scoped bindings
252
- */
253
- scopeBindings(): this;
254
- private findRoute;
255
- }
256
- interface GroupOptions {
257
- prefix?: string;
258
- as?: string;
259
- middleware?: (string | MiddlewareHandler)[];
260
- domain?: string;
261
- controller?: ControllerClass;
262
- }
263
- declare class GroupBuilderImpl implements GroupBuilder {
264
- private fluentRouter;
265
- private options;
266
- constructor(router: FluentRouter);
267
- prefix(prefix: string): this;
268
- name(name: string): this;
269
- middleware(...middleware: (string | MiddlewareHandler)[]): this;
270
- domain(domain: string): this;
271
- controller(controller: ControllerClass): this;
272
- routes(callback: (router: FluentRouter) => void): void;
273
- }
274
- /**
275
- * Extend Router with fluent routing methods
276
- */
277
- export declare function registerFluentRouting(RouterClass: typeof import('./router').Router): void;
278
- declare module './router' {
279
- interface Router {
280
- /**
281
- * Get fluent router instance
282
- */
283
- fluent: () => FluentRouter;
284
- /**
285
- * Register a controller-based resource
286
- * Note: Use `resourceController` to avoid conflict with existing `resource` method
287
- */
288
- resourceController: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router;
289
- /**
290
- * Register an API resource controller (no create/edit views)
291
- */
292
- apiResourceController: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router;
293
- /**
294
- * Register a singleton resource
295
- */
296
- singletonResource: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router;
297
- /**
298
- * Check if a named route exists
299
- */
300
- hasRoute: (name: string) => boolean;
301
- /**
302
- * Get the current route
303
- */
304
- currentRoute: () => Route | null;
305
- /**
306
- * Get the current route name
307
- */
308
- currentRouteName: () => string | null;
309
- /**
310
- * Get URL for a named route
311
- */
312
- routeUrl: (name: string, params?: Record<string, string | number>) => string;
313
- }
314
- }
315
- export {};