@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,876 +0,0 @@
1
- /**
2
- * Enhanced Request Methods
3
- *
4
- * Implements fluent request methods with full TypeScript support
5
- */
6
-
7
- import type {
8
- EnhancedRequest,
9
- InputValue,
10
- RequestInput,
11
- ValidatedData,
12
- } from '../types'
13
- import { Validator } from '../validation/validator'
14
-
15
- // ============================================================================
16
- // Types
17
- // ============================================================================
18
-
19
- /**
20
- * Validation rules type
21
- */
22
- export type ValidationRules = Record<string, string | ValidationRule[]>
23
-
24
- /**
25
- * Validation rule interface
26
- */
27
- export interface ValidationRule {
28
- name: string
29
- validate: (value: unknown, parameters: string[], field: string, data: Record<string, unknown>) => Promise<boolean> | boolean
30
- message: string | ((_field: string, _parameters: string[]) => string)
31
- }
32
-
33
- /**
34
- * Safe validated data wrapper
35
- */
36
- export interface SafeData<T extends Record<string, unknown> = Record<string, unknown>> {
37
- only: <K extends keyof T>(keys: K[]) => Pick<T, K>
38
- except: <K extends keyof T>(keys: K[]) => Omit<T, K>
39
- merge: <U extends Record<string, unknown>>(data: U) => T & U
40
- all: () => T
41
- has: (key: keyof T) => boolean
42
- get: <K extends keyof T>(key: K) => T[K]
43
- }
44
-
45
- /**
46
- * Collection interface for array data
47
- */
48
- export interface Collection<T> {
49
- items: T[]
50
- count: () => number
51
- first: () => T | undefined
52
- last: () => T | undefined
53
- map: <U>(fn: (item: T, index: number) => U) => Collection<U>
54
- filter: (fn: (item: T, index: number) => boolean) => Collection<T>
55
- find: (fn: (item: T, index: number) => boolean) => T | undefined
56
- reduce: <U>(fn: (acc: U, item: T, index: number) => U, initial: U) => U
57
- forEach: (fn: (item: T, index: number) => void) => void
58
- toArray: () => T[]
59
- isEmpty: () => boolean
60
- isNotEmpty: () => boolean
61
- pluck: <K extends keyof T>(key: K) => Collection<T[K]>
62
- unique: () => Collection<T>
63
- sortBy: <K extends keyof T>(key: K, direction?: 'asc' | 'desc') => Collection<T>
64
- groupBy: <K extends keyof T>(key: K) => Map<T[K], Collection<T>>
65
- chunk: (size: number) => Collection<Collection<T>>
66
- take: (count: number) => Collection<T>
67
- skip: (count: number) => Collection<T>
68
- reverse: () => Collection<T>
69
- sum: (key?: keyof T) => number
70
- avg: (key?: keyof T) => number
71
- min: (key?: keyof T) => T | undefined
72
- max: (key?: keyof T) => T | undefined
73
- }
74
-
75
- // ============================================================================
76
- // Collection Implementation
77
- // ============================================================================
78
-
79
- /**
80
- * Collection class for array manipulation
81
- */
82
- export class CollectionImpl<T> implements Collection<T> {
83
- items: T[]
84
-
85
- constructor(items: T[]) {
86
- this.items = [...items]
87
- }
88
-
89
- count(): number {
90
- return this.items.length
91
- }
92
-
93
- first(): T | undefined {
94
- return this.items[0]
95
- }
96
-
97
- last(): T | undefined {
98
- return this.items[this.items.length - 1]
99
- }
100
-
101
- map<U>(fn: (item: T, index: number) => U): Collection<U> {
102
- return new CollectionImpl(this.items.map(fn))
103
- }
104
-
105
- filter(fn: (item: T, index: number) => boolean): Collection<T> {
106
- return new CollectionImpl(this.items.filter(fn))
107
- }
108
-
109
- find(fn: (item: T, index: number) => boolean): T | undefined {
110
- return this.items.find(fn)
111
- }
112
-
113
- reduce<U>(fn: (acc: U, item: T, index: number) => U, initial: U): U {
114
- return this.items.reduce(fn, initial)
115
- }
116
-
117
- forEach(fn: (item: T, index: number) => void): void {
118
- this.items.forEach(fn)
119
- }
120
-
121
- toArray(): T[] {
122
- return [...this.items]
123
- }
124
-
125
- isEmpty(): boolean {
126
- return this.items.length === 0
127
- }
128
-
129
- isNotEmpty(): boolean {
130
- return this.items.length > 0
131
- }
132
-
133
- pluck<K extends keyof T>(key: K): Collection<T[K]> {
134
- return new CollectionImpl(this.items.map(item => item[key]))
135
- }
136
-
137
- unique(): Collection<T> {
138
- return new CollectionImpl([...new Set(this.items)])
139
- }
140
-
141
- sortBy<K extends keyof T>(key: K, direction: 'asc' | 'desc' = 'asc'): Collection<T> {
142
- const sorted = [...this.items].sort((a, b) => {
143
- const aVal = a[key]
144
- const bVal = b[key]
145
- if (aVal < bVal)
146
- return direction === 'asc' ? -1 : 1
147
- if (aVal > bVal)
148
- return direction === 'asc' ? 1 : -1
149
- return 0
150
- })
151
- return new CollectionImpl(sorted)
152
- }
153
-
154
- groupBy<K extends keyof T>(key: K): Map<T[K], Collection<T>> {
155
- const groups = new Map<T[K], T[]>()
156
- for (const item of this.items) {
157
- const groupKey = item[key]
158
- if (!groups.has(groupKey)) {
159
- groups.set(groupKey, [])
160
- }
161
- groups.get(groupKey)!.push(item)
162
- }
163
- const result = new Map<T[K], Collection<T>>()
164
- for (const [k, v] of groups) {
165
- result.set(k, new CollectionImpl(v))
166
- }
167
- return result
168
- }
169
-
170
- chunk(size: number): Collection<Collection<T>> {
171
- const chunks: Collection<T>[] = []
172
- for (let i = 0; i < this.items.length; i += size) {
173
- chunks.push(new CollectionImpl(this.items.slice(i, i + size)))
174
- }
175
- return new CollectionImpl(chunks)
176
- }
177
-
178
- take(count: number): Collection<T> {
179
- return new CollectionImpl(this.items.slice(0, count))
180
- }
181
-
182
- skip(count: number): Collection<T> {
183
- return new CollectionImpl(this.items.slice(count))
184
- }
185
-
186
- reverse(): Collection<T> {
187
- return new CollectionImpl([...this.items].reverse())
188
- }
189
-
190
- sum(key?: keyof T): number {
191
- if (key) {
192
- return this.items.reduce((acc, item) => acc + (Number(item[key]) || 0), 0)
193
- }
194
- return this.items.reduce((acc, item) => acc + (Number(item) || 0), 0)
195
- }
196
-
197
- avg(key?: keyof T): number {
198
- if (this.items.length === 0)
199
- return 0
200
- return this.sum(key) / this.items.length
201
- }
202
-
203
- min(key?: keyof T): T | undefined {
204
- if (this.items.length === 0)
205
- return undefined
206
- if (key) {
207
- return this.items.reduce((min, item) =>
208
- item[key] < min[key] ? item : min,
209
- )
210
- }
211
- return this.items.reduce((min, item) => (item < min ? item : min))
212
- }
213
-
214
- max(key?: keyof T): T | undefined {
215
- if (this.items.length === 0)
216
- return undefined
217
- if (key) {
218
- return this.items.reduce((max, item) =>
219
- item[key] > max[key] ? item : max,
220
- )
221
- }
222
- return this.items.reduce((max, item) => (item > max ? item : max))
223
- }
224
- }
225
-
226
- // ============================================================================
227
- // Safe Data Implementation
228
- // ============================================================================
229
-
230
- /**
231
- * Create a safe data wrapper
232
- */
233
- export function createSafeData<T extends Record<string, unknown>>(data: T): SafeData<T> {
234
- return {
235
- only: <K extends keyof T>(keys: K[]): Pick<T, K> => {
236
- const result = {} as Pick<T, K>
237
- for (const key of keys) {
238
- if (key in data) {
239
- result[key] = data[key]
240
- }
241
- }
242
- return result
243
- },
244
-
245
- except: <K extends keyof T>(keys: K[]): Omit<T, K> => {
246
- const result = { ...data }
247
- for (const key of keys) {
248
- delete result[key]
249
- }
250
- return result as Omit<T, K>
251
- },
252
-
253
- // eslint-disable-next-line pickier/no-unused-vars
254
- merge: <U extends Record<string, unknown>>(mergeData: U): T & U => {
255
- return { ...data, ...mergeData }
256
- },
257
-
258
- all: (): T => ({ ...data }),
259
-
260
- has: (key: keyof T): boolean => key in data,
261
-
262
- get: <K extends keyof T>(key: K): T[K] => data[key],
263
- }
264
- }
265
-
266
- // ============================================================================
267
- // Enhanced Request Methods
268
- // ============================================================================
269
-
270
- /**
271
- * Enhanced request methods interface
272
- */
273
- export interface EnhancedRequestMethods {
274
- /**
275
- * Validate the request data
276
- */
277
- validate: <T extends Record<string, unknown> = Record<string, unknown>>(
278
- rules: ValidationRules,
279
- messages?: Record<string, string>,
280
- ) => Promise<T>
281
-
282
- /**
283
- * Get validated data as typed object
284
- */
285
- getValidated: <T extends Record<string, unknown> = Record<string, unknown>>() => T
286
-
287
- /**
288
- * Get safe validated data wrapper
289
- */
290
- safe: <T extends Record<string, unknown> = Record<string, unknown>>() => SafeData<T>
291
-
292
- /**
293
- * Execute callback when input has a value
294
- */
295
- whenHas: <T>(
296
- key: string,
297
- callback: (value: T) => void,
298
- defaultCallback?: () => void,
299
- ) => void
300
-
301
- /**
302
- * Execute callback when input is filled (not empty)
303
- */
304
- whenFilled: <T>(
305
- key: string,
306
- callback: (value: T) => void,
307
- defaultCallback?: () => void,
308
- ) => void
309
-
310
- /**
311
- * Parse date input
312
- */
313
- date: (key: string, format?: string) => Date | null
314
-
315
- /**
316
- * Parse enum input
317
- */
318
- enum: <T extends Record<string, string | number>>(
319
- key: string,
320
- enumType: T,
321
- ) => T[keyof T] | null
322
-
323
- /**
324
- * Get input as collection
325
- */
326
- collect: <T = unknown>(key: string) => Collection<T>
327
-
328
- /**
329
- * Get all input keys
330
- */
331
- keys: () => string[]
332
-
333
- /**
334
- * Get input with type casting
335
- */
336
- string: (key: string, defaultValue?: string) => string
337
- integer: (key: string, defaultValue?: number) => number
338
- float: (key: string, defaultValue?: number) => number
339
- boolean: (key: string, defaultValue?: boolean) => boolean
340
-
341
- /**
342
- * Check if input matches a value
343
- */
344
- isValue: (key: string, value: unknown) => boolean
345
-
346
- /**
347
- * Get old input (for form repopulation)
348
- */
349
- old: <T = unknown>(key: string, defaultValue?: T) => T
350
-
351
- /**
352
- * Flash input to session for form repopulation
353
- */
354
- flashInput: (keys?: string[]) => void
355
-
356
- /**
357
- * Flash only specific keys
358
- */
359
- flashInputOnly: (keys: string[]) => void
360
-
361
- /**
362
- * Flash except specific keys
363
- */
364
- flashInputExcept: (keys: string[]) => void
365
-
366
- /**
367
- * Get input as array
368
- */
369
- array: <T = unknown>(key: string) => T[]
370
-
371
- /**
372
- * Get input value from any source (query, body, params)
373
- * Laravel-style request.get()
374
- */
375
- get: <T = unknown>(key: string, defaultValue?: T) => T
376
-
377
- /**
378
- * Get all input data
379
- */
380
- all: () => RequestInput
381
-
382
- /**
383
- * Get only specified keys
384
- */
385
- only: <T extends Record<string, unknown> = Record<string, unknown>>(keys: string[]) => T
386
-
387
- /**
388
- * Get all except specified keys
389
- */
390
- except: <T extends Record<string, unknown> = Record<string, unknown>>(keys: string[]) => T
391
-
392
- /**
393
- * Check if input has a key
394
- */
395
- has: (key: string | string[]) => boolean
396
-
397
- /**
398
- * Check if input has any of the keys
399
- */
400
- hasAny: (keys: string[]) => boolean
401
-
402
- /**
403
- * Check if input is filled (not empty)
404
- */
405
- filled: (key: string | string[]) => boolean
406
-
407
- /**
408
- * Check if input is missing
409
- */
410
- missing: (key: string | string[]) => boolean
411
-
412
- /**
413
- * Merge additional data into input
414
- */
415
- merge: (data: Record<string, unknown>) => void
416
-
417
- /**
418
- * Get input value or fail
419
- */
420
- input: <T = unknown>(key: string, defaultValue?: T) => T
421
- }
422
-
423
- // ============================================================================
424
- // Request Enhancement
425
- // ============================================================================
426
-
427
- /**
428
- * Enhance request with additional methods
429
- */
430
- export function enhanceRequestWithMethods(request: EnhancedRequest): EnhancedRequest & EnhancedRequestMethods {
431
- const enhanced = request as EnhancedRequest & EnhancedRequestMethods & {
432
- _validatedData?: ValidatedData
433
- _oldInput?: RequestInput
434
- }
435
-
436
- // Merged input (query + JSON body + form body + route params).
437
- // Memoized: many of the helpers below call this several times per
438
- // request, and the merge result only changes when a body is parsed
439
- // later — so the cache keys off the body object identities.
440
- let cachedInput: RequestInput | null = null
441
- let cachedJsonBody: unknown
442
- let cachedFormBody: unknown
443
-
444
- const getAllInput = (): RequestInput => {
445
- if (cachedInput
446
- && cachedJsonBody === request.jsonBody
447
- && cachedFormBody === request.formBody) {
448
- return cachedInput
449
- }
450
-
451
- const input: RequestInput = {}
452
-
453
- // Query parameters
454
- if (request.query) {
455
- for (const [key, value] of Object.entries(request.query)) {
456
- input[key] = value as InputValue
457
- }
458
- }
459
-
460
- // JSON body
461
- if (request.jsonBody) {
462
- for (const [key, value] of Object.entries(request.jsonBody as Record<string, unknown>)) {
463
- input[key] = value as InputValue
464
- }
465
- }
466
-
467
- // Form body
468
- if (request.formBody) {
469
- for (const [key, value] of Object.entries(request.formBody)) {
470
- input[key] = value as InputValue
471
- }
472
- }
473
-
474
- // Route params
475
- if (request.params) {
476
- for (const [key, value] of Object.entries(request.params)) {
477
- input[key] = value
478
- }
479
- }
480
-
481
- cachedInput = input
482
- cachedJsonBody = request.jsonBody
483
- cachedFormBody = request.formBody
484
- return input
485
- }
486
-
487
- /**
488
- * Validate the request
489
- */
490
- enhanced.validate = async <T extends Record<string, unknown>>(
491
- rules: ValidationRules,
492
- messages?: Record<string, string>,
493
- ): Promise<T> => {
494
- const input = getAllInput()
495
- const validator = new Validator({
496
- customMessages: messages,
497
- })
498
-
499
- const errors = await validator.validate(input as Record<string, unknown>, rules as Record<string, string>)
500
-
501
- if (Object.keys(errors).length > 0) {
502
- const error = new Error('Validation failed') as Error & { errors: Record<string, string[]> }
503
- error.errors = errors
504
- throw error
505
- }
506
-
507
- enhanced._validatedData = input as ValidatedData
508
- return input as T
509
- }
510
-
511
- /**
512
- * Get validated data as typed object
513
- */
514
- enhanced.getValidated = <T extends Record<string, unknown>>(): T => {
515
- return (enhanced._validatedData || {}) as T
516
- }
517
-
518
- /**
519
- * Get safe validated data wrapper
520
- */
521
- enhanced.safe = <T extends Record<string, unknown>>(): SafeData<T> => {
522
- return createSafeData(enhanced.getValidated<T>())
523
- }
524
-
525
- /**
526
- * Execute callback when input has a value
527
- */
528
- enhanced.whenHas = <T>(
529
- key: string,
530
- callback: (value: T) => void,
531
- defaultCallback?: () => void,
532
- ): void => {
533
- const input = getAllInput()
534
- if (key in input && input[key] !== undefined) {
535
- callback(input[key] as T)
536
- }
537
- else if (defaultCallback) {
538
- defaultCallback()
539
- }
540
- }
541
-
542
- /**
543
- * Execute callback when input is filled
544
- */
545
- enhanced.whenFilled = <T>(
546
- key: string,
547
- callback: (value: T) => void,
548
- defaultCallback?: () => void,
549
- ): void => {
550
- const input = getAllInput()
551
- const value = input[key]
552
- const isFilled = value !== undefined
553
- && value !== null
554
- && value !== ''
555
- && !(Array.isArray(value) && value.length === 0)
556
-
557
- if (isFilled) {
558
- callback(value as T)
559
- }
560
- else if (defaultCallback) {
561
- defaultCallback()
562
- }
563
- }
564
-
565
- /**
566
- * Parse date input
567
- */
568
- enhanced.date = (key: string, _format?: string): Date | null => {
569
- const input = getAllInput()
570
- const value = input[key]
571
-
572
- if (!value)
573
- return null
574
-
575
- const date = new Date(String(value))
576
- return Number.isNaN(date.getTime()) ? null : date
577
- }
578
-
579
- /**
580
- * Parse enum input
581
- */
582
- enhanced.enum = <T extends Record<string, string | number>>(
583
- key: string,
584
- enumType: T,
585
- ): T[keyof T] | null => {
586
- const input = getAllInput()
587
- const value = input[key]
588
-
589
- if (value === undefined || value === null)
590
- return null
591
-
592
- const enumValues = Object.values(enumType)
593
- if (enumValues.includes(value as T[keyof T])) {
594
- return value as T[keyof T]
595
- }
596
-
597
- // Try to match by key
598
- const stringValue = String(value)
599
- if (stringValue in enumType) {
600
- return enumType[stringValue as keyof T]
601
- }
602
-
603
- return null
604
- }
605
-
606
- /**
607
- * Get input as collection
608
- */
609
- enhanced.collect = <T = unknown>(key: string): Collection<T> => {
610
- const input = getAllInput()
611
- const value = input[key]
612
-
613
- if (Array.isArray(value)) {
614
- return new CollectionImpl(value as T[])
615
- }
616
-
617
- return new CollectionImpl(value ? [value as T] : [])
618
- }
619
-
620
- /**
621
- * Get all input keys
622
- */
623
- enhanced.keys = (): string[] => {
624
- return Object.keys(getAllInput())
625
- }
626
-
627
- /**
628
- * Get string input
629
- */
630
- enhanced.string = (key: string, defaultValue: string = ''): string => {
631
- const input = getAllInput()
632
- const value = input[key]
633
- return value !== undefined && value !== null ? String(value) : defaultValue
634
- }
635
-
636
- /**
637
- * Get integer input
638
- */
639
- enhanced.integer = (key: string, defaultValue: number = 0): number => {
640
- const input = getAllInput()
641
- const value = input[key]
642
- const parsed = Number.parseInt(String(value), 10)
643
- return Number.isNaN(parsed) ? defaultValue : parsed
644
- }
645
-
646
- /**
647
- * Get float input
648
- */
649
- enhanced.float = (key: string, defaultValue: number = 0): number => {
650
- const input = getAllInput()
651
- const value = input[key]
652
- const parsed = Number.parseFloat(String(value))
653
- return Number.isNaN(parsed) ? defaultValue : parsed
654
- }
655
-
656
- /**
657
- * Get boolean input
658
- */
659
- enhanced.boolean = (key: string, defaultValue: boolean = false): boolean => {
660
- const input = getAllInput()
661
- const value = input[key]
662
-
663
- if (value === undefined || value === null)
664
- return defaultValue
665
- if (typeof value === 'boolean')
666
- return value
667
- if (value === 'true' || value === '1' || value === 1)
668
- return true
669
- if (value === 'false' || value === '0' || value === 0)
670
- return false
671
-
672
- return defaultValue
673
- }
674
-
675
- /**
676
- * Check if input matches a value
677
- */
678
- enhanced.isValue = (key: string, value: unknown): boolean => {
679
- const input = getAllInput()
680
- return input[key] === value
681
- }
682
-
683
- /**
684
- * Get old input
685
- */
686
- enhanced.old = <T = unknown>(key: string, defaultValue?: T): T => {
687
- const oldInput = enhanced._oldInput || {}
688
- return (oldInput[key] as T) ?? (defaultValue as T)
689
- }
690
-
691
- /**
692
- * Flash input to session for form repopulation
693
- */
694
- enhanced.flashInput = (keys?: string[]): void => {
695
- const input = getAllInput()
696
- if (keys) {
697
- const filtered: RequestInput = {}
698
- for (const key of keys) {
699
- if (key in input) {
700
- filtered[key] = input[key]
701
- }
702
- }
703
- enhanced._oldInput = filtered
704
- }
705
- else {
706
- enhanced._oldInput = input
707
- }
708
- }
709
-
710
- /**
711
- * Flash only specific keys
712
- */
713
- enhanced.flashInputOnly = (keys: string[]): void => {
714
- enhanced.flashInput(keys)
715
- }
716
-
717
- /**
718
- * Flash except specific keys
719
- */
720
- enhanced.flashInputExcept = (keys: string[]): void => {
721
- const input = getAllInput()
722
- const filtered: RequestInput = {}
723
- for (const [key, value] of Object.entries(input)) {
724
- if (!keys.includes(key)) {
725
- filtered[key] = value
726
- }
727
- }
728
- enhanced._oldInput = filtered
729
- }
730
-
731
- /**
732
- * Get input as array
733
- */
734
- enhanced.array = <T = unknown>(key: string): T[] => {
735
- const input = getAllInput()
736
- const value = input[key]
737
-
738
- if (Array.isArray(value)) {
739
- return value as T[]
740
- }
741
-
742
- return value !== undefined && value !== null ? [value as T] : []
743
- }
744
-
745
- /**
746
- * Get input value from any source (query, body, params) - Laravel style
747
- */
748
- enhanced.get = <T = unknown>(key: string, defaultValue?: T): T => {
749
- const input = getAllInput()
750
- const value = input[key]
751
- return (value !== undefined ? value : defaultValue) as T
752
- }
753
-
754
- /**
755
- * Get all input data
756
- */
757
- enhanced.all = (): RequestInput => {
758
- return getAllInput()
759
- }
760
-
761
- /**
762
- * Get only specified keys
763
- */
764
- enhanced.only = <T extends Record<string, unknown> = Record<string, unknown>>(keys: string[]): T => {
765
- const input = getAllInput()
766
- const result = {} as T
767
- for (const key of keys) {
768
- if (key in input) {
769
- (result as Record<string, unknown>)[key] = input[key]
770
- }
771
- }
772
- return result
773
- }
774
-
775
- /**
776
- * Get all except specified keys
777
- */
778
- enhanced.except = <T extends Record<string, unknown> = Record<string, unknown>>(keys: string[]): T => {
779
- const input = getAllInput()
780
- const result = { ...input } as T
781
- for (const key of keys) {
782
- delete (result as Record<string, unknown>)[key]
783
- }
784
- return result
785
- }
786
-
787
- /**
788
- * Check if input has a key (or all keys if array)
789
- */
790
- enhanced.has = (key: string | string[]): boolean => {
791
- const input = getAllInput()
792
- if (Array.isArray(key)) {
793
- return key.every(k => k in input && input[k] !== undefined)
794
- }
795
- return key in input && input[key] !== undefined
796
- }
797
-
798
- /**
799
- * Check if input has any of the keys
800
- */
801
- enhanced.hasAny = (keys: string[]): boolean => {
802
- const input = getAllInput()
803
- return keys.some(k => k in input && input[k] !== undefined)
804
- }
805
-
806
- /**
807
- * Check if input is filled (not empty)
808
- */
809
- enhanced.filled = (key: string | string[]): boolean => {
810
- const input = getAllInput()
811
- const isFilled = (k: string): boolean => {
812
- const value = input[k]
813
- return value !== undefined
814
- && value !== null
815
- && value !== ''
816
- && !(Array.isArray(value) && value.length === 0)
817
- }
818
-
819
- if (Array.isArray(key)) {
820
- return key.every(isFilled)
821
- }
822
- return isFilled(key)
823
- }
824
-
825
- /**
826
- * Check if input is missing
827
- */
828
- enhanced.missing = (key: string | string[]): boolean => {
829
- const input = getAllInput()
830
- if (Array.isArray(key)) {
831
- return key.every(k => !(k in input) || input[k] === undefined)
832
- }
833
- return !(key in input) || input[key] === undefined
834
- }
835
-
836
- /**
837
- * Merge additional data into input
838
- */
839
- enhanced.merge = (data: Record<string, unknown>): void => {
840
- if (request.jsonBody && typeof request.jsonBody === 'object') {
841
- Object.assign(request.jsonBody, data)
842
- }
843
- else {
844
- (request as any).jsonBody = data
845
- }
846
- // In-place mutation doesn't change jsonBody's identity, so drop the
847
- // memoized merge explicitly
848
- cachedInput = null
849
- }
850
-
851
- /**
852
- * Get input value - alias for get() for Laravel compatibility
853
- */
854
- enhanced.input = <T = unknown>(key: string, defaultValue?: T): T => {
855
- return enhanced.get(key, defaultValue)
856
- }
857
-
858
- return enhanced
859
- }
860
-
861
- // ============================================================================
862
- // Middleware for Request Enhancement
863
- // ============================================================================
864
-
865
- /**
866
- * Middleware to enhance request with additional methods
867
- */
868
- export function enhancedRequestMiddleware() {
869
- return async (req: EnhancedRequest, next: () => Promise<Response | null>): Promise<Response | null> => {
870
- enhanceRequestWithMethods(req)
871
- return next()
872
- }
873
- }
874
-
875
- // Note: Collection interface and CollectionImpl class are already exported above
876
- // enhanceRequestWithMethods is already exported above