@stacksjs/bun-router 0.0.5 → 0.0.7
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.
- package/dist/auth.d.ts +134 -0
- package/dist/cache/lru-cache.d.ts +139 -0
- package/dist/cache/middleware-memoization.d.ts +151 -0
- package/dist/cache/route-cache-warmer.d.ts +161 -0
- package/dist/cache/sqlite-cache.d.ts +209 -0
- package/dist/cache/streaming-cache.d.ts +132 -0
- package/dist/cli/colors.d.ts +15 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/middleware.d.ts +31 -0
- package/dist/cli/openapi.d.ts +17 -0
- package/dist/cli/router.d.ts +15 -0
- package/dist/cli/routes.d.ts +29 -0
- package/dist/cli/utils.d.ts +74 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/container/container.d.ts +273 -0
- package/dist/container/contextual-binding.d.ts +240 -0
- package/dist/container/decorators.d.ts +141 -0
- package/dist/container/service-provider.d.ts +285 -0
- package/dist/development/hot-reload.d.ts +187 -0
- package/dist/development/index.d.ts +167 -0
- package/dist/development/performance-profiler.d.ts +217 -0
- package/dist/development/route-debugger.d.ts +154 -0
- package/dist/development/route-inspector.d.ts +211 -0
- package/dist/development/typescript-utilities.d.ts +209 -0
- package/dist/docs.d.ts +11 -0
- package/dist/errors/circuit-breaker.d.ts +195 -0
- package/dist/errors/error-handler.d.ts +88 -0
- package/dist/errors/error-reporting.d.ts +144 -0
- package/dist/errors/exceptions.d.ts +184 -0
- package/dist/errors/graceful-degradation.d.ts +154 -0
- package/dist/errors/index.d.ts +9 -0
- package/dist/errors/router-errors.d.ts +110 -0
- package/dist/file-serving/static-files.d.ts +143 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +375 -227
- package/dist/middleware/auth.d.ts +63 -0
- package/dist/middleware/content_security_policy.d.ts +53 -0
- package/dist/middleware/cors.d.ts +5 -0
- package/dist/middleware/csrf.d.ts +7 -0
- package/dist/middleware/ddos_protection.d.ts +39 -0
- package/dist/middleware/file_security.d.ts +26 -0
- package/dist/middleware/file_upload.d.ts +38 -0
- package/dist/middleware/helmet.d.ts +54 -0
- package/dist/middleware/index.d.ts +44 -0
- package/dist/middleware/input_validation.d.ts +45 -0
- package/dist/middleware/json_body.d.ts +4 -0
- package/dist/middleware/performance_alerting.d.ts +87 -0
- package/dist/middleware/performance_dashboard.d.ts +87 -0
- package/dist/middleware/performance_monitor.d.ts +209 -0
- package/dist/middleware/pipeline.d.ts +131 -0
- package/dist/middleware/rate_limit.d.ts +36 -0
- package/dist/middleware/request_id.d.ts +4 -0
- package/dist/middleware/request_signing.d.ts +153 -0
- package/dist/middleware/request_tracer.d.ts +72 -0
- package/dist/middleware/response_cache.d.ts +97 -0
- package/dist/middleware/security.d.ts +69 -0
- package/dist/middleware/security_suite.d.ts +46 -0
- package/dist/middleware/session.d.ts +23 -0
- package/dist/model-binding/index.d.ts +2 -0
- package/dist/model-binding/model-middleware.d.ts +118 -0
- package/dist/model-binding/model-registry.d.ts +164 -0
- package/dist/model-binding.d.ts +185 -0
- package/dist/model-resolver-factory.d.ts +31 -0
- package/dist/observability/correlation.d.ts +187 -0
- package/dist/observability/health-checks.d.ts +186 -0
- package/dist/observability/index.d.ts +60 -0
- package/dist/observability/integration.d.ts +147 -0
- package/dist/observability/metrics.d.ts +184 -0
- package/dist/observability/tracing.d.ts +187 -0
- package/dist/optimization/bun-utilities.d.ts +224 -0
- package/dist/query-builder-integration.d.ts +36 -0
- package/dist/request/context.d.ts +34 -0
- package/dist/request/enhanced-request.d.ts +213 -0
- package/dist/request/macros.d.ts +300 -0
- package/dist/response/macros.d.ts +245 -0
- package/dist/response/response-factory.d.ts +133 -0
- package/dist/router/api-routes.d.ts +5 -0
- package/dist/router/file-based-routing.d.ts +118 -0
- package/dist/router/file-streaming.d.ts +5 -0
- package/dist/router/fluent-router.d.ts +315 -0
- package/dist/router/fluent-routing.d.ts +271 -0
- package/dist/router/group-organization.d.ts +5 -0
- package/dist/router/handler-resolver.d.ts +24 -0
- package/dist/router/http-methods.d.ts +10 -0
- package/dist/router/index.d.ts +113 -0
- package/dist/router/middleware-groups.d.ts +94 -0
- package/dist/router/middleware-integration.d.ts +112 -0
- package/dist/router/middleware.d.ts +5 -0
- package/dist/router/model-binding.d.ts +5 -0
- package/dist/router/optimized-route-matching.d.ts +6 -0
- package/dist/router/route-building.d.ts +5 -0
- package/dist/router/route-compiler.d.ts +153 -0
- package/dist/router/route-matching.d.ts +5 -0
- package/dist/router/route-trie.d.ts +114 -0
- package/dist/router/router.d.ts +276 -0
- package/dist/router/server.d.ts +5 -0
- package/dist/router/validation-integration.d.ts +170 -0
- package/dist/router/view-rendering.d.ts +5 -0
- package/dist/router/websocket.d.ts +5 -0
- package/dist/routing/route-caching.d.ts +129 -0
- package/dist/routing/route-throttling.d.ts +150 -0
- package/dist/routing/subdomain-routing.d.ts +206 -0
- package/dist/session/database-store.d.ts +51 -0
- package/dist/session/file-store.d.ts +22 -0
- package/dist/session/index.d.ts +106 -0
- package/dist/session/memory-store.d.ts +21 -0
- package/dist/session/redis-store.d.ts +31 -0
- package/dist/streaming/index.d.ts +3 -0
- package/dist/streaming/sse-handler.d.ts +138 -0
- package/dist/streaming/stream-handler.d.ts +114 -0
- package/dist/testing/auth-testing.d.ts +156 -0
- package/dist/testing/file-upload-testing.d.ts +190 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/middleware-testing.d.ts +138 -0
- package/dist/testing/model-binding-testing.d.ts +186 -0
- package/dist/testing/performance-testing.d.ts +235 -0
- package/dist/testing/test-client.d.ts +117 -0
- package/dist/testing/test-request.d.ts +85 -0
- package/dist/testing/test-response.d.ts +90 -0
- package/dist/testing/types.d.ts +207 -0
- package/dist/testing/websocket-testing.d.ts +227 -0
- package/dist/types/controller-types.d.ts +208 -0
- package/dist/types/core.d.ts +543 -0
- package/dist/types/middleware-types.d.ts +227 -0
- package/dist/types/request-response-augmentation.d.ts +261 -0
- package/dist/types/route-inference.d.ts +168 -0
- package/dist/types.d.ts +1776 -0
- package/dist/url.d.ts +56 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/query-preservation.d.ts +48 -0
- package/dist/utils.d.ts +69 -0
- package/dist/validation/validator.d.ts +140 -0
- package/dist/websocket/clustering.d.ts +185 -0
- package/package.json +3 -2
- package/src/auth.ts +469 -0
- package/src/cache/lru-cache.ts +457 -0
- package/src/cache/middleware-memoization.ts +531 -0
- package/src/cache/route-cache-warmer.ts +486 -0
- package/src/cache/sqlite-cache.ts +783 -0
- package/src/cache/streaming-cache.ts +572 -0
- package/src/cli/colors.ts +29 -0
- package/src/cli/index.ts +287 -0
- package/src/cli/middleware.ts +291 -0
- package/src/cli/openapi.ts +407 -0
- package/src/cli/router.ts +188 -0
- package/src/cli/routes.ts +265 -0
- package/src/cli/utils.ts +531 -0
- package/src/cli.ts +5 -0
- package/src/config.ts +322 -0
- package/src/container/container.ts +740 -0
- package/src/container/contextual-binding.ts +603 -0
- package/src/container/decorators.ts +359 -0
- package/src/container/service-provider.ts +596 -0
- package/src/development/hot-reload.ts +673 -0
- package/src/development/index.ts +499 -0
- package/src/development/performance-profiler.ts +717 -0
- package/src/development/route-debugger.ts +527 -0
- package/src/development/route-inspector.ts +749 -0
- package/src/development/typescript-utilities.ts +678 -0
- package/src/docs.ts +397 -0
- package/src/errors/circuit-breaker.ts +732 -0
- package/src/errors/error-handler.ts +569 -0
- package/src/errors/error-reporting.ts +672 -0
- package/src/errors/exceptions.ts +536 -0
- package/src/errors/graceful-degradation.ts +621 -0
- package/src/errors/index.ts +21 -0
- package/src/errors/router-errors.ts +632 -0
- package/src/file-serving/static-files.ts +581 -0
- package/src/index.ts +14 -0
- package/src/middleware/auth.ts +219 -0
- package/src/middleware/content_security_policy.ts +215 -0
- package/src/middleware/cors.ts +74 -0
- package/src/middleware/csrf.ts +108 -0
- package/src/middleware/ddos_protection.ts +255 -0
- package/src/middleware/file_security.ts +191 -0
- package/src/middleware/file_upload.ts +264 -0
- package/src/middleware/helmet.ts +268 -0
- package/src/middleware/index.ts +117 -0
- package/src/middleware/input_validation.ts +449 -0
- package/src/middleware/json_body.ts +37 -0
- package/src/middleware/performance_alerting.ts +538 -0
- package/src/middleware/performance_dashboard.ts +661 -0
- package/src/middleware/performance_monitor.ts +943 -0
- package/src/middleware/pipeline.ts +489 -0
- package/src/middleware/rate_limit.ts +245 -0
- package/src/middleware/request_id.ts +36 -0
- package/src/middleware/request_signing.ts +636 -0
- package/src/middleware/request_tracer.ts +636 -0
- package/src/middleware/response_cache.ts +743 -0
- package/src/middleware/security.ts +482 -0
- package/src/middleware/security_suite.ts +257 -0
- package/src/middleware/session.ts +91 -0
- package/src/model-binding/index.ts +18 -0
- package/src/model-binding/model-middleware.ts +425 -0
- package/src/model-binding/model-registry.ts +550 -0
- package/src/model-binding.ts +370 -0
- package/src/model-resolver-factory.ts +106 -0
- package/src/observability/correlation.ts +691 -0
- package/src/observability/health-checks.ts +729 -0
- package/src/observability/index.ts +184 -0
- package/src/observability/integration.ts +548 -0
- package/src/observability/metrics.ts +753 -0
- package/src/observability/tracing.ts +638 -0
- package/src/optimization/bun-utilities.ts +778 -0
- package/src/query-builder-integration.ts +137 -0
- package/src/request/context.ts +62 -0
- package/src/request/enhanced-request.ts +857 -0
- package/src/request/macros.ts +688 -0
- package/src/response/macros.ts +612 -0
- package/src/response/response-factory.ts +588 -0
- package/src/router/api-routes.ts +243 -0
- package/src/router/file-based-routing.ts +670 -0
- package/src/router/file-streaming.ts +372 -0
- package/src/router/fluent-router.ts +927 -0
- package/src/router/fluent-routing.ts +797 -0
- package/src/router/group-organization.ts +213 -0
- package/src/router/handler-resolver.ts +291 -0
- package/src/router/http-methods.ts +377 -0
- package/src/router/index.ts +185 -0
- package/src/router/middleware-groups.ts +222 -0
- package/src/router/middleware-integration.ts +399 -0
- package/src/router/middleware.ts +231 -0
- package/src/router/model-binding.ts +215 -0
- package/src/router/optimized-route-matching.ts +253 -0
- package/src/router/route-building.ts +212 -0
- package/src/router/route-compiler.ts +702 -0
- package/src/router/route-matching.ts +349 -0
- package/src/router/route-trie.ts +464 -0
- package/src/router/router.ts +1524 -0
- package/src/router/server.ts +429 -0
- package/src/router/validation-integration.ts +443 -0
- package/src/router/view-rendering.ts +233 -0
- package/src/router/websocket.ts +100 -0
- package/src/routing/route-caching.ts +402 -0
- package/src/routing/route-throttling.ts +469 -0
- package/src/routing/subdomain-routing.ts +492 -0
- package/src/session/database-store.ts +109 -0
- package/src/session/file-store.ts +148 -0
- package/src/session/index.ts +244 -0
- package/src/session/memory-store.ts +88 -0
- package/src/session/redis-store.ts +93 -0
- package/src/streaming/index.ts +17 -0
- package/src/streaming/sse-handler.ts +482 -0
- package/src/streaming/stream-handler.ts +552 -0
- package/src/testing/auth-testing.ts +446 -0
- package/src/testing/file-upload-testing.ts +543 -0
- package/src/testing/index.ts +10 -0
- package/src/testing/middleware-testing.ts +322 -0
- package/src/testing/model-binding-testing.ts +645 -0
- package/src/testing/performance-testing.ts +738 -0
- package/src/testing/test-client.ts +310 -0
- package/src/testing/test-request.ts +315 -0
- package/src/testing/test-response.ts +332 -0
- package/src/testing/types.ts +224 -0
- package/src/testing/websocket-testing.ts +590 -0
- package/src/types/controller-types.ts +385 -0
- package/src/types/core.ts +683 -0
- package/src/types/middleware-types.ts +419 -0
- package/src/types/request-response-augmentation.ts +489 -0
- package/src/types/route-inference.ts +357 -0
- package/src/types.ts +2049 -0
- package/src/url.ts +126 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/query-preservation.ts +201 -0
- package/src/utils.ts +327 -0
- package/src/validation/validator.ts +685 -0
- package/src/websocket/clustering.ts +762 -0
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fluent Routing Extensions
|
|
3
|
+
*
|
|
4
|
+
* Implements fluent routing patterns with full TypeScript support
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ActionHandler,
|
|
9
|
+
EnhancedRequest,
|
|
10
|
+
MiddlewareHandler,
|
|
11
|
+
Route,
|
|
12
|
+
RouteHandler,
|
|
13
|
+
} from '../types'
|
|
14
|
+
import type { Router } from './router'
|
|
15
|
+
import { registerNamedRoute } from '../url'
|
|
16
|
+
import { matchPath } from '../utils'
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Types
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Controller class interface
|
|
24
|
+
*/
|
|
25
|
+
export interface ControllerClass {
|
|
26
|
+
new (): Controller
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Base controller interface
|
|
31
|
+
*/
|
|
32
|
+
export interface Controller {
|
|
33
|
+
[key: string]: RouteHandler | unknown
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resource controller methods
|
|
38
|
+
*/
|
|
39
|
+
export type ResourceMethod = 'index' | 'create' | 'store' | 'show' | 'edit' | 'update' | 'destroy'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* API resource methods (no create/edit views)
|
|
43
|
+
*/
|
|
44
|
+
export type ApiResourceMethod = 'index' | 'store' | 'show' | 'update' | 'destroy'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Singleton resource methods
|
|
48
|
+
*/
|
|
49
|
+
export type SingletonMethod = 'show' | 'edit' | 'update'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resource configuration
|
|
53
|
+
*/
|
|
54
|
+
export interface ResourceOptions {
|
|
55
|
+
only?: ResourceMethod[]
|
|
56
|
+
except?: ResourceMethod[]
|
|
57
|
+
middleware?: (string | MiddlewareHandler)[]
|
|
58
|
+
names?: Partial<Record<ResourceMethod, string>>
|
|
59
|
+
parameters?: Record<string, string>
|
|
60
|
+
shallow?: boolean
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Route builder for fluent API
|
|
65
|
+
*/
|
|
66
|
+
export interface FluentRoute {
|
|
67
|
+
name: (name: string) => FluentRoute
|
|
68
|
+
middleware: (...middleware: (string | MiddlewareHandler)[]) => FluentRoute
|
|
69
|
+
withoutMiddleware: (...middleware: string[]) => FluentRoute
|
|
70
|
+
where: (param: string, pattern: string | RegExp) => FluentRoute
|
|
71
|
+
whereNumber: (param: string) => FluentRoute
|
|
72
|
+
whereAlpha: (param: string) => FluentRoute
|
|
73
|
+
whereAlphaNumeric: (param: string) => FluentRoute
|
|
74
|
+
whereUuid: (param: string) => FluentRoute
|
|
75
|
+
whereSlug: (param: string) => FluentRoute
|
|
76
|
+
can: (ability: string, model?: string) => FluentRoute
|
|
77
|
+
missing: (handler: (req: EnhancedRequest) => Response) => FluentRoute
|
|
78
|
+
withTrashed: () => FluentRoute
|
|
79
|
+
scopeBindings: () => FluentRoute
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Group builder for fluent API
|
|
84
|
+
*/
|
|
85
|
+
export interface GroupBuilder {
|
|
86
|
+
prefix: (prefix: string) => GroupBuilder
|
|
87
|
+
name: (name: string) => GroupBuilder
|
|
88
|
+
middleware: (...middleware: (string | MiddlewareHandler)[]) => GroupBuilder
|
|
89
|
+
domain: (domain: string) => GroupBuilder
|
|
90
|
+
controller: (controller: ControllerClass) => GroupBuilder
|
|
91
|
+
routes: (callback: (router: FluentRouter) => void) => void
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// Fluent Router Implementation
|
|
96
|
+
// ============================================================================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Fluent router with chainable API
|
|
100
|
+
*/
|
|
101
|
+
export class FluentRouter {
|
|
102
|
+
private router: Router
|
|
103
|
+
private currentPrefix: string = ''
|
|
104
|
+
private currentNamePrefix: string = ''
|
|
105
|
+
private currentMiddleware: (string | MiddlewareHandler)[] = []
|
|
106
|
+
private currentDomain: string | null = null
|
|
107
|
+
private currentController: ControllerClass | null = null
|
|
108
|
+
private excludedMiddleware: Set<string> = new Set()
|
|
109
|
+
|
|
110
|
+
constructor(router: Router) {
|
|
111
|
+
this.router = router
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// HTTP Methods
|
|
116
|
+
// ============================================================================
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Register a GET route
|
|
120
|
+
*/
|
|
121
|
+
get(path: string, handler: RouteHandler | string | Response): FluentRouteBuilder {
|
|
122
|
+
return this.addRoute('GET', path, handler)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Register a POST route
|
|
127
|
+
*/
|
|
128
|
+
post(path: string, handler: RouteHandler | string): FluentRouteBuilder {
|
|
129
|
+
return this.addRoute('POST', path, handler)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Register a PUT route
|
|
134
|
+
*/
|
|
135
|
+
put(path: string, handler: RouteHandler | string): FluentRouteBuilder {
|
|
136
|
+
return this.addRoute('PUT', path, handler)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Register a PATCH route
|
|
141
|
+
*/
|
|
142
|
+
patch(path: string, handler: RouteHandler | string): FluentRouteBuilder {
|
|
143
|
+
return this.addRoute('PATCH', path, handler)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Register a DELETE route
|
|
148
|
+
*/
|
|
149
|
+
delete(path: string, handler: RouteHandler | string): FluentRouteBuilder {
|
|
150
|
+
return this.addRoute('DELETE', path, handler)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Register an OPTIONS route
|
|
155
|
+
*/
|
|
156
|
+
options(path: string, handler: RouteHandler | string): FluentRouteBuilder {
|
|
157
|
+
return this.addRoute('OPTIONS', path, handler)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Register a route for any HTTP method
|
|
162
|
+
*/
|
|
163
|
+
any(path: string, handler: RouteHandler | string): FluentRouteBuilder {
|
|
164
|
+
return this.addRoute('*', path, handler)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Register a route for multiple HTTP methods
|
|
169
|
+
*/
|
|
170
|
+
match(methods: string[], path: string, handler: RouteHandler | string): FluentRouteBuilder {
|
|
171
|
+
const fullPath = this.buildPath(path)
|
|
172
|
+
const resolvedHandler = this.resolveHandler(handler)
|
|
173
|
+
const middleware = this.buildMiddleware()
|
|
174
|
+
|
|
175
|
+
for (const method of methods) {
|
|
176
|
+
const route: Route = {
|
|
177
|
+
path: fullPath,
|
|
178
|
+
method: method.toUpperCase(),
|
|
179
|
+
handler: resolvedHandler,
|
|
180
|
+
middleware,
|
|
181
|
+
domain: this.currentDomain || undefined,
|
|
182
|
+
pattern: this.createPattern(fullPath),
|
|
183
|
+
}
|
|
184
|
+
// Use direct push to routes array to avoid conflict with http-methods.ts addRoute
|
|
185
|
+
this.router.routes.push(route)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return new FluentRouteBuilder(this.router, fullPath, methods[0])
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// Resource Routes
|
|
193
|
+
// ============================================================================
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Register a resource controller
|
|
197
|
+
*/
|
|
198
|
+
resource(name: string, Controller: ControllerClass, options: ResourceOptions = {}): this {
|
|
199
|
+
const methods = this.getResourceMethods(options)
|
|
200
|
+
const paramName = options.parameters?.[name] || 'id'
|
|
201
|
+
|
|
202
|
+
const controllerInstance = new Controller()
|
|
203
|
+
|
|
204
|
+
for (const method of methods) {
|
|
205
|
+
const { httpMethod, path, handlerName } = this.getResourceRoute(name, method, paramName)
|
|
206
|
+
const handler = controllerInstance[handlerName] as RouteHandler | undefined
|
|
207
|
+
|
|
208
|
+
if (handler) {
|
|
209
|
+
const fullPath = this.buildPath(path)
|
|
210
|
+
const routeName = options.names?.[method] || `${name}.${method}`
|
|
211
|
+
|
|
212
|
+
const route: Route = {
|
|
213
|
+
path: fullPath,
|
|
214
|
+
method: httpMethod,
|
|
215
|
+
handler,
|
|
216
|
+
middleware: this.buildMiddleware(options.middleware),
|
|
217
|
+
name: this.currentNamePrefix + routeName,
|
|
218
|
+
domain: this.currentDomain || undefined,
|
|
219
|
+
pattern: this.createPattern(fullPath),
|
|
220
|
+
}
|
|
221
|
+
// Use direct push to routes array to avoid conflict with http-methods.ts addRoute
|
|
222
|
+
this.router.routes.push(route)
|
|
223
|
+
if (route.name) {
|
|
224
|
+
this.router.namedRoutes.set(route.name, route)
|
|
225
|
+
registerNamedRoute(route.name, route.path)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return this
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Register an API resource controller (no create/edit views)
|
|
235
|
+
*/
|
|
236
|
+
apiResource(name: string, controller: ControllerClass, options: ResourceOptions = {}): this {
|
|
237
|
+
const apiMethods: ApiResourceMethod[] = ['index', 'store', 'show', 'update', 'destroy']
|
|
238
|
+
const filteredOptions: ResourceOptions = {
|
|
239
|
+
...options,
|
|
240
|
+
only: options.only?.filter((m): m is ResourceMethod => apiMethods.includes(m as ApiResourceMethod)) || apiMethods,
|
|
241
|
+
}
|
|
242
|
+
return this.resource(name, controller, filteredOptions)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Register a singleton resource (no index, create, store, destroy)
|
|
247
|
+
*/
|
|
248
|
+
singleton(name: string, Controller: ControllerClass, options: ResourceOptions = {}): this {
|
|
249
|
+
const singletonMethods: SingletonMethod[] = ['show', 'edit', 'update']
|
|
250
|
+
const controllerInstance = new Controller()
|
|
251
|
+
|
|
252
|
+
for (const method of singletonMethods) {
|
|
253
|
+
if (options.except?.includes(method))
|
|
254
|
+
continue
|
|
255
|
+
if (options.only && !options.only.includes(method))
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
const { httpMethod, handlerName } = this.getSingletonRoute(name, method)
|
|
259
|
+
const handler = controllerInstance[handlerName] as RouteHandler | undefined
|
|
260
|
+
|
|
261
|
+
if (handler) {
|
|
262
|
+
const fullPath = this.buildPath(`/${name}${method === 'edit' ? '/edit' : ''}`)
|
|
263
|
+
const routeName = options.names?.[method] || `${name}.${method}`
|
|
264
|
+
|
|
265
|
+
const route: Route = {
|
|
266
|
+
path: fullPath,
|
|
267
|
+
method: httpMethod,
|
|
268
|
+
handler,
|
|
269
|
+
middleware: this.buildMiddleware(options.middleware),
|
|
270
|
+
name: this.currentNamePrefix + routeName,
|
|
271
|
+
domain: this.currentDomain || undefined,
|
|
272
|
+
pattern: this.createPattern(fullPath),
|
|
273
|
+
}
|
|
274
|
+
// Use direct push to routes array to avoid conflict with http-methods.ts addRoute
|
|
275
|
+
this.router.routes.push(route)
|
|
276
|
+
if (route.name) {
|
|
277
|
+
this.router.namedRoutes.set(route.name, route)
|
|
278
|
+
registerNamedRoute(route.name, route.path)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return this
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ============================================================================
|
|
287
|
+
// Group Methods
|
|
288
|
+
// ============================================================================
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Create a route group with prefix
|
|
292
|
+
*/
|
|
293
|
+
prefix(prefix: string): GroupBuilderImpl {
|
|
294
|
+
return new GroupBuilderImpl(this).prefix(prefix)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Create a route group with name prefix
|
|
299
|
+
*/
|
|
300
|
+
name(namePrefix: string): GroupBuilderImpl {
|
|
301
|
+
return new GroupBuilderImpl(this).name(namePrefix)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Create a route group with middleware
|
|
306
|
+
*/
|
|
307
|
+
middleware(...middleware: (string | MiddlewareHandler)[]): GroupBuilderImpl {
|
|
308
|
+
return new GroupBuilderImpl(this).middleware(...middleware)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Create a route group for a domain
|
|
313
|
+
*/
|
|
314
|
+
domain(domain: string): GroupBuilderImpl {
|
|
315
|
+
return new GroupBuilderImpl(this).domain(domain)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Create a route group with a controller
|
|
320
|
+
*/
|
|
321
|
+
controller(controller: ControllerClass): GroupBuilderImpl {
|
|
322
|
+
return new GroupBuilderImpl(this).controller(controller)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Create a route group
|
|
327
|
+
*/
|
|
328
|
+
group(options: GroupOptions, callback: (router: FluentRouter) => void): this {
|
|
329
|
+
const previousPrefix = this.currentPrefix
|
|
330
|
+
const previousNamePrefix = this.currentNamePrefix
|
|
331
|
+
const previousMiddleware = [...this.currentMiddleware]
|
|
332
|
+
const previousDomain = this.currentDomain
|
|
333
|
+
const previousController = this.currentController
|
|
334
|
+
|
|
335
|
+
if (options.prefix)
|
|
336
|
+
this.currentPrefix = this.buildPath(options.prefix)
|
|
337
|
+
if (options.as)
|
|
338
|
+
this.currentNamePrefix = this.currentNamePrefix + options.as
|
|
339
|
+
if (options.middleware)
|
|
340
|
+
this.currentMiddleware.push(...options.middleware)
|
|
341
|
+
if (options.domain)
|
|
342
|
+
this.currentDomain = options.domain
|
|
343
|
+
if (options.controller)
|
|
344
|
+
this.currentController = options.controller
|
|
345
|
+
|
|
346
|
+
callback(this)
|
|
347
|
+
|
|
348
|
+
this.currentPrefix = previousPrefix
|
|
349
|
+
this.currentNamePrefix = previousNamePrefix
|
|
350
|
+
this.currentMiddleware = previousMiddleware
|
|
351
|
+
this.currentDomain = previousDomain
|
|
352
|
+
this.currentController = previousController
|
|
353
|
+
|
|
354
|
+
return this
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// ============================================================================
|
|
358
|
+
// Route Information
|
|
359
|
+
// ============================================================================
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Check if a named route exists
|
|
363
|
+
*/
|
|
364
|
+
has(name: string): boolean {
|
|
365
|
+
return this.router.namedRoutes.has(name)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get the current route (from request context)
|
|
370
|
+
*/
|
|
371
|
+
current(): Route | null {
|
|
372
|
+
// This would need to be set during request handling
|
|
373
|
+
return null
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get the current route name
|
|
378
|
+
*/
|
|
379
|
+
currentRouteName(): string | null {
|
|
380
|
+
const route = this.current()
|
|
381
|
+
return route?.name || null
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get URL for a named route
|
|
386
|
+
*/
|
|
387
|
+
route(name: string, params: Record<string, string | number> = {}): string {
|
|
388
|
+
const route = this.router.namedRoutes.get(name)
|
|
389
|
+
if (!route) {
|
|
390
|
+
throw new Error(`Route [${name}] not defined.`)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
let url = route.path
|
|
394
|
+
for (const [key, value] of Object.entries(params)) {
|
|
395
|
+
url = url.replace(`{${key}}`, String(value))
|
|
396
|
+
url = url.replace(`:${key}`, String(value))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return url
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ============================================================================
|
|
403
|
+
// Fallback & Error Handling
|
|
404
|
+
// ============================================================================
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Register a fallback route
|
|
408
|
+
*/
|
|
409
|
+
fallback(handler: RouteHandler): this {
|
|
410
|
+
this.router.fallbackHandler = handler
|
|
411
|
+
return this
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ============================================================================
|
|
415
|
+
// Private Methods
|
|
416
|
+
// ============================================================================
|
|
417
|
+
|
|
418
|
+
private addRoute(method: string, path: string, handler: RouteHandler | string | Response): FluentRouteBuilder {
|
|
419
|
+
const fullPath = this.buildPath(path)
|
|
420
|
+
|
|
421
|
+
// If handler is a Response object, register for Bun's native static dispatch
|
|
422
|
+
if (handler instanceof Response) {
|
|
423
|
+
this.router.staticResponses.set(fullPath, handler)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const resolvedHandler = handler instanceof Response ? handler : this.resolveHandler(handler)
|
|
427
|
+
const middleware = this.buildMiddleware()
|
|
428
|
+
|
|
429
|
+
const route: Route = {
|
|
430
|
+
path: fullPath,
|
|
431
|
+
method: method.toUpperCase(),
|
|
432
|
+
handler: resolvedHandler,
|
|
433
|
+
middleware,
|
|
434
|
+
domain: this.currentDomain || undefined,
|
|
435
|
+
pattern: this.createPattern(fullPath),
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Use direct push to routes array to avoid conflict with http-methods.ts addRoute
|
|
439
|
+
this.router.routes.push(route)
|
|
440
|
+
if (route.name) {
|
|
441
|
+
this.router.namedRoutes.set(route.name, route)
|
|
442
|
+
registerNamedRoute(route.name, route.path)
|
|
443
|
+
}
|
|
444
|
+
return new FluentRouteBuilder(this.router, fullPath, method)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Create a pattern object for route matching
|
|
449
|
+
*/
|
|
450
|
+
private createPattern(routePath: string): { exec: (url: URL) => { pathname: { groups: Record<string, string> } } | null } {
|
|
451
|
+
return {
|
|
452
|
+
exec: (url: URL): { pathname: { groups: Record<string, string> } } | null => {
|
|
453
|
+
const params: Record<string, string> = {}
|
|
454
|
+
const isMatch = matchPath(routePath, url.pathname, params)
|
|
455
|
+
|
|
456
|
+
if (!isMatch) {
|
|
457
|
+
return null
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
pathname: {
|
|
462
|
+
groups: params,
|
|
463
|
+
},
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private buildPath(path: string): string {
|
|
470
|
+
if (this.currentPrefix) {
|
|
471
|
+
return `${this.currentPrefix}${path.startsWith('/') ? path : `/${path}`}`
|
|
472
|
+
}
|
|
473
|
+
return path.startsWith('/') ? path : `/${path}`
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private buildMiddleware(additional?: (string | MiddlewareHandler)[]): MiddlewareHandler[] {
|
|
477
|
+
const allMiddleware = [...this.currentMiddleware, ...(additional || [])]
|
|
478
|
+
return allMiddleware
|
|
479
|
+
.filter(m => typeof m !== 'string' || !this.excludedMiddleware.has(m))
|
|
480
|
+
.map(m => this.resolveMiddleware(m))
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private resolveHandler(handler: RouteHandler | string): ActionHandler {
|
|
484
|
+
if (typeof handler === 'string') {
|
|
485
|
+
if (this.currentController) {
|
|
486
|
+
const ControllerClass = this.currentController
|
|
487
|
+
const controllerInstance = new ControllerClass()
|
|
488
|
+
const method = controllerInstance[handler] as RouteHandler | undefined
|
|
489
|
+
if (method) {
|
|
490
|
+
return method.bind(controllerInstance)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return handler as ActionHandler
|
|
494
|
+
}
|
|
495
|
+
return handler
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private resolveMiddleware(middleware: string | MiddlewareHandler): MiddlewareHandler {
|
|
499
|
+
if (typeof middleware === 'function') {
|
|
500
|
+
return middleware
|
|
501
|
+
}
|
|
502
|
+
// String middleware would be resolved from named middleware registry
|
|
503
|
+
return async (_req, next) => next()
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private getResourceMethods(options: ResourceOptions): ResourceMethod[] {
|
|
507
|
+
const allMethods: ResourceMethod[] = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy']
|
|
508
|
+
|
|
509
|
+
if (options.only) {
|
|
510
|
+
return options.only
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (options.except) {
|
|
514
|
+
return allMethods.filter(m => !options.except!.includes(m))
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return allMethods
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private getResourceRoute(name: string, method: ResourceMethod, paramName: string): { httpMethod: string, path: string, handlerName: string } {
|
|
521
|
+
const routes: Record<ResourceMethod, { httpMethod: string, path: string, handlerName: string }> = {
|
|
522
|
+
index: { httpMethod: 'GET', path: `/${name}`, handlerName: 'index' },
|
|
523
|
+
create: { httpMethod: 'GET', path: `/${name}/create`, handlerName: 'create' },
|
|
524
|
+
store: { httpMethod: 'POST', path: `/${name}`, handlerName: 'store' },
|
|
525
|
+
show: { httpMethod: 'GET', path: `/${name}/{${paramName}}`, handlerName: 'show' },
|
|
526
|
+
edit: { httpMethod: 'GET', path: `/${name}/{${paramName}}/edit`, handlerName: 'edit' },
|
|
527
|
+
update: { httpMethod: 'PUT', path: `/${name}/{${paramName}}`, handlerName: 'update' },
|
|
528
|
+
destroy: { httpMethod: 'DELETE', path: `/${name}/{${paramName}}`, handlerName: 'destroy' },
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return routes[method]
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
private getSingletonRoute(name: string, method: SingletonMethod): { httpMethod: string, handlerName: string } {
|
|
535
|
+
const routes: Record<SingletonMethod, { httpMethod: string, handlerName: string }> = {
|
|
536
|
+
show: { httpMethod: 'GET', handlerName: 'show' },
|
|
537
|
+
edit: { httpMethod: 'GET', handlerName: 'edit' },
|
|
538
|
+
update: { httpMethod: 'PUT', handlerName: 'update' },
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return routes[method]
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// ============================================================================
|
|
546
|
+
// Fluent Route Builder
|
|
547
|
+
// ============================================================================
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Fluent route builder for chaining route configuration
|
|
551
|
+
*/
|
|
552
|
+
export class FluentRouteBuilder implements FluentRoute {
|
|
553
|
+
private router: Router
|
|
554
|
+
private path: string
|
|
555
|
+
private method: string
|
|
556
|
+
private routeName: string | null = null
|
|
557
|
+
private routeMiddleware: MiddlewareHandler[] = []
|
|
558
|
+
private excludedMiddleware: string[] = []
|
|
559
|
+
private constraints: Record<string, string | RegExp> = {}
|
|
560
|
+
private missingHandler: ((_req: EnhancedRequest) => Response) | null = null
|
|
561
|
+
private includeTrashed: boolean = false
|
|
562
|
+
private scopeBindingsEnabled: boolean = false
|
|
563
|
+
private ability: string | null = null
|
|
564
|
+
private abilityModel: string | null = null
|
|
565
|
+
|
|
566
|
+
constructor(router: Router, path: string, method: string) {
|
|
567
|
+
this.router = router
|
|
568
|
+
this.path = path
|
|
569
|
+
this.method = method
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Set the route name
|
|
574
|
+
*/
|
|
575
|
+
name(name: string): this {
|
|
576
|
+
this.routeName = name
|
|
577
|
+
// Update the route in the router
|
|
578
|
+
const route = this.findRoute()
|
|
579
|
+
if (route) {
|
|
580
|
+
route.name = name
|
|
581
|
+
this.router.namedRoutes.set(name, route)
|
|
582
|
+
registerNamedRoute(name, route.path)
|
|
583
|
+
}
|
|
584
|
+
return this
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Add middleware to the route
|
|
589
|
+
*/
|
|
590
|
+
middleware(...middleware: (string | MiddlewareHandler)[]): this {
|
|
591
|
+
const route = this.findRoute()
|
|
592
|
+
if (route) {
|
|
593
|
+
for (const m of middleware) {
|
|
594
|
+
if (typeof m === 'function') {
|
|
595
|
+
route.middleware.push(m)
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return this
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Exclude middleware from the route
|
|
604
|
+
*/
|
|
605
|
+
withoutMiddleware(...middleware: string[]): this {
|
|
606
|
+
this.excludedMiddleware.push(...middleware)
|
|
607
|
+
const route = this.findRoute()
|
|
608
|
+
if (route) {
|
|
609
|
+
route.middleware = route.middleware.filter((m) => {
|
|
610
|
+
const name = m.name || ''
|
|
611
|
+
return !this.excludedMiddleware.includes(name)
|
|
612
|
+
})
|
|
613
|
+
}
|
|
614
|
+
return this
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Add a parameter constraint
|
|
619
|
+
*/
|
|
620
|
+
where(param: string, pattern: string | RegExp): this {
|
|
621
|
+
this.constraints[param] = pattern
|
|
622
|
+
const route = this.findRoute()
|
|
623
|
+
if (route) {
|
|
624
|
+
route.constraints = { ...route.constraints, [param]: String(pattern) }
|
|
625
|
+
}
|
|
626
|
+
return this
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Constrain parameter to numbers
|
|
631
|
+
*/
|
|
632
|
+
whereNumber(param: string): this {
|
|
633
|
+
return this.where(param, '[0-9]+')
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Constrain parameter to letters
|
|
638
|
+
*/
|
|
639
|
+
whereAlpha(param: string): this {
|
|
640
|
+
return this.where(param, '[a-zA-Z]+')
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Constrain parameter to alphanumeric
|
|
645
|
+
*/
|
|
646
|
+
whereAlphaNumeric(param: string): this {
|
|
647
|
+
return this.where(param, '[a-zA-Z0-9]+')
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Constrain parameter to UUID
|
|
652
|
+
*/
|
|
653
|
+
whereUuid(param: string): this {
|
|
654
|
+
return this.where(param, '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}')
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Constrain parameter to slug format
|
|
659
|
+
*/
|
|
660
|
+
whereSlug(param: string): this {
|
|
661
|
+
return this.where(param, '[a-z0-9]+(?:-[a-z0-9]+)*')
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Add authorization check
|
|
666
|
+
*/
|
|
667
|
+
can(ability: string, model?: string): this {
|
|
668
|
+
this.ability = ability
|
|
669
|
+
this.abilityModel = model || null
|
|
670
|
+
// Add authorization middleware
|
|
671
|
+
const authMiddleware: MiddlewareHandler = async (req, next) => {
|
|
672
|
+
// Check if user can perform ability
|
|
673
|
+
const user = req.user
|
|
674
|
+
if (!user) {
|
|
675
|
+
return new Response('Unauthorized', { status: 401 })
|
|
676
|
+
}
|
|
677
|
+
// Authorization logic would go here
|
|
678
|
+
return next()
|
|
679
|
+
}
|
|
680
|
+
const route = this.findRoute()
|
|
681
|
+
if (route) {
|
|
682
|
+
route.middleware.push(authMiddleware)
|
|
683
|
+
}
|
|
684
|
+
return this
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Handle missing model binding
|
|
689
|
+
*/
|
|
690
|
+
missing(handler: (req: EnhancedRequest) => Response): this {
|
|
691
|
+
this.missingHandler = handler
|
|
692
|
+
return this
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Include soft-deleted models
|
|
697
|
+
*/
|
|
698
|
+
withTrashed(): this {
|
|
699
|
+
this.includeTrashed = true
|
|
700
|
+
return this
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Enable scoped bindings
|
|
705
|
+
*/
|
|
706
|
+
scopeBindings(): this {
|
|
707
|
+
this.scopeBindingsEnabled = true
|
|
708
|
+
return this
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private findRoute(): Route | undefined {
|
|
712
|
+
return this.router.routes.find(r =>
|
|
713
|
+
r.path === this.path && r.method === this.method.toUpperCase(),
|
|
714
|
+
)
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ============================================================================
|
|
719
|
+
// Group Builder Implementation
|
|
720
|
+
// ============================================================================
|
|
721
|
+
|
|
722
|
+
interface GroupOptions {
|
|
723
|
+
prefix?: string
|
|
724
|
+
as?: string
|
|
725
|
+
middleware?: (string | MiddlewareHandler)[]
|
|
726
|
+
domain?: string
|
|
727
|
+
controller?: ControllerClass
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
class GroupBuilderImpl implements GroupBuilder {
|
|
731
|
+
private fluentRouter: FluentRouter
|
|
732
|
+
private options: GroupOptions = {}
|
|
733
|
+
|
|
734
|
+
constructor(router: FluentRouter) {
|
|
735
|
+
this.fluentRouter = router
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
prefix(prefix: string): this {
|
|
739
|
+
this.options.prefix = prefix
|
|
740
|
+
return this
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
name(name: string): this {
|
|
744
|
+
this.options.as = name
|
|
745
|
+
return this
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
middleware(...middleware: (string | MiddlewareHandler)[]): this {
|
|
749
|
+
this.options.middleware = [...(this.options.middleware || []), ...middleware]
|
|
750
|
+
return this
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
domain(domain: string): this {
|
|
754
|
+
this.options.domain = domain
|
|
755
|
+
return this
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
controller(controller: ControllerClass): this {
|
|
759
|
+
this.options.controller = controller
|
|
760
|
+
return this
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
routes(callback: (router: FluentRouter) => void): void {
|
|
764
|
+
this.fluentRouter.group(this.options, callback)
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// ============================================================================
|
|
769
|
+
// Router Extension
|
|
770
|
+
// ============================================================================
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Extend Router with fluent routing methods
|
|
774
|
+
*/
|
|
775
|
+
export function registerFluentRouting(RouterClass: typeof import('./router').Router): void {
|
|
776
|
+
const proto = RouterClass.prototype as Router & {
|
|
777
|
+
fluent?: () => FluentRouter
|
|
778
|
+
resourceController?: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router
|
|
779
|
+
apiResourceController?: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router
|
|
780
|
+
singletonResource?: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router
|
|
781
|
+
hasRoute?: (name: string) => boolean
|
|
782
|
+
currentRoute?: () => Route | null
|
|
783
|
+
currentRouteName?: () => string | null
|
|
784
|
+
routeUrl?: (name: string, params?: Record<string, string | number>) => string
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Get fluent router instance
|
|
789
|
+
*/
|
|
790
|
+
proto.fluent = function (this: Router): FluentRouter {
|
|
791
|
+
return new FluentRouter(this)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Register a controller-based resource
|
|
796
|
+
*/
|
|
797
|
+
proto.resourceController = function (
|
|
798
|
+
this: Router,
|
|
799
|
+
name: string,
|
|
800
|
+
controller: ControllerClass,
|
|
801
|
+
options?: ResourceOptions,
|
|
802
|
+
): Router {
|
|
803
|
+
new FluentRouter(this).resource(name, controller, options)
|
|
804
|
+
return this
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Register an API resource controller
|
|
809
|
+
*/
|
|
810
|
+
proto.apiResourceController = function (
|
|
811
|
+
this: Router,
|
|
812
|
+
name: string,
|
|
813
|
+
controller: ControllerClass,
|
|
814
|
+
options?: ResourceOptions,
|
|
815
|
+
): Router {
|
|
816
|
+
new FluentRouter(this).apiResource(name, controller, options)
|
|
817
|
+
return this
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Register a singleton resource
|
|
822
|
+
*/
|
|
823
|
+
proto.singletonResource = function (
|
|
824
|
+
this: Router,
|
|
825
|
+
name: string,
|
|
826
|
+
controller: ControllerClass,
|
|
827
|
+
options?: ResourceOptions,
|
|
828
|
+
): Router {
|
|
829
|
+
new FluentRouter(this).singleton(name, controller, options)
|
|
830
|
+
return this
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* Check if a named route exists
|
|
835
|
+
*/
|
|
836
|
+
proto.hasRoute = function (this: Router, name: string): boolean {
|
|
837
|
+
return this.namedRoutes.has(name)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Get the current route
|
|
842
|
+
*/
|
|
843
|
+
// eslint-disable-next-line pickier/no-unused-vars
|
|
844
|
+
proto.currentRoute = function (this: Router): Route | null {
|
|
845
|
+
// Would need request context
|
|
846
|
+
return null
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Get the current route name
|
|
851
|
+
*/
|
|
852
|
+
proto.currentRouteName = function (this: Router): string | null {
|
|
853
|
+
const route = this.currentRoute?.()
|
|
854
|
+
return route?.name || null
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Get URL for a named route
|
|
859
|
+
*/
|
|
860
|
+
proto.routeUrl = function (
|
|
861
|
+
this: Router,
|
|
862
|
+
name: string,
|
|
863
|
+
params: Record<string, string | number> = {},
|
|
864
|
+
): string {
|
|
865
|
+
const route = this.namedRoutes.get(name)
|
|
866
|
+
if (!route) {
|
|
867
|
+
throw new Error(`Route [${name}] not defined.`)
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
let url = route.path
|
|
871
|
+
for (const [key, value] of Object.entries(params)) {
|
|
872
|
+
url = url.replace(`{${key}}`, String(value))
|
|
873
|
+
url = url.replace(`:${key}`, String(value))
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
return url
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// ============================================================================
|
|
881
|
+
// Type Augmentation
|
|
882
|
+
// ============================================================================
|
|
883
|
+
|
|
884
|
+
declare module './router' {
|
|
885
|
+
interface Router {
|
|
886
|
+
/**
|
|
887
|
+
* Get fluent router instance
|
|
888
|
+
*/
|
|
889
|
+
fluent: () => FluentRouter
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* Register a controller-based resource
|
|
893
|
+
* Note: Use `resourceController` to avoid conflict with existing `resource` method
|
|
894
|
+
*/
|
|
895
|
+
resourceController: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Register an API resource controller (no create/edit views)
|
|
899
|
+
*/
|
|
900
|
+
apiResourceController: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Register a singleton resource
|
|
904
|
+
*/
|
|
905
|
+
singletonResource: (name: string, controller: ControllerClass, options?: ResourceOptions) => Router
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Check if a named route exists
|
|
909
|
+
*/
|
|
910
|
+
hasRoute: (name: string) => boolean
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Get the current route
|
|
914
|
+
*/
|
|
915
|
+
currentRoute: () => Route | null
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Get the current route name
|
|
919
|
+
*/
|
|
920
|
+
currentRouteName: () => string | null
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Get URL for a named route
|
|
924
|
+
*/
|
|
925
|
+
routeUrl: (name: string, params?: Record<string, string | number>) => string
|
|
926
|
+
}
|
|
927
|
+
}
|