@stacksjs/bun-router 0.0.17 → 0.0.19

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