@stacksjs/bun-router 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/container/service-provider.d.ts +3 -3
- package/dist/development/performance-profiler.d.ts +3 -3
- package/dist/development/route-debugger.d.ts +1 -1
- package/dist/development/route-inspector.d.ts +2 -2
- package/dist/observability/integration.d.ts +1 -1
- package/dist/response/macros.d.ts +1 -1
- package/dist/router/index.d.ts +7 -7
- package/dist/router/router.d.ts +0 -32
- package/dist/testing/performance-testing.d.ts +2 -2
- package/dist/testing/test-client.d.ts +1 -1
- package/dist/testing/websocket-testing.d.ts +1 -1
- package/package.json +2 -3
- package/src/auth.ts +0 -489
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -786
- package/src/container/contextual-binding.ts +0 -606
- package/src/container/decorators.ts +0 -368
- package/src/container/index.ts +0 -49
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -733
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -623
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -718
- package/src/index.ts +0 -66
- package/src/middleware/auth.ts +0 -220
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -142
- package/src/middleware/csrf.ts +0 -166
- package/src/middleware/ddos_protection.ts +0 -279
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -275
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -453
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -540
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -945
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -251
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -638
- package/src/middleware/request_tracer.ts +0 -653
- package/src/middleware/response_cache.ts +0 -778
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -109
- package/src/request/enhanced-request.ts +0 -876
- package/src/request/macros.ts +0 -822
- package/src/response/macros.ts +0 -665
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -697
- package/src/router/file-streaming.ts +0 -383
- package/src/router/fluent-routing.ts +0 -866
- package/src/router/group-organization.ts +0 -229
- package/src/router/handler-resolver.ts +0 -327
- package/src/router/http-methods.ts +0 -407
- package/src/router/index.ts +0 -193
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -234
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -302
- package/src/router/route-building.ts +0 -221
- package/src/router/route-compiler.ts +0 -691
- package/src/router/route-matching.ts +0 -368
- package/src/router/route-trie.ts +0 -450
- package/src/router/router.ts +0 -1616
- package/src/router/server.ts +0 -684
- package/src/router/validation-integration.ts +0 -449
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -448
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -422
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -372
- package/src/types.ts +0 -2123
- package/src/url.ts +0 -131
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -395
- package/src/validation/validator.ts +0 -688
- 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 }
|