@stacksjs/bun-router 0.0.17 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/container/service-provider.d.ts +3 -3
  3. package/dist/development/performance-profiler.d.ts +3 -3
  4. package/dist/development/route-debugger.d.ts +1 -1
  5. package/dist/development/route-inspector.d.ts +2 -2
  6. package/dist/observability/integration.d.ts +1 -1
  7. package/dist/response/macros.d.ts +1 -1
  8. package/dist/router/index.d.ts +7 -7
  9. package/dist/router/router.d.ts +0 -32
  10. package/dist/testing/performance-testing.d.ts +2 -2
  11. package/dist/testing/test-client.d.ts +1 -1
  12. package/dist/testing/websocket-testing.d.ts +1 -1
  13. package/package.json +2 -3
  14. package/src/auth.ts +0 -489
  15. package/src/cache/lru-cache.ts +0 -457
  16. package/src/cache/middleware-memoization.ts +0 -531
  17. package/src/cache/route-cache-warmer.ts +0 -486
  18. package/src/cache/sqlite-cache.ts +0 -783
  19. package/src/cache/streaming-cache.ts +0 -572
  20. package/src/cli/colors.ts +0 -29
  21. package/src/cli/index.ts +0 -287
  22. package/src/cli/middleware.ts +0 -291
  23. package/src/cli/openapi.ts +0 -407
  24. package/src/cli/router.ts +0 -188
  25. package/src/cli/routes.ts +0 -265
  26. package/src/cli/utils.ts +0 -531
  27. package/src/cli.ts +0 -5
  28. package/src/config.ts +0 -322
  29. package/src/container/container.ts +0 -786
  30. package/src/container/contextual-binding.ts +0 -606
  31. package/src/container/decorators.ts +0 -368
  32. package/src/container/index.ts +0 -49
  33. package/src/container/service-provider.ts +0 -596
  34. package/src/development/hot-reload.ts +0 -673
  35. package/src/development/index.ts +0 -499
  36. package/src/development/performance-profiler.ts +0 -717
  37. package/src/development/route-debugger.ts +0 -527
  38. package/src/development/route-inspector.ts +0 -749
  39. package/src/development/typescript-utilities.ts +0 -682
  40. package/src/docs.ts +0 -397
  41. package/src/errors/circuit-breaker.ts +0 -733
  42. package/src/errors/error-handler.ts +0 -569
  43. package/src/errors/error-reporting.ts +0 -672
  44. package/src/errors/exceptions.ts +0 -536
  45. package/src/errors/graceful-degradation.ts +0 -623
  46. package/src/errors/index.ts +0 -21
  47. package/src/errors/router-errors.ts +0 -632
  48. package/src/file-serving/static-files.ts +0 -718
  49. package/src/index.ts +0 -66
  50. package/src/middleware/auth.ts +0 -220
  51. package/src/middleware/content_security_policy.ts +0 -215
  52. package/src/middleware/cors.ts +0 -142
  53. package/src/middleware/csrf.ts +0 -166
  54. package/src/middleware/ddos_protection.ts +0 -279
  55. package/src/middleware/file_security.ts +0 -191
  56. package/src/middleware/file_upload.ts +0 -275
  57. package/src/middleware/helmet.ts +0 -268
  58. package/src/middleware/index.ts +0 -117
  59. package/src/middleware/input_validation.ts +0 -453
  60. package/src/middleware/json_body.ts +0 -37
  61. package/src/middleware/performance_alerting.ts +0 -540
  62. package/src/middleware/performance_dashboard.ts +0 -661
  63. package/src/middleware/performance_monitor.ts +0 -945
  64. package/src/middleware/pipeline.ts +0 -489
  65. package/src/middleware/rate_limit.ts +0 -251
  66. package/src/middleware/request_id.ts +0 -36
  67. package/src/middleware/request_signing.ts +0 -638
  68. package/src/middleware/request_tracer.ts +0 -653
  69. package/src/middleware/response_cache.ts +0 -778
  70. package/src/middleware/security.ts +0 -482
  71. package/src/middleware/security_suite.ts +0 -257
  72. package/src/middleware/session.ts +0 -91
  73. package/src/model-binding/index.ts +0 -18
  74. package/src/model-binding/model-middleware.ts +0 -425
  75. package/src/model-binding/model-registry.ts +0 -550
  76. package/src/model-binding.ts +0 -370
  77. package/src/model-resolver-factory.ts +0 -106
  78. package/src/observability/correlation.ts +0 -691
  79. package/src/observability/health-checks.ts +0 -729
  80. package/src/observability/index.ts +0 -184
  81. package/src/observability/integration.ts +0 -548
  82. package/src/observability/metrics.ts +0 -753
  83. package/src/observability/tracing.ts +0 -638
  84. package/src/optimization/bun-utilities.ts +0 -778
  85. package/src/query-builder-integration.ts +0 -137
  86. package/src/request/context.ts +0 -109
  87. package/src/request/enhanced-request.ts +0 -876
  88. package/src/request/macros.ts +0 -822
  89. package/src/response/macros.ts +0 -665
  90. package/src/response/response-factory.ts +0 -596
  91. package/src/router/api-routes.ts +0 -243
  92. package/src/router/file-based-routing.ts +0 -697
  93. package/src/router/file-streaming.ts +0 -383
  94. package/src/router/fluent-routing.ts +0 -866
  95. package/src/router/group-organization.ts +0 -229
  96. package/src/router/handler-resolver.ts +0 -327
  97. package/src/router/http-methods.ts +0 -407
  98. package/src/router/index.ts +0 -193
  99. package/src/router/middleware-groups.ts +0 -222
  100. package/src/router/middleware-integration.ts +0 -399
  101. package/src/router/middleware.ts +0 -234
  102. package/src/router/model-binding.ts +0 -215
  103. package/src/router/optimized-route-matching.ts +0 -302
  104. package/src/router/route-building.ts +0 -221
  105. package/src/router/route-compiler.ts +0 -691
  106. package/src/router/route-matching.ts +0 -368
  107. package/src/router/route-trie.ts +0 -450
  108. package/src/router/router.ts +0 -1616
  109. package/src/router/server.ts +0 -684
  110. package/src/router/validation-integration.ts +0 -449
  111. package/src/router/view-rendering.ts +0 -233
  112. package/src/router/websocket.ts +0 -100
  113. package/src/routing/route-caching.ts +0 -402
  114. package/src/routing/route-throttling.ts +0 -469
  115. package/src/routing/subdomain-routing.ts +0 -492
  116. package/src/session/database-store.ts +0 -109
  117. package/src/session/file-store.ts +0 -148
  118. package/src/session/index.ts +0 -244
  119. package/src/session/memory-store.ts +0 -88
  120. package/src/session/redis-store.ts +0 -93
  121. package/src/streaming/index.ts +0 -17
  122. package/src/streaming/sse-handler.ts +0 -482
  123. package/src/streaming/stream-handler.ts +0 -552
  124. package/src/testing/auth-testing.ts +0 -448
  125. package/src/testing/file-upload-testing.ts +0 -543
  126. package/src/testing/index.ts +0 -10
  127. package/src/testing/middleware-testing.ts +0 -322
  128. package/src/testing/model-binding-testing.ts +0 -645
  129. package/src/testing/performance-testing.ts +0 -738
  130. package/src/testing/test-client.ts +0 -310
  131. package/src/testing/test-request.ts +0 -315
  132. package/src/testing/test-response.ts +0 -332
  133. package/src/testing/types.ts +0 -224
  134. package/src/testing/websocket-testing.ts +0 -590
  135. package/src/types/controller-types.ts +0 -385
  136. package/src/types/core.ts +0 -683
  137. package/src/types/middleware-types.ts +0 -422
  138. package/src/types/request-response-augmentation.ts +0 -489
  139. package/src/types/route-inference.ts +0 -372
  140. package/src/types.ts +0 -2123
  141. package/src/url.ts +0 -131
  142. package/src/utils/index.ts +0 -1
  143. package/src/utils/query-preservation.ts +0 -201
  144. package/src/utils.ts +0 -395
  145. package/src/validation/validator.ts +0 -688
  146. package/src/websocket/clustering.ts +0 -769
@@ -1,822 +0,0 @@
1
- /**
2
- * Request/Response Enhancements - Request Macros
3
- *
4
- * Laravel-style request macros for enhanced request functionality
5
- */
6
-
7
- import type { EnhancedRequest } from '../types'
8
-
9
- export interface RequestMacro {
10
- name: string
11
- handler: (this: EnhancedRequest, ...args: any[]) => any
12
- /**
13
- * Accessor macros are installed as getters on the macro prototype —
14
- * `req.<name>` invokes the handler instead of returning a function.
15
- * Assignment shadows the accessor with an own property.
16
- */
17
- accessor?: boolean
18
- }
19
-
20
- // Hoisted hot-path helpers — compiled once instead of per call
21
- const MOBILE_REGEX = /Mobile|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
22
- const BOT_REGEX = /bot|crawler|spider|crawling/i
23
- const GLOB_PATTERN_CACHE = new Map<string, RegExp>()
24
-
25
- /** Parsed-state cache slots attached to a request on first use. */
26
- interface RequestParseCache {
27
- _parsedURL?: URL
28
- _parsedCookies?: Record<string, string>
29
- }
30
-
31
- export function getParsedURL(req: EnhancedRequest): URL {
32
- const cache = req as EnhancedRequest & RequestParseCache
33
- if (!cache._parsedURL) {
34
- cache._parsedURL = new URL(req.url)
35
- }
36
- return cache._parsedURL
37
- }
38
-
39
- export function getParsedCookies(req: EnhancedRequest): Record<string, string> {
40
- const cache = req as EnhancedRequest & RequestParseCache
41
- if (!cache._parsedCookies) {
42
- const cookies: Record<string, string> = {}
43
- const cookieHeader = req.headers.get('cookie')
44
- if (cookieHeader) {
45
- for (const cookie of cookieHeader.split(';')) {
46
- const eqIndex = cookie.indexOf('=')
47
- if (eqIndex === -1)
48
- continue
49
- const key = cookie.slice(0, eqIndex).trim()
50
- if (!key)
51
- continue
52
- const value = cookie.slice(eqIndex + 1).trim()
53
- try {
54
- cookies[key] = decodeURIComponent(value)
55
- }
56
- catch {
57
- cookies[key] = value
58
- }
59
- }
60
- }
61
- cache._parsedCookies = cookies
62
- }
63
- return cache._parsedCookies
64
- }
65
-
66
- /**
67
- * Request macro registry
68
- */
69
- class RequestMacroRegistry {
70
- private macros: Map<string, RequestMacro> = new Map()
71
- // Bumped on every mutation so cached macro prototypes can be rebuilt
72
- private epoch = 0
73
-
74
- /**
75
- * Register a request macro
76
- */
77
- register(name: string, handler: (this: EnhancedRequest, ...args: any[]) => any, accessor = false): void {
78
- this.macros.set(name, { name, handler, accessor })
79
- this.epoch++
80
- }
81
-
82
- /**
83
- * Get a registered macro
84
- */
85
- get(name: string): RequestMacro | undefined {
86
- return this.macros.get(name)
87
- }
88
-
89
- /**
90
- * Check if macro exists
91
- */
92
- has(name: string): boolean {
93
- return this.macros.has(name)
94
- }
95
-
96
- /**
97
- * Get all registered macros
98
- */
99
- all(): RequestMacro[] {
100
- return Array.from(this.macros.values())
101
- }
102
-
103
- /**
104
- * Remove a macro
105
- */
106
- remove(name: string): boolean {
107
- const removed = this.macros.delete(name)
108
- if (removed)
109
- this.epoch++
110
- return removed
111
- }
112
-
113
- /**
114
- * Clear all macros
115
- */
116
- clear(): void {
117
- this.macros.clear()
118
- this.epoch++
119
- }
120
-
121
- /**
122
- * Monotonic registry version (see `RequestWithMacros.applyMacros`)
123
- */
124
- getEpoch(): number {
125
- return this.epoch
126
- }
127
- }
128
-
129
- /**
130
- * Global request macro registry
131
- */
132
- export const requestMacroRegistry: RequestMacroRegistry = new RequestMacroRegistry()
133
-
134
- // Marker symbols for prototype-based macro attachment
135
- const MACRO_PROTO = Symbol('bunRouterMacroProto')
136
- const MACRO_BASE = Symbol('bunRouterMacroBase')
137
-
138
- // Cache of generated macro prototypes, keyed by the request's original
139
- // prototype. Rebuilt whenever the registry epoch moves.
140
- let macroProtoCache = new WeakMap<object, Record<PropertyKey, unknown>>()
141
- let macroProtoEpoch = -1
142
-
143
- /**
144
- * Request class with macro support
145
- */
146
- export class RequestWithMacros {
147
- /**
148
- * Register a request macro
149
- */
150
- static macro(name: string, handler: (this: EnhancedRequest, ...args: any[]) => any): void {
151
- requestMacroRegistry.register(name, handler)
152
- }
153
-
154
- /**
155
- * Register an accessor macro: `req.<name>` evaluates the getter
156
- * (instead of exposing a callable). Assignment to the property
157
- * shadows the accessor with an own value, so consumers that set
158
- * `req.<name> = ...` keep working.
159
- */
160
- static macroAccessor(name: string, getter: (this: EnhancedRequest) => any): void {
161
- requestMacroRegistry.register(name, getter, true)
162
- }
163
-
164
- /**
165
- * Apply macros to a request object.
166
- *
167
- * Macros are attached via a shared prototype inserted between the
168
- * request and its original prototype — one `setPrototypeOf` per
169
- * request. The previous implementation looped over every macro and
170
- * `bind`-assigned it, costing ~40 property writes and closures per
171
- * request on the dispatch hot path. Macro handlers receive the
172
- * request as `this`, so no per-request binding is needed.
173
- */
174
- static applyMacros(request: EnhancedRequest): EnhancedRequest {
175
- const epoch = requestMacroRegistry.getEpoch()
176
- if (epoch !== macroProtoEpoch) {
177
- macroProtoCache = new WeakMap()
178
- macroProtoEpoch = epoch
179
- }
180
-
181
- let base = (Object.getPrototypeOf(request) ?? Object.prototype) as Record<PropertyKey, unknown>
182
- if (base[MACRO_PROTO]) {
183
- // Request was already enhanced. Current generation: nothing to do.
184
- if (macroProtoCache.get(base[MACRO_BASE] as object) === base) {
185
- return request
186
- }
187
- // Stale generation: rebuild on top of the original prototype
188
- base = base[MACRO_BASE] as Record<PropertyKey, unknown>
189
- }
190
-
191
- let proto = macroProtoCache.get(base)
192
- if (!proto) {
193
- proto = Object.create(base) as Record<PropertyKey, unknown>
194
- for (const macro of requestMacroRegistry.all()) {
195
- if (macro.accessor) {
196
- Object.defineProperty(proto, macro.name, {
197
- get: macro.handler,
198
- // Assignment shadows the accessor with an own property
199
- set(value: unknown) {
200
- Object.defineProperty(this, macro.name, {
201
- value,
202
- writable: true,
203
- configurable: true,
204
- enumerable: true,
205
- })
206
- },
207
- configurable: true,
208
- })
209
- }
210
- else {
211
- proto[macro.name] = macro.handler
212
- }
213
- }
214
- Object.defineProperty(proto, MACRO_PROTO, { value: true })
215
- Object.defineProperty(proto, MACRO_BASE, { value: base })
216
- macroProtoCache.set(base, proto)
217
- }
218
-
219
- Object.setPrototypeOf(request, proto)
220
- return request
221
- }
222
-
223
- /**
224
- * Check if macro exists
225
- */
226
- static hasMacro(name: string): boolean {
227
- return requestMacroRegistry.has(name)
228
- }
229
- }
230
-
231
- /**
232
- * Built-in request macros
233
- */
234
- export const BuiltInRequestMacros = {
235
- /**
236
- * Check if request wants JSON response
237
- */
238
- wantsJson(this: EnhancedRequest): boolean {
239
- const accept = this.headers.get('accept') || ''
240
- return accept.includes('application/json') || accept.includes('json')
241
- },
242
-
243
- /**
244
- * Check if request wants HTML response
245
- */
246
- wantsHtml(this: EnhancedRequest): boolean {
247
- const accept = this.headers.get('accept') || ''
248
- return accept.includes('text/html') || accept.includes('html')
249
- },
250
-
251
- /**
252
- * Check if request wants XML response
253
- */
254
- wantsXml(this: EnhancedRequest): boolean {
255
- const accept = this.headers.get('accept') || ''
256
- return accept.includes('application/xml') || accept.includes('xml')
257
- },
258
-
259
- /**
260
- * Check if request expects JSON response
261
- */
262
- expectsJson(this: EnhancedRequest): boolean {
263
- return this.wantsJson() || this.isAjax() || this.isPjax()
264
- },
265
-
266
- /**
267
- * Check if request is AJAX
268
- */
269
- isAjax(this: EnhancedRequest): boolean {
270
- return this.headers.get('x-requested-with') === 'XMLHttpRequest'
271
- },
272
-
273
- /**
274
- * Check if request is PJAX
275
- */
276
- isPjax(this: EnhancedRequest): boolean {
277
- return this.headers.get('x-pjax') === 'true'
278
- },
279
-
280
- /**
281
- * Check if request is from mobile device
282
- */
283
- isMobile(this: EnhancedRequest): boolean {
284
- return MOBILE_REGEX.test(this.headers.get('user-agent') || '')
285
- },
286
-
287
- /**
288
- * Check if request is from bot/crawler
289
- */
290
- isBot(this: EnhancedRequest): boolean {
291
- return BOT_REGEX.test(this.headers.get('user-agent') || '')
292
- },
293
-
294
- /**
295
- * Check if request is secure (HTTPS)
296
- */
297
- isSecure(this: EnhancedRequest): boolean {
298
- return this.url.startsWith('https://')
299
- || this.headers.get('x-forwarded-proto') === 'https'
300
- || this.headers.get('x-forwarded-ssl') === 'on'
301
- },
302
-
303
- /**
304
- * Get client IP address
305
- */
306
- ip(this: EnhancedRequest): string {
307
- return this.headers.get('x-forwarded-for')?.split(',')[0]?.trim()
308
- || this.headers.get('x-real-ip')
309
- || this.headers.get('cf-connecting-ip')
310
- || this.headers.get('x-client-ip')
311
- || 'unknown'
312
- },
313
-
314
- /**
315
- * Get all client IPs (including proxies)
316
- */
317
- ips(this: EnhancedRequest): string[] {
318
- const forwardedFor = this.headers.get('x-forwarded-for')
319
- if (forwardedFor) {
320
- return forwardedFor.split(',').map(ip => ip.trim())
321
- }
322
- return [BuiltInRequestMacros.ip.call(this)]
323
- },
324
-
325
- /**
326
- * Get user agent
327
- */
328
- userAgent(this: EnhancedRequest): string {
329
- return this.headers.get('user-agent') || ''
330
- },
331
-
332
- /**
333
- * Get referer URL
334
- */
335
- referer(this: EnhancedRequest): string | null {
336
- return this.headers.get('referer') || this.headers.get('referrer') || null
337
- },
338
-
339
- /**
340
- * Get authorization header
341
- */
342
- bearerToken(this: EnhancedRequest): string | null {
343
- const auth = this.headers.get('authorization')
344
- if (auth && auth.startsWith('Bearer ')) {
345
- return auth.substring(7)
346
- }
347
- return null
348
- },
349
-
350
- /**
351
- * Get the raw, unparsed request body as a string.
352
- *
353
- * The body stream can only be consumed once, so this caches the result on
354
- * `_rawBody`. Signature-verifying callbacks (Stripe/GitHub/Slack webhooks)
355
- * need the exact bytes the client sent, before any JSON parsing — a parsed
356
- * `jsonBody` re-serialized is NOT byte-identical and will fail HMAC checks.
357
- * A framework body parser that has already read the body may populate
358
- * `_rawBody` up front so this returns without re-reading.
359
- */
360
- async rawBody(this: EnhancedRequest): Promise<string> {
361
- if (typeof this._rawBody === 'string')
362
- return this._rawBody
363
- try {
364
- const text = await this.clone().text()
365
- this._rawBody = text
366
- return text
367
- }
368
- catch {
369
- return ''
370
- }
371
- },
372
-
373
- /**
374
- * Get basic auth credentials
375
- */
376
- basicAuth(this: EnhancedRequest): { username: string, password: string } | null {
377
- const auth = this.headers.get('authorization')
378
- if (auth && auth.startsWith('Basic ')) {
379
- try {
380
- const decoded = atob(auth.substring(6))
381
- const [username, password] = decoded.split(':')
382
- return { username, password }
383
- }
384
- catch {
385
- return null
386
- }
387
- }
388
- return null
389
- },
390
-
391
- /**
392
- * Check if request has specific header
393
- */
394
- hasHeader(this: EnhancedRequest, name: string): boolean {
395
- return this.headers.has(name.toLowerCase())
396
- },
397
-
398
- /**
399
- * Get header value with default
400
- */
401
- header(this: EnhancedRequest, name: string, defaultValue?: string): string | null {
402
- return this.headers.get(name.toLowerCase()) || defaultValue || null
403
- },
404
-
405
- /**
406
- * Get all headers as object
407
- */
408
- allHeaders(this: EnhancedRequest): Record<string, string> {
409
- const headers: Record<string, string> = {}
410
- this.headers.forEach((value, key) => {
411
- headers[key] = value
412
- })
413
- return headers
414
- },
415
-
416
- /**
417
- * Get input value from query, body, or params
418
- */
419
- input(this: EnhancedRequest, key: string, defaultValue?: any): any {
420
- // Check validated data first
421
- if (this.validated && key in this.validated) {
422
- return this.validated[key]
423
- }
424
-
425
- // Check query parameters
426
- if (this.query && key in this.query) {
427
- return this.query[key]
428
- }
429
-
430
- // Check route parameters
431
- if (this.params && key in this.params) {
432
- return this.params[key]
433
- }
434
-
435
- // Check JSON body
436
- if (this.jsonBody && typeof this.jsonBody === 'object' && key in this.jsonBody) {
437
- return (this.jsonBody as Record<string, unknown>)[key]
438
- }
439
-
440
- // Check form body
441
- if (this.formBody && typeof this.formBody === 'object' && key in this.formBody) {
442
- return (this.formBody as Record<string, unknown>)[key]
443
- }
444
-
445
- return defaultValue
446
- },
447
-
448
- /**
449
- * Get all input data
450
- */
451
- all(this: EnhancedRequest): Record<string, unknown> {
452
- return {
453
- ...this.params,
454
- ...this.query,
455
- ...(typeof this.jsonBody === 'object' && this.jsonBody !== null ? this.jsonBody : {}),
456
- ...(typeof this.formBody === 'object' && this.formBody !== null ? this.formBody : {}),
457
- ...(typeof this.validated === 'object' && this.validated !== null ? this.validated : {}),
458
- }
459
- },
460
-
461
- /**
462
- * Get only specified input keys
463
- */
464
- only(this: EnhancedRequest, keys: string[]): Record<string, any> {
465
- const all = this.all()
466
- const result: Record<string, any> = {}
467
-
468
- keys.forEach((key) => {
469
- if (key in all) {
470
- result[key] = all[key]
471
- }
472
- })
473
-
474
- return result
475
- },
476
-
477
- /**
478
- * Get all input except specified keys
479
- */
480
- except(this: EnhancedRequest, keys: string[]): Record<string, any> {
481
- const all = this.all()
482
- const result: Record<string, any> = {}
483
-
484
- Object.keys(all).forEach((key) => {
485
- if (!keys.includes(key)) {
486
- result[key] = all[key]
487
- }
488
- })
489
-
490
- return result
491
- },
492
-
493
- /**
494
- * Check if input has specific key
495
- */
496
- has(this: EnhancedRequest, key: string): boolean {
497
- const all = this.all()
498
- return key in all && all[key] !== undefined && all[key] !== null && all[key] !== ''
499
- },
500
-
501
- /**
502
- * Check if input has any of the specified keys
503
- */
504
- hasAny(this: EnhancedRequest, keys: string[]): boolean {
505
- return keys.some(key => this.has(key))
506
- },
507
-
508
- /**
509
- * Check if input is missing specific key
510
- */
511
- missing(this: EnhancedRequest, key: string): boolean {
512
- return !this.has(key)
513
- },
514
-
515
- /**
516
- * Check if input is filled (not empty)
517
- */
518
- filled(this: EnhancedRequest, key: string): boolean {
519
- const value = this.input(key)
520
- if (value === null || value === undefined)
521
- return false
522
- if (typeof value === 'string')
523
- return value.trim().length > 0
524
- if (Array.isArray(value))
525
- return value.length > 0
526
- if (typeof value === 'object')
527
- return Object.keys(value).length > 0
528
- return true
529
- },
530
-
531
- /**
532
- * Get query parameter
533
- */
534
- getQuery(this: EnhancedRequest, key?: string, defaultValue?: any): any {
535
- if (!key)
536
- return this.query || {}
537
- return this.query?.[key] || defaultValue
538
- },
539
-
540
- /**
541
- * Get route parameter
542
- */
543
- param(this: EnhancedRequest, key: string, defaultValue?: any): any {
544
- return this.params?.[key] || defaultValue
545
- },
546
-
547
- /**
548
- * Get cookie value
549
- */
550
- cookie(this: EnhancedRequest, name: string, defaultValue?: string): string | null {
551
- const value = getParsedCookies(this)[name]
552
- return value !== undefined ? value : (defaultValue || null)
553
- },
554
-
555
- /**
556
- * Get all cookies
557
- */
558
- cookies(this: EnhancedRequest): Record<string, string> {
559
- return { ...getParsedCookies(this) }
560
- },
561
-
562
- /**
563
- * Get file from upload
564
- */
565
- file(this: EnhancedRequest, name: string): any {
566
- return this.files?.find(f => f.fieldName === name)
567
- },
568
-
569
- /**
570
- * Check if request has file upload
571
- */
572
- hasFile(this: EnhancedRequest, name: string): boolean {
573
- return !!this.files?.find(f => f.fieldName === name)
574
- },
575
-
576
- /**
577
- * Get request path without query string
578
- */
579
- path(this: EnhancedRequest): string {
580
- return getParsedURL(this).pathname
581
- },
582
-
583
- /**
584
- * Get request URL
585
- */
586
- fullUrl(this: EnhancedRequest): string {
587
- return this.url
588
- },
589
-
590
- /**
591
- * Get request root URL
592
- */
593
- root(this: EnhancedRequest): string {
594
- const url = getParsedURL(this)
595
- return `${url.protocol}//${url.host}`
596
- },
597
-
598
- /**
599
- * Check if path matches pattern
600
- */
601
- is(this: EnhancedRequest, pattern: string): boolean {
602
- const path = this.path()
603
-
604
- // Compiled glob patterns are memoized — route checks tend to reuse a
605
- // small set of patterns across many requests
606
- let regex = GLOB_PATTERN_CACHE.get(pattern)
607
- if (!regex) {
608
- const regexPattern = pattern
609
- .replace(/\*/g, '.*')
610
- .replace(/\?/g, '.')
611
- regex = new RegExp(`^${regexPattern}$`)
612
- if (GLOB_PATTERN_CACHE.size >= 1000) {
613
- GLOB_PATTERN_CACHE.clear()
614
- }
615
- GLOB_PATTERN_CACHE.set(pattern, regex)
616
- }
617
- return regex.test(path)
618
- },
619
-
620
- /**
621
- * Get route name (if available)
622
- */
623
- route(this: EnhancedRequest): string | null {
624
- return (this as any).routeName || null
625
- },
626
-
627
- /**
628
- * Get request fingerprint for caching
629
- */
630
- fingerprint(this: EnhancedRequest): string {
631
- const url = getParsedURL(this)
632
- const data = {
633
- method: this.method,
634
- path: url.pathname,
635
- query: url.search,
636
- ip: BuiltInRequestMacros.ip.call(this),
637
- }
638
-
639
- return btoa(JSON.stringify(data))
640
- },
641
-
642
- /**
643
- * Get request signature for security
644
- */
645
- signature(this: EnhancedRequest, secret: string): string {
646
- const data = `${this.method}${this.url}${BuiltInRequestMacros.ip.call(this)}${secret}`
647
-
648
- // Simple hash function (in production, use crypto)
649
- let hash = 0
650
- for (let i = 0; i < data.length; i++) {
651
- const char = data.charCodeAt(i)
652
- hash = ((hash << 5) - hash) + char
653
- hash = hash & hash // Convert to 32-bit integer
654
- }
655
-
656
- return Math.abs(hash).toString(36)
657
- },
658
-
659
- /**
660
- * Merge additional data into request
661
- */
662
- merge(this: EnhancedRequest, data: Record<string, any>): void {
663
- if (!this.validated) {
664
- this.validated = {}
665
- }
666
- Object.assign(this.validated, data)
667
- },
668
-
669
- /**
670
- * Replace request data
671
- */
672
- replace(this: EnhancedRequest, data: Record<string, any>): void {
673
- this.validated = { ...data }
674
- },
675
-
676
- /**
677
- * Get request age in milliseconds
678
- */
679
- age(this: EnhancedRequest): number {
680
- const startTime = (this as any).startTime || Date.now()
681
- return Date.now() - startTime
682
- },
683
-
684
- /**
685
- * Check if request is from trusted proxy
686
- */
687
- isFromTrustedProxy(this: EnhancedRequest, trustedProxies: string[] = []): boolean {
688
- const ip = BuiltInRequestMacros.ip.call(this)
689
- return trustedProxies.includes(ip)
690
- },
691
-
692
- /**
693
- * Get request content length
694
- */
695
- contentLength(this: EnhancedRequest): number {
696
- const length = this.headers.get('content-length')
697
- return length ? Number.parseInt(length, 10) : 0
698
- },
699
-
700
- /**
701
- * Get request content type
702
- */
703
- contentType(this: EnhancedRequest): string | null {
704
- return this.headers.get('content-type')
705
- },
706
-
707
- /**
708
- * Check if request content type matches
709
- */
710
- isContentType(this: EnhancedRequest, type: string): boolean {
711
- const contentType = this.contentType()
712
- return contentType ? contentType.includes(type) : false
713
- },
714
- }
715
-
716
- /**
717
- * Register all built-in macros
718
- */
719
- export function registerBuiltInRequestMacros(): void {
720
- Object.entries(BuiltInRequestMacros).forEach(([name, handler]) => {
721
- RequestWithMacros.macro(name, handler)
722
- })
723
- }
724
-
725
- /**
726
- * Request macro factory functions
727
- */
728
- export const RequestMacroFactory = {
729
- /**
730
- * Create input validation macro
731
- */
732
- validateInput: (_rules: Record<string, string>) => {
733
- // eslint-disable-next-line pickier/no-unused-vars
734
- return function (this: EnhancedRequest): boolean {
735
- // This would integrate with the validation system
736
- // For now, return true as placeholder
737
- return true
738
- }
739
- },
740
-
741
- /**
742
- * Create custom header checker macro
743
- */
744
- hasCustomHeader: (headerName: string) => {
745
- return function (this: EnhancedRequest): boolean {
746
- return this.hasHeader(headerName)
747
- }
748
- },
749
-
750
- /**
751
- * Create role checker macro
752
- */
753
- hasRole: (role: string) => {
754
- return function (this: EnhancedRequest): boolean {
755
- const user = (this as any).user
756
- return user?.roles?.includes(role) || false
757
- }
758
- },
759
-
760
- /**
761
- * Create permission checker macro
762
- */
763
- can: (permission: string) => {
764
- return function (this: EnhancedRequest): boolean {
765
- const user = (this as any).user
766
- return user?.permissions?.includes(permission) || false
767
- }
768
- },
769
- }
770
-
771
- /**
772
- * Request helper utilities
773
- */
774
- export const RequestHelpers = {
775
- /**
776
- * Apply macros to request
777
- */
778
- withMacros: (request: EnhancedRequest): EnhancedRequest => {
779
- return RequestWithMacros.applyMacros(request)
780
- },
781
-
782
- /**
783
- * Create request from URL and options
784
- */
785
- create: (url: string, options: RequestInit = {}): EnhancedRequest => {
786
- const request = new Request(url, options) as EnhancedRequest
787
- return RequestWithMacros.applyMacros(request)
788
- },
789
-
790
- /**
791
- * Parse query string to object
792
- */
793
- parseQuery: (queryString: string): Record<string, string> => {
794
- const params = new URLSearchParams(queryString)
795
- const result: Record<string, string> = {}
796
-
797
- params.forEach((value, key) => {
798
- result[key] = value
799
- })
800
-
801
- return result
802
- },
803
-
804
- /**
805
- * Parse cookies from header
806
- */
807
- parseCookies: (cookieHeader: string): Record<string, string> => {
808
- const cookies: Record<string, string> = {}
809
-
810
- cookieHeader.split(';').forEach((cookie) => {
811
- const [key, value] = cookie.trim().split('=')
812
- if (key && value) {
813
- cookies[key] = decodeURIComponent(value)
814
- }
815
- })
816
-
817
- return cookies
818
- },
819
- }
820
-
821
- // Auto-register built-in macros
822
- registerBuiltInRequestMacros()