@buenojs/bueno 0.8.0

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 (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. package/tsconfig.json +27 -0
@@ -0,0 +1,626 @@
1
+ /**
2
+ * Pipes System
3
+ *
4
+ * Pipes run after guards in the request pipeline and can transform/validate
5
+ * data before it reaches the handler.
6
+ *
7
+ * Execution Order:
8
+ * Incoming Request → Guards → Pipes → Handler
9
+ *
10
+ * Pipes can:
11
+ * - Transform data (e.g., string to number)
12
+ * - Validate data (e.g., using Standard Schema)
13
+ * - Provide default values
14
+ */
15
+
16
+ import type { Context } from "../context";
17
+ import type { Token } from "../container";
18
+ import type { StandardSchema } from "../types";
19
+ import { validate, isStandardSchema, type ValidationResult } from "../validation";
20
+
21
+ // ============= Types =============
22
+
23
+ /**
24
+ * Parameter metadata available to pipes
25
+ */
26
+ export interface ParameterMetadata {
27
+ /** Parameter index */
28
+ index: number;
29
+ /** Parameter name if available */
30
+ name?: string;
31
+ /** Decorator type (body, query, param, etc.) */
32
+ decorator?: string;
33
+ }
34
+
35
+ /**
36
+ * Context available to pipes during transformation
37
+ */
38
+ export interface PipeContext {
39
+ /** The request context */
40
+ context: Context;
41
+ /** Parameter metadata */
42
+ metadata?: ParameterMetadata;
43
+ /** Target type information */
44
+ type?: unknown;
45
+ }
46
+
47
+ /**
48
+ * Pipe interface for data transformation
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * class ParseIntPipe implements PipeTransform<string, number> {
53
+ * transform(value: string, context: PipeContext): number {
54
+ * const parsed = parseInt(value, 10);
55
+ * if (isNaN(parsed)) {
56
+ * throw new Error('Validation failed');
57
+ * }
58
+ * return parsed;
59
+ * }
60
+ * }
61
+ * ```
62
+ */
63
+ export interface PipeTransform<T = unknown, R = unknown> {
64
+ transform(value: T, context: PipeContext): R | Promise<R>;
65
+ }
66
+
67
+ /**
68
+ * Pipe function type (for functional pipes)
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const trimPipe: PipeFn<string, string> = (value) => value.trim();
73
+ * ```
74
+ */
75
+ export type PipeFn<T = unknown, R = unknown> = (
76
+ value: T,
77
+ context: PipeContext
78
+ ) => R | Promise<R>;
79
+
80
+ /**
81
+ * Pipe type - can be:
82
+ * - A token for a pipe class registered in the container
83
+ * - A pipe class instance
84
+ * - A pipe function
85
+ */
86
+ export type Pipe<T = unknown, R = unknown> =
87
+ | Token<PipeTransform<T, R>>
88
+ | PipeTransform<T, R>
89
+ | PipeFn<T, R>;
90
+
91
+ // ============= Metadata Storage =============
92
+
93
+ // Type alias for class constructors
94
+ type Constructor = new (...args: unknown[]) => unknown;
95
+
96
+ // WeakMap for storing pipes metadata on method prototypes
97
+ const pipesMethodMetadata = new WeakMap<object, Map<string | symbol, ParameterPipeMetadata[]>>();
98
+
99
+ /**
100
+ * Metadata for a parameter with pipes
101
+ */
102
+ export interface ParameterPipeMetadata {
103
+ /** Parameter index */
104
+ index: number;
105
+ /** Parameter decorator type */
106
+ decorator: 'body' | 'query' | 'param' | 'custom';
107
+ /** Key for query/param decorators */
108
+ key?: string;
109
+ /** Schema for validation */
110
+ schema?: StandardSchema;
111
+ /** Pipes to apply */
112
+ pipes: Pipe[];
113
+ }
114
+
115
+ /**
116
+ * Set pipes metadata on a method
117
+ */
118
+ function setMethodPipes(
119
+ target: object,
120
+ propertyKey: string | symbol,
121
+ metadata: ParameterPipeMetadata[]
122
+ ): void {
123
+ if (!pipesMethodMetadata.has(target)) {
124
+ pipesMethodMetadata.set(target, new Map());
125
+ }
126
+ pipesMethodMetadata.get(target)?.set(propertyKey, metadata);
127
+ }
128
+
129
+ /**
130
+ * Get pipes metadata from a method
131
+ */
132
+ export function getMethodPipes(
133
+ target: object,
134
+ propertyKey: string | symbol
135
+ ): ParameterPipeMetadata[] | undefined {
136
+ return pipesMethodMetadata.get(target)?.get(propertyKey);
137
+ }
138
+
139
+ // ============= Pipe Decorator =============
140
+
141
+ /**
142
+ * Decorator to apply pipes to a parameter.
143
+ * Pipes are executed in the order they are provided.
144
+ *
145
+ * @param pipes - Pipes to apply
146
+ * @returns ParameterDecorator
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * @Get(':id')
151
+ * getUser(@Param('id', ParseIntPipe) id: number) {}
152
+ * ```
153
+ */
154
+ export function UsePipes(...pipes: Pipe[]): ParameterDecorator {
155
+ return (
156
+ target: unknown,
157
+ propertyKey: string | symbol | undefined,
158
+ parameterIndex: number
159
+ ) => {
160
+ if (propertyKey === undefined) {
161
+ throw new Error("UsePipes can only be used on method parameters");
162
+ }
163
+
164
+ const targetObj = target as object;
165
+ const existing = getMethodPipes(targetObj, propertyKey) ?? [];
166
+
167
+ // Find existing metadata for this parameter or create new
168
+ const existingParam = existing.find(p => p.index === parameterIndex);
169
+ if (existingParam) {
170
+ existingParam.pipes.push(...pipes);
171
+ } else {
172
+ existing.push({
173
+ index: parameterIndex,
174
+ decorator: 'custom',
175
+ pipes: [...pipes]
176
+ });
177
+ }
178
+
179
+ setMethodPipes(targetObj, propertyKey, existing);
180
+ };
181
+ }
182
+
183
+ // ============= Parameter Decorators =============
184
+
185
+ /**
186
+ * Extract and optionally validate request body.
187
+ *
188
+ * @param schema - Optional Standard Schema for validation
189
+ * @returns ParameterDecorator
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * @Post()
194
+ * createUser(@Body(userSchema) body: User) {}
195
+ * ```
196
+ */
197
+ export function Body(schema?: StandardSchema): ParameterDecorator {
198
+ return (
199
+ target: unknown,
200
+ propertyKey: string | symbol | undefined,
201
+ parameterIndex: number
202
+ ) => {
203
+ if (propertyKey === undefined) {
204
+ throw new Error("Body can only be used on method parameters");
205
+ }
206
+
207
+ const targetObj = target as object;
208
+ const existing = getMethodPipes(targetObj, propertyKey) ?? [];
209
+
210
+ existing.push({
211
+ index: parameterIndex,
212
+ decorator: 'body',
213
+ schema,
214
+ pipes: schema ? [new ValidationPipe(schema)] : []
215
+ });
216
+
217
+ setMethodPipes(targetObj, propertyKey, existing);
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Extract and optionally validate query parameter.
223
+ *
224
+ * @param key - Query parameter key (optional, if omitted returns all query params)
225
+ * @param schema - Optional Standard Schema for validation
226
+ * @returns ParameterDecorator
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * @Get()
231
+ * search(@Query('q') query: string) {}
232
+ *
233
+ * @Get()
234
+ * search(@Query('limit', limitSchema) limit: number) {}
235
+ * ```
236
+ */
237
+ export function Query(key?: string, schema?: StandardSchema): ParameterDecorator {
238
+ return (
239
+ target: unknown,
240
+ propertyKey: string | symbol | undefined,
241
+ parameterIndex: number
242
+ ) => {
243
+ if (propertyKey === undefined) {
244
+ throw new Error("Query can only be used on method parameters");
245
+ }
246
+
247
+ const targetObj = target as object;
248
+ const existing = getMethodPipes(targetObj, propertyKey) ?? [];
249
+
250
+ existing.push({
251
+ index: parameterIndex,
252
+ decorator: 'query',
253
+ key,
254
+ schema,
255
+ pipes: schema ? [new ValidationPipe(schema)] : []
256
+ });
257
+
258
+ setMethodPipes(targetObj, propertyKey, existing);
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Extract and transform route parameter.
264
+ *
265
+ * @param key - Route parameter key (optional, if omitted returns all params)
266
+ * @param pipes - Pipes to apply for transformation
267
+ * @returns ParameterDecorator
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * @Get(':id')
272
+ * getUser(@Param('id', ParseIntPipe) id: number) {}
273
+ * ```
274
+ */
275
+ export function Param(key?: string, ...pipes: Pipe[]): ParameterDecorator {
276
+ return (
277
+ target: unknown,
278
+ propertyKey: string | symbol | undefined,
279
+ parameterIndex: number
280
+ ) => {
281
+ if (propertyKey === undefined) {
282
+ throw new Error("Param can only be used on method parameters");
283
+ }
284
+
285
+ const targetObj = target as object;
286
+ const existing = getMethodPipes(targetObj, propertyKey) ?? [];
287
+
288
+ existing.push({
289
+ index: parameterIndex,
290
+ decorator: 'param',
291
+ key,
292
+ pipes: [...pipes]
293
+ });
294
+
295
+ setMethodPipes(targetObj, propertyKey, existing);
296
+ };
297
+ }
298
+
299
+ // ============= Built-in Pipes =============
300
+
301
+ /**
302
+ * ValidationPipe - Validates using Standard Schema
303
+ *
304
+ * @example
305
+ * ```typescript
306
+ * @Body(userSchema) body: User
307
+ * // or explicitly
308
+ * @UsePipes(new ValidationPipe(userSchema))
309
+ * ```
310
+ */
311
+ export class ValidationPipe<T = unknown> implements PipeTransform<unknown, T> {
312
+ constructor(private schema: StandardSchema<unknown, T>) {}
313
+
314
+ async transform(value: unknown, context: PipeContext): Promise<T> {
315
+ const result: ValidationResult<T> = await validate(this.schema, value);
316
+
317
+ if (result.success) {
318
+ return result.data;
319
+ }
320
+
321
+ // Validation failed
322
+ const failedResult = result as Extract<ValidationResult<T>, { success: false }>;
323
+ const error = new Error("Validation failed");
324
+ (error as Error & { issues: unknown[] }).issues = [...failedResult.issues];
325
+ throw error;
326
+ }
327
+ }
328
+
329
+ /**
330
+ * ParseIntPipe - Transforms string to integer
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * @Param('id', ParseIntPipe) id: number
335
+ * ```
336
+ */
337
+ export class ParseIntPipe implements PipeTransform<string, number> {
338
+ transform(value: string, context: PipeContext): number {
339
+ const parsed = parseInt(value, 10);
340
+
341
+ if (isNaN(parsed)) {
342
+ throw new Error(`Validation failed: "${value}" is not a valid integer`);
343
+ }
344
+
345
+ return parsed;
346
+ }
347
+ }
348
+
349
+ /**
350
+ * ParseFloatPipe - Transforms string to float
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * @Param('price', ParseFloatPipe) price: number
355
+ * ```
356
+ */
357
+ export class ParseFloatPipe implements PipeTransform<string, number> {
358
+ transform(value: string, context: PipeContext): number {
359
+ const parsed = parseFloat(value);
360
+
361
+ if (isNaN(parsed)) {
362
+ throw new Error(`Validation failed: "${value}" is not a valid number`);
363
+ }
364
+
365
+ return parsed;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * ParseBoolPipe - Transforms string to boolean
371
+ *
372
+ * @example
373
+ * ```typescript
374
+ * @Query('active', ParseBoolPipe) active: boolean
375
+ * ```
376
+ */
377
+ export class ParseBoolPipe implements PipeTransform<string, boolean> {
378
+ private readonly truthyValues = ['true', '1', 'yes', 'on'];
379
+ private readonly falsyValues = ['false', '0', 'no', 'off'];
380
+
381
+ transform(value: string, context: PipeContext): boolean {
382
+ const lower = value.toLowerCase();
383
+
384
+ if (this.truthyValues.includes(lower)) {
385
+ return true;
386
+ }
387
+
388
+ if (this.falsyValues.includes(lower)) {
389
+ return false;
390
+ }
391
+
392
+ throw new Error(`Validation failed: "${value}" is not a valid boolean`);
393
+ }
394
+ }
395
+
396
+ /**
397
+ * DefaultValuePipe - Provides default value when input is undefined or null
398
+ *
399
+ * @example
400
+ * ```typescript
401
+ * @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number
402
+ * ```
403
+ */
404
+ export class DefaultValuePipe<T> implements PipeTransform<unknown, T> {
405
+ constructor(private defaultValue: T) {}
406
+
407
+ transform(value: unknown, context: PipeContext): T {
408
+ if (value === undefined || value === null) {
409
+ return this.defaultValue;
410
+ }
411
+ return value as T;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * TrimPipe - Trims string whitespace
417
+ *
418
+ * @example
419
+ * ```typescript
420
+ * @Query('name', TrimPipe) name: string
421
+ * ```
422
+ */
423
+ export class TrimPipe implements PipeTransform<string, string> {
424
+ transform(value: string, context: PipeContext): string {
425
+ if (typeof value !== 'string') {
426
+ throw new Error('Value must be a string');
427
+ }
428
+ return value.trim();
429
+ }
430
+ }
431
+
432
+ /**
433
+ * ParseJsonPipe - Parses JSON string to object
434
+ *
435
+ * @example
436
+ * ```typescript
437
+ * @Query('data', ParseJsonPipe) data: MyObject
438
+ * ```
439
+ */
440
+ export class ParseJsonPipe<T = unknown> implements PipeTransform<string, T> {
441
+ transform(value: string, context: PipeContext): T {
442
+ try {
443
+ return JSON.parse(value) as T;
444
+ } catch {
445
+ throw new Error(`Validation failed: "${value}" is not valid JSON`);
446
+ }
447
+ }
448
+ }
449
+
450
+ /**
451
+ * ParseArrayPipe - Transforms comma-separated string to array
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * @Query('tags', ParseArrayPipe) tags: string[]
456
+ * ```
457
+ */
458
+ export class ParseArrayPipe implements PipeTransform<string, string[]> {
459
+ constructor(private separator: string = ',') {}
460
+
461
+ transform(value: string, context: PipeContext): string[] {
462
+ if (typeof value !== 'string') {
463
+ throw new Error('Value must be a string');
464
+ }
465
+ return value.split(this.separator).map(s => s.trim()).filter(s => s.length > 0);
466
+ }
467
+ }
468
+
469
+ // ============= Pipe Executor =============
470
+
471
+ /**
472
+ * Pipe executor options
473
+ */
474
+ export interface PipeExecutorOptions {
475
+ /** Global pipes applied to all parameters */
476
+ globalPipes?: Pipe[];
477
+ /** Pipes from parameter decorator */
478
+ parameterPipes?: Pipe[];
479
+ /** Container for resolving pipe instances */
480
+ resolvePipe?: (pipe: Pipe) => PipeTransform | PipeFn | null;
481
+ }
482
+
483
+ /**
484
+ * Execute pipes in order and return the transformed value
485
+ *
486
+ * @param value - Initial value to transform
487
+ * @param context - Pipe context
488
+ * @param options - Pipe executor options
489
+ * @returns Transformed value
490
+ * @throws Error if any pipe fails
491
+ */
492
+ export async function executePipes<T = unknown>(
493
+ value: unknown,
494
+ context: PipeContext,
495
+ options: PipeExecutorOptions
496
+ ): Promise<T> {
497
+ const { globalPipes = [], parameterPipes = [], resolvePipe } = options;
498
+
499
+ // Combine all pipes in execution order
500
+ const allPipes = [...globalPipes, ...parameterPipes];
501
+
502
+ let currentValue: unknown = value;
503
+
504
+ // Execute each pipe in order
505
+ for (const pipe of allPipes) {
506
+ let pipeInstance: PipeTransform | PipeFn | null = null;
507
+
508
+ // Resolve the pipe
509
+ if (typeof pipe === "function") {
510
+ // Check if it's a pipe function or a class constructor
511
+ const funcPipe = pipe as { prototype?: unknown; transform?: unknown };
512
+ if (funcPipe.prototype && typeof funcPipe.prototype === "object" &&
513
+ "transform" in (funcPipe.prototype as object)) {
514
+ // It's a class constructor - try to resolve from container or create instance
515
+ pipeInstance = resolvePipe ? resolvePipe(pipe) : null;
516
+ if (!pipeInstance) {
517
+ // Create a new instance if not in container
518
+ const PipeClass = pipe as unknown as new () => PipeTransform;
519
+ pipeInstance = new PipeClass();
520
+ }
521
+ } else {
522
+ // It's a pipe function
523
+ pipeInstance = pipe as PipeFn;
524
+ }
525
+ } else if (typeof pipe === "object" && pipe !== null) {
526
+ // It's a token or already an instance
527
+ const objPipe = pipe as { transform?: unknown };
528
+ if ("transform" in objPipe && typeof objPipe.transform === "function") {
529
+ // It's already a PipeTransform instance
530
+ pipeInstance = pipe as PipeTransform;
531
+ } else {
532
+ // It's a token - try to resolve
533
+ pipeInstance = resolvePipe ? resolvePipe(pipe) : null;
534
+ }
535
+ }
536
+
537
+ if (!pipeInstance) {
538
+ console.warn("Pipe could not be resolved:", pipe);
539
+ continue;
540
+ }
541
+
542
+ // Execute the pipe
543
+ if (typeof pipeInstance === "function") {
544
+ // Pipe function
545
+ currentValue = await pipeInstance(currentValue, context);
546
+ } else {
547
+ // PipeTransform instance
548
+ currentValue = await pipeInstance.transform(currentValue, context);
549
+ }
550
+ }
551
+
552
+ return currentValue as T;
553
+ }
554
+
555
+ // ============= Parameter Value Extractor =============
556
+
557
+ /**
558
+ * Extract parameter value from context based on decorator type
559
+ */
560
+ export async function extractParameterValue(
561
+ context: Context,
562
+ metadata: ParameterPipeMetadata
563
+ ): Promise<unknown> {
564
+ switch (metadata.decorator) {
565
+ case 'body':
566
+ return await context.body();
567
+
568
+ case 'query':
569
+ if (metadata.key) {
570
+ return context.query[metadata.key];
571
+ }
572
+ return context.query;
573
+
574
+ case 'param':
575
+ if (metadata.key) {
576
+ return context.params[metadata.key];
577
+ }
578
+ return context.params;
579
+
580
+ case 'custom':
581
+ default:
582
+ return undefined;
583
+ }
584
+ }
585
+
586
+ // ============= Error Response =============
587
+
588
+ /**
589
+ * Create a 400 Bad Request response for pipe errors
590
+ */
591
+ export function createBadRequestResponse(error: Error): Response {
592
+ const issues = (error as Error & { issues?: unknown[] }).issues;
593
+
594
+ return new Response(JSON.stringify({
595
+ statusCode: 400,
596
+ error: "Bad Request",
597
+ message: error.message,
598
+ ...(issues && { issues })
599
+ }), {
600
+ status: 400,
601
+ headers: {
602
+ "Content-Type": "application/json",
603
+ },
604
+ });
605
+ }
606
+
607
+ // ============= Type Guards =============
608
+
609
+ /**
610
+ * Check if a value is a PipeTransform instance
611
+ */
612
+ export function isPipeTransform(value: unknown): value is PipeTransform {
613
+ return (
614
+ typeof value === "object" &&
615
+ value !== null &&
616
+ "transform" in value &&
617
+ typeof (value as PipeTransform).transform === "function"
618
+ );
619
+ }
620
+
621
+ /**
622
+ * Check if a value is a pipe function
623
+ */
624
+ export function isPipeFn(value: unknown): value is PipeFn {
625
+ return typeof value === "function" && !isPipeTransform(value);
626
+ }