balda 0.0.39 → 0.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import { schedule, TaskContext } from 'node-cron';
2
1
  import { TSchema, Static } from '@sinclair/typebox';
3
2
  import { ZodType, z } from 'zod';
4
3
  import { Ajv } from 'ajv';
5
4
  import { IncomingMessage, ServerResponse, Server as Server$2 } from 'node:http';
5
+ import { schedule, TaskContext } from 'node-cron';
6
6
  import { Http2Server } from 'node:http2';
7
7
  import { Server as Server$1, ServerOptions as ServerOptions$1 } from 'node:https';
8
8
  import { ApolloServerOptions, BaseContext } from '@apollo/server';
@@ -19,103 +19,11 @@ import { AsyncLocalStorage } from 'node:async_hooks';
19
19
  import { S3Client } from '@aws-sdk/client-s3';
20
20
  import { Transporter } from 'nodemailer';
21
21
 
22
- type CronSchedule = {
23
- name: string;
24
- args: Parameters<typeof schedule>;
25
- };
26
- type CronScheduleParams = Parameters<typeof schedule>;
27
- type CronUIOptions = {
28
- path: string;
29
- };
30
-
31
- /**
32
- * Decorator to schedule a cron job. Must be applied to a class method. By default, the cron job will not overlap with other cron jobs of the same type.
33
- * ```ts
34
- * @cron('* * * * *', { timezone: 'Europe/Istanbul' })
35
- * async test() {
36
- * console.log('test');
37
- * }
38
- * ```
39
- */
40
- declare const cron: (schedule: CronScheduleParams[0], options?: CronScheduleParams[2]) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
41
-
42
- type FlagType = "boolean" | "string" | "number" | "list";
43
- type InferFlagType<T extends FlagType> = T extends "boolean" ? boolean : T extends "string" ? string : T extends "number" ? number : T extends "list" ? string[] : never;
44
- type ArgOptions = {
45
- /**
46
- * The description of the argument.
47
- */
48
- description?: string;
49
- /**
50
- * Whether the argument is required.
51
- * @default false
52
- */
53
- required?: boolean;
54
- /**
55
- * The default value of the argument.
56
- */
57
- defaultValue?: string;
58
- /**
59
- * A function to parse the argument value.
60
- * @default The value is returned as is.
61
- */
62
- parse?: (value: string) => string;
63
- };
64
- type FlagOptions<T extends FlagType> = {
65
- /**
66
- * The description of the flag.
67
- */
68
- description?: string;
69
- /**
70
- * The type of the flag.
71
- */
72
- type: T;
73
- /**
74
- * The name of the flag.
75
- * @default The name of the field.
76
- */
77
- name?: string;
78
- /**
79
- * The aliases of the flag.
80
- */
81
- aliases?: string[] | string;
82
- /**
83
- * The default value of the flag.
84
- */
85
- defaultValue?: InferFlagType<T>;
86
- /**
87
- * Whether the flag is required.
88
- * @default false
89
- */
90
- required?: boolean;
91
- /**
92
- * A function to parse the flag value.
93
- * @default The value is returned as is.
94
- */
95
- parse?: (value: any) => InferFlagType<T>;
96
- };
97
-
98
- /**
99
- * Decorator to mark a field of a command class as an argument.
100
- * @param options - The options for the argument.
101
- * @warning Arguments are evaluated in the order they are defined in the Command class.
102
- */
103
- declare const arg: (options: ArgOptions) => (target: any, propertyKey: string) => void;
104
-
105
- declare const flag: {
106
- <T extends FlagType>(options: FlagOptions<T>): (target: any, propertyKey: string) => void;
107
- boolean(options: Omit<FlagOptions<"boolean">, "type">): (target: any, propertyKey: string) => void;
108
- string(options: Omit<FlagOptions<"string">, "type">): (target: any, propertyKey: string) => void;
109
- number(options: Omit<FlagOptions<"number">, "type">): (target: any, propertyKey: string) => void;
110
- list(options: Omit<FlagOptions<"list">, "type">): (target: any, propertyKey: string) => void;
111
- array(options: Omit<FlagOptions<"list">, "type">): (target: any, propertyKey: string) => void;
112
- };
113
-
114
22
  type AjvInstance = InstanceType<typeof Ajv>;
115
23
  type AjvCompileParams = Parameters<AjvInstance["compile"]>;
116
24
 
117
25
  type RequestSchema = ZodType | TSchema | AjvCompileParams[0];
118
- type ValidatedData<T extends RequestSchema> = T extends ZodType ? z.infer<T> : T extends TSchema ? Static<T> : T extends AjvCompileParams[0] ? any : any;
26
+ type ValidatedData<T extends RequestSchema> = T extends ZodType ? z.infer<T> : T extends TSchema ? Static<T> : T extends AjvCompileParams[0] ? unknown : unknown;
119
27
  interface CustomValidationError {
120
28
  status?: number;
121
29
  message?: string;
@@ -141,192 +49,41 @@ interface ValidationOptions {
141
49
  }
142
50
 
143
51
  /**
144
- * Type of Swagger UI to use
145
- */
146
- type SwaggerUIType = "standard" | "rapidoc" | "scalar" | "elements" | "custom";
147
- type HTMLString = string;
148
- /**
149
- * Custom UI generator function that takes the spec URL and global options and returns HTML
150
- */
151
- type CustomUIGenerator = (specUrl: string, globalOptions: SwaggerGlobalOptions) => HTMLString;
152
- /**
153
- * Type of request body for a route
52
+ * Extracts parameter names from a path string and creates a typed object
53
+ * @example ExtractParams<"/users/:id/posts/:postId"> → { id: string; postId: string }
154
54
  */
155
- type SwaggerBodyType = "json" | "form-data" | "urlencoded";
55
+ type ExtractParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
56
+ [K in Param | keyof ExtractParams<Rest>]: string;
57
+ } : T extends `${infer _Start}:${infer Param}` ? {
58
+ [K in Param]: string;
59
+ } : Record<string, never>;
156
60
  /**
157
- * JSONSchema type for OpenAPI/AJV-compatible schemas
61
+ * Helper type to infer the output type from a Zod schema, TypeBox schema, or any schema with _output
158
62
  */
159
- type JSONSchema = {
160
- $id?: string;
161
- $schema?: string;
162
- type?: string | string[];
163
- properties?: Record<string, JSONSchema>;
164
- items?: JSONSchema | JSONSchema[];
165
- required?: string[];
166
- enum?: any[];
167
- allOf?: JSONSchema[];
168
- oneOf?: JSONSchema[];
169
- anyOf?: JSONSchema[];
170
- not?: JSONSchema;
171
- additionalProperties?: boolean | JSONSchema;
172
- description?: string;
173
- format?: string;
174
- default?: any;
175
- title?: string;
176
- definitions?: Record<string, JSONSchema>;
177
- [key: string]: any;
178
- };
63
+ type InferSchemaType<T> = T extends ZodType ? z.infer<T> : T extends TSchema ? Static<T> : any;
179
64
  /**
180
- * Base options shared across all Swagger UI types
65
+ * Maps a responses object (e.g. { 200: ZodSchema, 404: TypeBoxSchema }) to
66
+ * an inferred type map (e.g. { 200: InferredType200, 404: InferredType404 }).
181
67
  */
182
- type SwaggerGlobalOptionsBase = {
183
- /** The path to the swagger documentation, defaults to /docs for the UI and /docs/json for the raw json */
184
- path?: string;
185
- /** API title */
186
- title?: string;
187
- /** API description */
188
- description?: string;
189
- /** API version */
190
- version?: string;
191
- /** Server URLs */
192
- servers?: string[];
193
- /** Components (schemas, responses, parameters, etc.) */
194
- components?: Record<string, any>;
195
- /** Security schemes (OpenAPI 3.0 style) */
196
- securitySchemes?: Record<string, Security>;
197
- /** OpenID Connect configuration (discovery document) */
198
- openIdConnect?: OpenIDConnectConfig;
199
- /** API tags */
200
- tags?: Record<string, any>;
201
- /** Global security requirements */
202
- security?: Security[];
203
- /** External documentation */
204
- externalDocs?: {
205
- description?: string;
206
- url: string;
207
- };
208
- /** Info object (detailed metadata) */
209
- info?: {
210
- title: string;
211
- description?: string;
212
- version: string;
213
- termsOfService?: string;
214
- contact?: {
215
- name?: string;
216
- url?: string;
217
- email?: string;
218
- };
219
- license?: {
220
- name: string;
221
- url?: string;
222
- };
223
- };
224
- /**
225
- * OpenAPI models to be shown in the documentation. Must be valid OpenAPI/AJV JSONSchema objects.
226
- */
227
- models?: Record<string, JSONSchema> | JSONSchema[];
68
+ type InferResponseMap<T extends Record<number, RequestSchema>> = {
69
+ [K in keyof T]: InferSchemaType<T[K]>;
228
70
  };
229
71
  /**
230
- * Global documentation options for the API (OpenAPI/Swagger style)
72
+ * Infers the typed body from a schema. Returns `unknown` if no schema is provided.
231
73
  */
232
- type SwaggerGlobalOptions = (SwaggerGlobalOptionsBase & {
233
- /** Type of Swagger UI to use, one of 'standard', 'redoc', 'rapidoc', 'scalar', or 'elements'. Defaults to 'standard'. */
234
- type?: Exclude<SwaggerUIType, "custom">;
235
- }) | (SwaggerGlobalOptionsBase & {
236
- /** Type of Swagger UI to use. When set to 'custom', customUIGenerator is required. */
237
- type: "custom";
238
- /** Custom UI generator function. Required when type is 'custom'. Must return a string of HTML that uses the given specUrl. */
239
- customUIGenerator: CustomUIGenerator;
240
- });
74
+ type InferBodyType<T> = T extends RequestSchema ? ValidatedData<T> : unknown;
241
75
  /**
242
- * Route-specific documentation options (for individual endpoints)
76
+ * Infers the typed query from a schema. Returns `Record<string, string>` if no schema is provided.
243
77
  */
244
- type SwaggerRouteOptions<TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>> = {
245
- /** Service category where the route belongs to */
246
- service?: string;
247
- /** Name of the route */
248
- name?: string;
249
- /** Responses for this route */
250
- responses?: TResponses;
251
- /** Errors for this route */
252
- errors?: Record<number, RequestSchema>;
253
- /** Security requirements for this route */
254
- security?: Security[] | Security;
255
- /** Description of the route */
256
- description?: string;
257
- /** Deprecated flag */
258
- deprecated?: boolean;
259
- /** Exclude from swagger */
260
- excludeFromSwagger?: boolean;
261
- /**
262
- * The request body type for this route. Allowed values: 'json', 'form-data', 'urlencoded'. Defaults to 'json'.
263
- */
264
- bodyType?: SwaggerBodyType;
265
- };
266
- type OAuth2Flows = {
267
- implicit?: OAuth2Flow;
268
- authorizationCode?: OAuth2Flow;
269
- clientCredentials?: OAuth2Flow;
270
- password?: OAuth2Flow;
271
- };
272
- type OAuth2Flow = {
273
- authorizationUrl?: string;
274
- tokenUrl?: string;
275
- refreshUrl?: string;
276
- scopes: Record<string, string>;
277
- };
278
- type OpenIDConnectConfig = {
279
- issuer: string;
280
- authorizationEndpoint: string;
281
- tokenEndpoint: string;
282
- userinfoEndpoint?: string;
283
- jwksUri: string;
284
- endSessionEndpoint?: string;
285
- introspectionEndpoint?: string;
286
- revocationEndpoint?: string;
287
- scopesSupported?: string[];
288
- responseTypesSupported?: string[];
289
- grantTypesSupported?: string[];
290
- tokenEndpointAuthMethodsSupported?: string[];
291
- subjectTypesSupported?: string[];
292
- idTokenSigningAlgValuesSupported?: string[];
293
- claimsSupported?: string[];
294
- codeChallengeMethodsSupported?: string[];
295
- };
296
- type Security = BearerOptions | ApiKeyOptions | OAuth2Options | OpenIdConnectOptions;
297
- type BearerOptions = {
298
- type: "bearer";
299
- bearerFormat?: string;
300
- description?: string;
301
- };
302
- type ApiKeyOptions = {
303
- type: "apiKey";
304
- name: string;
305
- in: "header" | "query" | "cookie";
306
- description?: string;
307
- };
308
- type OAuth2Options = {
309
- type: "oauth2";
310
- flows: OAuth2Flows;
311
- description?: string;
312
- name?: string;
313
- };
314
- type OpenIdConnectOptions = {
315
- type: "openIdConnect";
316
- openIdConnectUrl: string;
317
- description?: string;
318
- name?: string;
319
- };
320
-
78
+ type InferQueryType<T> = T extends RequestSchema ? ValidatedData<T> : Record<string, string>;
321
79
  /**
322
- * Decorator to mark a class as a controller, routes defined in the controller will be registered at import time when calling the `listen` method.
323
- * You can customize the path pattern for controller imports in the server options `controllerPatterns`
324
- * @param path - The path pattern for the controller.
325
- * @param swaggerOptions - The swagger options for the controller that will be applied to all routes defined in the controller. Controller options will override route options.
326
- * @swagger If swagger is enabled, the default service name for all routes defined in the controller will be the controller name.
327
- * @swagger For naming commodity, the default service name will remove the "Controller" suffix if it exists. e.g. "UserController" -> "User"
80
+ * Extracts the body type for a specific HTTP status code from a response map.
81
+ * When the status code has a schema defined, enforces exact type matching.
82
+ * Defaults to `any` when the status code is not present in the map.
328
83
  */
329
- declare const controller: (path?: string, swaggerOptions?: SwaggerRouteOptions) => (target: any) => void;
84
+ type ResponseBodyForStatus<TMap, TStatus extends number> = TStatus extends keyof TMap ? 0 extends 1 & TMap[TStatus] ? any : {
85
+ [K in keyof TMap[TStatus]]: TMap[TStatus][K];
86
+ } : any;
330
87
 
331
88
  type SyncOrAsync<T = void> = T | Promise<T>;
332
89
 
@@ -398,8 +155,10 @@ type FilePluginOptions = {
398
155
  * It also contains the validation methods.
399
156
  *
400
157
  * @template Params - The path parameters type (automatically extracted from route)
158
+ * @template TBody - The typed body (inferred from body schema when provided)
159
+ * @template TQuery - The typed query (inferred from query schema when provided)
401
160
  */
402
- declare class Request<Params extends Record<string, string> = any> {
161
+ declare class Request<Params extends Record<string, string> = any, TBody = unknown, TQuery extends Record<string, any> = Record<string, string>> {
403
162
  #private;
404
163
  /**
405
164
  * Creates a new request object from a Web API Request object.
@@ -489,22 +248,24 @@ declare class Request<Params extends Record<string, string> = any> {
489
248
  * The parsed body of the request from the body parser middleware.
490
249
  * If body parser middleware is not used, this will be undefined.
491
250
  *
492
- * Type is `unknown` to enforce validation before use.
493
- * Use validation methods or decorators to get typed body:
494
- * - `req.validate(schema)` - Validates and returns typed body
495
- * - `@validate.body(schema)` - Decorator for automatic validation
496
- *
251
+ * Type is `unknown` by default to enforce validation or casting before use.
252
+ * @imperative while using router directly, when using body property, the type is inferred from the schema provided in the route options.
497
253
  * @example
254
+ * ```typescript
255
+ * router.post("/", { body: z.object({ name: z.string() }) }, async (req, res) => {
256
+ * return res.json({ name: req.body.name });
257
+ * });
258
+ * ```
259
+ * @decorator When using the validate decorator, the validated data is appended to the function parameters.
498
260
  * ```ts
499
- * // With validation
500
- * const validBody = req.validate(mySchema);
501
- * // Now validBody is properly typed
502
- *
503
- * // Without validation (not recommended)
504
- * const unsafeBody = req.body as MyType; // Requires type assertion
261
+ * @post("/")
262
+ * @validate.body(z.object({ name: z.string() }))
263
+ * async createUser(req: Request, res: Response, body: z.infer<typeof z.object({ name: z.string() })>) {
264
+ * return res.json({ name: body.name });
265
+ * }
505
266
  * ```
506
267
  */
507
- body: unknown;
268
+ body: TBody;
508
269
  /**
509
270
  * Flag indicating if the body has been read/parsed
510
271
  */
@@ -616,25 +377,11 @@ declare class Request<Params extends Record<string, string> = any> {
616
377
  *
617
378
  * ## Type Safety
618
379
  *
619
- * Query values are **untyped strings**. For type-safe query parameters:
620
- * - Use `req.validateQuery(schema)` for runtime validation
621
- * - Use `@validate.query(schema)` decorator for automatic validation
622
- *
623
- * @example
624
- * ```ts
625
- * // Basic usage
626
- * req.query.name // string | undefined
627
- *
628
- * // With validation for type safety
629
- * const validated = req.validateQuery(z.object({
630
- * page: z.coerce.number(),
631
- * limit: z.coerce.number(),
632
- * }));
633
- * validated.page // number
634
- * ```
380
+ * When a `query` schema is provided in route options, the type is automatically
381
+ * inferred from the schema.
635
382
  */
636
- get query(): Record<string, string>;
637
- set query(value: Record<string, string>);
383
+ get query(): TQuery;
384
+ set query(value: TQuery);
638
385
  /**
639
386
  * Set the raw query string (called by server implementations)
640
387
  * @internal
@@ -658,19 +405,564 @@ declare class Request<Params extends Record<string, string> = any> {
658
405
  */
659
406
  validateQuery<T extends RequestSchema>(inputSchema: T, throwErrorOnValidationFail?: boolean): ValidatedData<T>;
660
407
  /**
661
- * Validates the body and query string of the request.
662
- * @param inputSchema - The schema to validate against (Zod schema or JSON Schema).
663
- * @param throwErrorOnValidationFail - If true, throws ValidationError on validation failure. If false, returns the original data.
408
+ * Validates the body and query string of the request.
409
+ * @param inputSchema - The schema to validate against (Zod schema or JSON Schema).
410
+ * @param throwErrorOnValidationFail - If true, throws ValidationError on validation failure. If false, returns the original data.
411
+ */
412
+ validateAll<T extends RequestSchema>(inputSchema: T, throwErrorOnValidationFail?: boolean): ValidatedData<T>;
413
+ /**
414
+ * Sets the Node.js IncomingMessage for lazy body reading.
415
+ * Used internally by the Node.js server implementation.
416
+ * @param req - The Node.js IncomingMessage
417
+ * @internal
418
+ */
419
+ setNodeRequest(req: IncomingMessage): void;
420
+ }
421
+
422
+ /**
423
+ * Behavior when cache lock cannot be acquired (thundering herd protection).
424
+ * - `wait`: Poll for cache until timeout, then execute handler (default)
425
+ * - `bypass`: Execute handler immediately without waiting
426
+ * - `fail`: Return 503 Service Unavailable immediately
427
+ */
428
+ type LockBehavior = "wait" | "bypass" | "fail";
429
+ /**
430
+ * Specifies which request data to include in the cache key.
431
+ * - `true`: include all fields
432
+ * - `string[]`: include only the specified field names
433
+ *
434
+ * Params from the URL path are always included automatically.
435
+ */
436
+ interface CacheKeyIncludes {
437
+ /** Include request body fields in cache key */
438
+ body?: boolean | string[];
439
+ /** Include query string fields in cache key */
440
+ query?: boolean | string[];
441
+ /** Include request headers in cache key */
442
+ headers?: boolean | string[];
443
+ /**
444
+ * Custom discriminator function. The return value is hashed and appended to
445
+ * the cache key, allowing arbitrary request data (e.g. auth context, tenant)
446
+ * to be used as a cache dimension.
447
+ *
448
+ * @example
449
+ * include: { fromRequest: (req) => req.headers.get("x-tenant-id") }
450
+ */
451
+ fromRequest?: (req: Request) => SyncOrAsync<unknown>;
452
+ }
453
+ /**
454
+ * Type-safe cache key includes when schemas are available (router inline config).
455
+ * Allows picking specific fields from body/query schemas.
456
+ * `fromRequest` receives a fully-typed Request so body and query fields are inferred.
457
+ */
458
+ type TypedCacheKeyIncludes<TBody extends RequestSchema | unknown = unknown, TQuery extends RequestSchema | unknown = unknown, TPath extends string = string> = {
459
+ body?: TBody extends RequestSchema ? boolean | (keyof InferSchemaType<TBody>)[] : boolean | string[];
460
+ query?: TQuery extends RequestSchema ? boolean | (keyof InferSchemaType<TQuery>)[] : boolean | string[];
461
+ headers?: boolean | string[];
462
+ /**
463
+ * Custom discriminator function. Receives the fully-typed request and must
464
+ * return a value that is hashed into the cache key.
465
+ *
466
+ * @example
467
+ * include: { fromRequest: (req) => req.body.tenantId }
468
+ */
469
+ fromRequest?: (req: Request<ExtractParams<TPath>, InferBodyType<TBody>, InferQueryType<TQuery> extends Record<string, any> ? InferQueryType<TQuery> : Record<string, unknown>>) => SyncOrAsync<unknown>;
470
+ };
471
+ /**
472
+ * Cache configuration for a route.
473
+ *
474
+ * @example
475
+ * ```typescript
476
+ * // Decorator usage
477
+ * @cache({ ttl: 60, tags: ['chat-messages'] })
478
+ *
479
+ * // Inline router usage
480
+ * router.get('/users', {
481
+ * cache: { ttl: 120, include: { query: ['page', 'limit'] } }
482
+ * }, handler)
483
+ * ```
484
+ */
485
+ interface CacheRouteConfig {
486
+ /** Time-to-live in seconds (max 86400 = 24 hours) */
487
+ ttl: number;
488
+ /** Enable gzip compression for responses larger than compressionThreshold */
489
+ useCompression?: boolean;
490
+ /** Tags for bulk invalidation via server.cache.invalidate(tags) */
491
+ tags?: string[];
492
+ /** Behavior when lock cannot be acquired (default: 'wait') */
493
+ lockBehavior?: LockBehavior;
494
+ /**
495
+ * Specifies which request data to include in the cache key.
496
+ * Params are always included automatically.
497
+ * When omitted, body is included by default for backward compatibility.
498
+ */
499
+ include?: CacheKeyIncludes;
500
+ }
501
+ /**
502
+ * Type-safe cache route config for inline router usage with schema inference.
503
+ */
504
+ type TypedCacheRouteConfig<TBody extends RequestSchema | unknown = unknown, TQuery extends RequestSchema | unknown = unknown, TPath extends string = string> = Omit<CacheRouteConfig, "include"> & {
505
+ include?: TypedCacheKeyIncludes<TBody, TQuery, TPath>;
506
+ };
507
+ /**
508
+ * Cache statistics for monitoring.
509
+ */
510
+ interface CacheStats {
511
+ /** Total cache hits */
512
+ hits: number;
513
+ /** Total cache misses */
514
+ misses: number;
515
+ /** Hit rate (0.0 - 1.0) */
516
+ hitRate: number;
517
+ /** Total keys invalidated */
518
+ invalidations: number;
519
+ }
520
+ /**
521
+ * Cache service interface exposed on the server instance.
522
+ *
523
+ * Access via `server.cache` after cache is configured in server options.
524
+ *
525
+ * @example
526
+ * ```typescript
527
+ * // Get cached value
528
+ * const data = await server.cache.get<UserData>('cache:user:123:/api/users:abc')
529
+ *
530
+ * // Invalidate by tags
531
+ * await server.cache.invalidate(['user:123', 'chat-messages'])
532
+ *
533
+ * // Get stats
534
+ * const stats = server.cache.getStats()
535
+ * console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(2)}%`)
536
+ * ```
537
+ */
538
+ interface CacheServiceInterface {
539
+ /** Get a cached value by key */
540
+ get<T = unknown>(key: string): Promise<T | null>;
541
+ /** Set a cached value */
542
+ set(key: string, value: unknown, ttl: number, opts?: {
543
+ compressed?: boolean;
544
+ tags?: string[];
545
+ }): Promise<void>;
546
+ /** Invalidate all cache entries with any of the given tags */
547
+ invalidate(tags: string[]): Promise<number>;
548
+ /** Invalidate a specific cache key */
549
+ invalidateKey(key: string): Promise<boolean>;
550
+ /** Invalidate all keys matching a pattern (e.g., 'cache:user:123:*') */
551
+ invalidatePattern(pattern: string): Promise<number>;
552
+ /** Get cache statistics */
553
+ getStats(): CacheStats;
554
+ }
555
+ /**
556
+ * Options for configuring the cache system in ServerOptions.
557
+ */
558
+ interface CachePluginOptions {
559
+ /**
560
+ * The cache provider to use.
561
+ * - `'memory'`: built-in in-memory cache (default)
562
+ * - `'redis'`: Redis-backed cache (requires ioredis peer dependency)
563
+ * - `CacheProvider`: custom provider instance
564
+ */
565
+ provider?: "memory" | "redis" | CacheProvider;
566
+ /** Redis connection options (only used when provider is 'redis') */
567
+ redis?: CacheRedisOptions;
568
+ /** Default TTL in seconds (default: 300) */
569
+ defaultTtl?: number;
570
+ /** Minimum response size in bytes to apply compression (default: 1024) */
571
+ compressionThreshold?: number;
572
+ /** Key prefix for all cache entries (default: 'cache') */
573
+ keyPrefix?: string;
574
+ /** Enable statistics tracking (default: true) */
575
+ enableStats?: boolean;
576
+ /** Lock timeout in milliseconds for thundering herd protection (default: 5000) */
577
+ lockTimeout?: number;
578
+ /** Default behavior when lock cannot be acquired (default: 'wait') */
579
+ lockBehavior?: LockBehavior;
580
+ }
581
+ /**
582
+ * Redis connection options for the Redis cache provider.
583
+ */
584
+ interface CacheRedisOptions {
585
+ /** Redis host (default: 'localhost') */
586
+ host?: string;
587
+ /** Redis port (default: 6379) */
588
+ port?: number;
589
+ /** Redis password */
590
+ password?: string;
591
+ /** Redis database number (default: 0) */
592
+ db?: number;
593
+ /** Redis key prefix */
594
+ keyPrefix?: string;
595
+ /** Full Redis connection URL (overrides host/port/password/db) */
596
+ url?: string;
597
+ }
598
+ /**
599
+ * Internal resolved plugin options with defaults applied.
600
+ */
601
+ interface CachePluginOptionsResolved {
602
+ defaultTtl: number;
603
+ compressionThreshold: number;
604
+ keyPrefix: string;
605
+ enableStats: boolean;
606
+ lockTimeout: number;
607
+ lockBehavior: LockBehavior;
608
+ }
609
+ /**
610
+ * Abstract cache provider interface.
611
+ * Implement this to create a custom cache backend.
612
+ *
613
+ * @example
614
+ * ```typescript
615
+ * class MyCustomProvider implements CacheProvider {
616
+ * async get(key: string) { ... }
617
+ * async set(key: string, value: string, ttlSeconds: number) { ... }
618
+ * // ...
619
+ * }
620
+ *
621
+ * const server = new Server({
622
+ * cache: { provider: new MyCustomProvider() }
623
+ * })
624
+ * ```
625
+ */
626
+ interface CacheProvider {
627
+ /** Get a raw string value by key */
628
+ get(key: string): Promise<string | null>;
629
+ /** Set a raw string value with TTL in seconds */
630
+ set(key: string, value: string, ttlSeconds: number): Promise<void>;
631
+ /** Delete a single key, returns true if key existed */
632
+ del(key: string): Promise<boolean>;
633
+ /** Delete multiple keys, returns count of deleted keys */
634
+ delMany(keys: string[]): Promise<number>;
635
+ /** Add members to a set key with optional TTL */
636
+ addToSet(key: string, members: string[], ttlSeconds?: number): Promise<void>;
637
+ /** Get all members of a set */
638
+ getSetMembers(key: string): Promise<string[]>;
639
+ /** Acquire a lock (NX semantics), returns true if acquired */
640
+ acquireLock(key: string, ttlMs: number): Promise<boolean>;
641
+ /** Release a previously acquired lock */
642
+ releaseLock(key: string): Promise<void>;
643
+ /** Scan keys matching a glob pattern, yields batches */
644
+ scan(pattern: string): AsyncIterable<string[]>;
645
+ /** Disconnect and clean up resources */
646
+ disconnect(): Promise<void>;
647
+ }
648
+
649
+ /**
650
+ * Decorator to enable caching for a controller route handler.
651
+ *
652
+ * Stores cache configuration in MetadataStore, which is processed
653
+ * by the `@controller` decorator to inject cache middleware.
654
+ *
655
+ * **Note:** `include` field picks are not type-safe in decorators since
656
+ * the decorator doesn't have access to body/query schema types.
657
+ * Use the inline router config with schemas for type-safe picks.
658
+ *
659
+ * @param config - Cache configuration for the route
660
+ *
661
+ * @example
662
+ * ```typescript
663
+ * import { controller, get, cache } from "balda";
664
+ *
665
+ * @controller("/api/users")
666
+ * class UserController {
667
+ * @get("/")
668
+ * @cache({ ttl: 120 })
669
+ * async listUsers(req, res) {
670
+ * return await db.users.findAll();
671
+ * }
672
+ *
673
+ * @get("/:id")
674
+ * @cache({
675
+ * ttl: 60,
676
+ * tags: ["users"],
677
+ * include: { query: ["fields"] },
678
+ * })
679
+ * async getUser(req, res) {
680
+ * return await db.users.findById(req.params.id);
681
+ * }
682
+ * }
683
+ * ```
684
+ */
685
+ declare const cache: (config: CacheRouteConfig) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
686
+
687
+ type CronSchedule = {
688
+ name: string;
689
+ args: Parameters<typeof schedule>;
690
+ };
691
+ type CronScheduleParams = Parameters<typeof schedule>;
692
+ type CronUIOptions = {
693
+ path: string;
694
+ };
695
+
696
+ /**
697
+ * Decorator to schedule a cron job. Must be applied to a class method. By default, the cron job will not overlap with other cron jobs of the same type.
698
+ * ```ts
699
+ * @cron('* * * * *', { timezone: 'Europe/Istanbul' })
700
+ * async test() {
701
+ * console.log('test');
702
+ * }
703
+ * ```
704
+ */
705
+ declare const cron: (schedule: CronScheduleParams[0], options?: CronScheduleParams[2]) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
706
+
707
+ type FlagType = "boolean" | "string" | "number" | "list";
708
+ type InferFlagType<T extends FlagType> = T extends "boolean" ? boolean : T extends "string" ? string : T extends "number" ? number : T extends "list" ? string[] : never;
709
+ type ArgOptions = {
710
+ /**
711
+ * The description of the argument.
712
+ */
713
+ description?: string;
714
+ /**
715
+ * Whether the argument is required.
716
+ * @default false
717
+ */
718
+ required?: boolean;
719
+ /**
720
+ * The default value of the argument.
721
+ */
722
+ defaultValue?: string;
723
+ /**
724
+ * A function to parse the argument value.
725
+ * @default The value is returned as is.
726
+ */
727
+ parse?: (value: string) => string;
728
+ };
729
+ type FlagOptions<T extends FlagType> = {
730
+ /**
731
+ * The description of the flag.
732
+ */
733
+ description?: string;
734
+ /**
735
+ * The type of the flag.
736
+ */
737
+ type: T;
738
+ /**
739
+ * The name of the flag.
740
+ * @default The name of the field.
741
+ */
742
+ name?: string;
743
+ /**
744
+ * The aliases of the flag.
745
+ */
746
+ aliases?: string[] | string;
747
+ /**
748
+ * The default value of the flag.
749
+ */
750
+ defaultValue?: InferFlagType<T>;
751
+ /**
752
+ * Whether the flag is required.
753
+ * @default false
754
+ */
755
+ required?: boolean;
756
+ /**
757
+ * A function to parse the flag value.
758
+ * @default The value is returned as is.
759
+ */
760
+ parse?: (value: any) => InferFlagType<T>;
761
+ };
762
+
763
+ /**
764
+ * Decorator to mark a field of a command class as an argument.
765
+ * @param options - The options for the argument.
766
+ * @warning Arguments are evaluated in the order they are defined in the Command class.
767
+ */
768
+ declare const arg: (options: ArgOptions) => (target: any, propertyKey: string) => void;
769
+
770
+ declare const flag: {
771
+ <T extends FlagType>(options: FlagOptions<T>): (target: any, propertyKey: string) => void;
772
+ boolean(options: Omit<FlagOptions<"boolean">, "type">): (target: any, propertyKey: string) => void;
773
+ string(options: Omit<FlagOptions<"string">, "type">): (target: any, propertyKey: string) => void;
774
+ number(options: Omit<FlagOptions<"number">, "type">): (target: any, propertyKey: string) => void;
775
+ list(options: Omit<FlagOptions<"list">, "type">): (target: any, propertyKey: string) => void;
776
+ array(options: Omit<FlagOptions<"list">, "type">): (target: any, propertyKey: string) => void;
777
+ };
778
+
779
+ /**
780
+ * Type of Swagger UI to use
781
+ */
782
+ type SwaggerUIType = "standard" | "rapidoc" | "scalar" | "elements" | "custom";
783
+ type HTMLString = string;
784
+ /**
785
+ * Custom UI generator function that takes the spec URL and global options and returns HTML
786
+ */
787
+ type CustomUIGenerator = (specUrl: string, globalOptions: SwaggerGlobalOptions) => HTMLString;
788
+ /**
789
+ * Type of request body for a route
790
+ */
791
+ type SwaggerBodyType = "json" | "form-data" | "urlencoded";
792
+ /**
793
+ * JSONSchema type for OpenAPI/AJV-compatible schemas
794
+ */
795
+ type JSONSchema = {
796
+ $id?: string;
797
+ $schema?: string;
798
+ type?: string | string[];
799
+ properties?: Record<string, JSONSchema>;
800
+ items?: JSONSchema | JSONSchema[];
801
+ required?: string[];
802
+ enum?: any[];
803
+ allOf?: JSONSchema[];
804
+ oneOf?: JSONSchema[];
805
+ anyOf?: JSONSchema[];
806
+ not?: JSONSchema;
807
+ additionalProperties?: boolean | JSONSchema;
808
+ description?: string;
809
+ format?: string;
810
+ default?: any;
811
+ title?: string;
812
+ definitions?: Record<string, JSONSchema>;
813
+ [key: string]: any;
814
+ };
815
+ /**
816
+ * Base options shared across all Swagger UI types
817
+ */
818
+ type SwaggerGlobalOptionsBase = {
819
+ /** The path to the swagger documentation, defaults to /docs for the UI and /docs/json for the raw json */
820
+ path?: string;
821
+ /** API title */
822
+ title?: string;
823
+ /** API description */
824
+ description?: string;
825
+ /** API version */
826
+ version?: string;
827
+ /** Server URLs */
828
+ servers?: string[];
829
+ /** Components (schemas, responses, parameters, etc.) */
830
+ components?: Record<string, any>;
831
+ /** Security schemes (OpenAPI 3.0 style) */
832
+ securitySchemes?: Record<string, Security>;
833
+ /** OpenID Connect configuration (discovery document) */
834
+ openIdConnect?: OpenIDConnectConfig;
835
+ /** API tags */
836
+ tags?: Record<string, any>;
837
+ /** Global security requirements */
838
+ security?: Security[];
839
+ /** External documentation */
840
+ externalDocs?: {
841
+ description?: string;
842
+ url: string;
843
+ };
844
+ /** Info object (detailed metadata) */
845
+ info?: {
846
+ title: string;
847
+ description?: string;
848
+ version: string;
849
+ termsOfService?: string;
850
+ contact?: {
851
+ name?: string;
852
+ url?: string;
853
+ email?: string;
854
+ };
855
+ license?: {
856
+ name: string;
857
+ url?: string;
858
+ };
859
+ };
860
+ /**
861
+ * OpenAPI models to be shown in the documentation. Must be valid OpenAPI/AJV JSONSchema objects.
664
862
  */
665
- validateAll<T extends RequestSchema>(inputSchema: T, throwErrorOnValidationFail?: boolean): ValidatedData<T>;
863
+ models?: Record<string, JSONSchema> | JSONSchema[];
864
+ };
865
+ /**
866
+ * Global documentation options for the API (OpenAPI/Swagger style)
867
+ */
868
+ type SwaggerGlobalOptions = (SwaggerGlobalOptionsBase & {
869
+ /** Type of Swagger UI to use, one of 'standard', 'redoc', 'rapidoc', 'scalar', or 'elements'. Defaults to 'standard'. */
870
+ type?: Exclude<SwaggerUIType, "custom">;
871
+ }) | (SwaggerGlobalOptionsBase & {
872
+ /** Type of Swagger UI to use. When set to 'custom', customUIGenerator is required. */
873
+ type: "custom";
874
+ /** Custom UI generator function. Required when type is 'custom'. Must return a string of HTML that uses the given specUrl. */
875
+ customUIGenerator: CustomUIGenerator;
876
+ });
877
+ /**
878
+ * Route-specific documentation options (for individual endpoints)
879
+ */
880
+ type SwaggerRouteOptions = {
881
+ /** Service category where the route belongs to */
882
+ service?: string;
883
+ /** Name of the route */
884
+ name?: string;
885
+ /** Responses for this route (used by decorators) */
886
+ responses?: Record<number, RequestSchema>;
887
+ /** Errors for this route */
888
+ errors?: Record<number, RequestSchema>;
889
+ /** Security requirements for this route */
890
+ security?: Security[] | Security;
891
+ /** Description of the route */
892
+ description?: string;
893
+ /** Deprecated flag */
894
+ deprecated?: boolean;
895
+ /** Exclude from swagger */
896
+ excludeFromSwagger?: boolean;
666
897
  /**
667
- * Sets the Node.js IncomingMessage for lazy body reading.
668
- * Used internally by the Node.js server implementation.
669
- * @param req - The Node.js IncomingMessage
670
- * @internal
898
+ * The request body type for this route. Allowed values: 'json', 'form-data', 'urlencoded'. Defaults to 'json'.
671
899
  */
672
- setNodeRequest(req: IncomingMessage): void;
673
- }
900
+ bodyType?: SwaggerBodyType;
901
+ };
902
+ type OAuth2Flows = {
903
+ implicit?: OAuth2Flow;
904
+ authorizationCode?: OAuth2Flow;
905
+ clientCredentials?: OAuth2Flow;
906
+ password?: OAuth2Flow;
907
+ };
908
+ type OAuth2Flow = {
909
+ authorizationUrl?: string;
910
+ tokenUrl?: string;
911
+ refreshUrl?: string;
912
+ scopes: Record<string, string>;
913
+ };
914
+ type OpenIDConnectConfig = {
915
+ issuer: string;
916
+ authorizationEndpoint: string;
917
+ tokenEndpoint: string;
918
+ userinfoEndpoint?: string;
919
+ jwksUri: string;
920
+ endSessionEndpoint?: string;
921
+ introspectionEndpoint?: string;
922
+ revocationEndpoint?: string;
923
+ scopesSupported?: string[];
924
+ responseTypesSupported?: string[];
925
+ grantTypesSupported?: string[];
926
+ tokenEndpointAuthMethodsSupported?: string[];
927
+ subjectTypesSupported?: string[];
928
+ idTokenSigningAlgValuesSupported?: string[];
929
+ claimsSupported?: string[];
930
+ codeChallengeMethodsSupported?: string[];
931
+ };
932
+ type Security = BearerOptions | ApiKeyOptions | OAuth2Options | OpenIdConnectOptions;
933
+ type BearerOptions = {
934
+ type: "bearer";
935
+ bearerFormat?: string;
936
+ description?: string;
937
+ };
938
+ type ApiKeyOptions = {
939
+ type: "apiKey";
940
+ name: string;
941
+ in: "header" | "query" | "cookie";
942
+ description?: string;
943
+ };
944
+ type OAuth2Options = {
945
+ type: "oauth2";
946
+ flows: OAuth2Flows;
947
+ description?: string;
948
+ name?: string;
949
+ };
950
+ type OpenIdConnectOptions = {
951
+ type: "openIdConnect";
952
+ openIdConnectUrl: string;
953
+ description?: string;
954
+ name?: string;
955
+ };
956
+
957
+ /**
958
+ * Decorator to mark a class as a controller, routes defined in the controller will be registered at import time when calling the `listen` method.
959
+ * You can customize the path pattern for controller imports in the server options `controllerPatterns`
960
+ * @param path - The path pattern for the controller.
961
+ * @param swaggerOptions - The swagger options for the controller that will be applied to all routes defined in the controller. Controller options will override route options.
962
+ * @swagger If swagger is enabled, the default service name for all routes defined in the controller will be the controller name.
963
+ * @swagger For naming commodity, the default service name will remove the "Controller" suffix if it exists. e.g. "UserController" -> "User"
964
+ */
965
+ declare const controller: (path?: string, swaggerOptions?: SwaggerRouteOptions) => (target: any) => void;
674
966
 
675
967
  /**
676
968
  * Cookie options for setting cookies
@@ -747,35 +1039,9 @@ type CookieMiddlewareOptions = {
747
1039
  sign?: boolean;
748
1040
  };
749
1041
 
750
- /**
751
- * Extracts parameter names from a path string and creates a typed object
752
- * @example ExtractParams<"/users/:id/posts/:postId"> → { id: string; postId: string }
753
- */
754
- type ExtractParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? {
755
- [K in Param | keyof ExtractParams<Rest>]: string;
756
- } : T extends `${infer _Start}:${infer Param}` ? {
757
- [K in Param]: string;
758
- } : Record<string, never>;
759
- /**
760
- * Helper type to infer the output type from a Zod schema, TypeBox schema, or any schema with _output
761
- */
762
- type InferSchemaType<T> = T extends ZodType ? z.infer<T> : T extends TSchema ? Static<T> : any;
763
- /**
764
- * Maps a responses object (e.g. { 200: ZodSchema, 404: TypeBoxSchema }) to
765
- * an inferred type map (e.g. { 200: InferredType200, 404: InferredType404 }).
766
- */
767
- type InferResponseMap<T extends Record<number, RequestSchema>> = {
768
- [K in keyof T]: InferSchemaType<T[K]>;
769
- };
770
- /**
771
- * Extracts the body type for a specific HTTP status code from a response map.
772
- * Defaults to `any` when the status code is not present in the map.
773
- */
774
- type ResponseBodyForStatus<TMap, TStatus extends number> = TStatus extends keyof TMap ? TMap[TStatus] : any;
775
-
776
1042
  /**
777
1043
  * The response object with per-status-code type-safe response bodies.
778
- * When response schemas are provided (e.g. via swagger.responses), each shorthand
1044
+ * When response schemas are provided (e.g. via the `responses` route option), each shorthand
779
1045
  * method (ok, created, notFound, etc.) is typed to its corresponding status code schema.
780
1046
  * @template TResponseMap - Maps HTTP status codes to their inferred body types (defaults to Record<number, any>)
781
1047
  */
@@ -1291,7 +1557,7 @@ declare class Router {
1291
1557
  body?: RequestSchema;
1292
1558
  query?: RequestSchema;
1293
1559
  all?: RequestSchema;
1294
- }, swaggerOptions?: SwaggerRouteOptions): void;
1560
+ }, swaggerOptions?: SwaggerRouteOptions, responses?: Record<number, RequestSchema>): void;
1295
1561
  /**
1296
1562
  * Find the matching route for the given HTTP method and path.
1297
1563
  * Returns the resolved middleware chain, handler, extracted params, and response schemas; or null if not found.
@@ -1313,37 +1579,37 @@ declare class Router {
1313
1579
  * Register a GET route under this router's base path with type-safe path parameters.
1314
1580
  */
1315
1581
  get<TPath extends string = string>(path: TPath, handler: ControllerHandler<TPath>): void;
1316
- get<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>>(path: TPath, options: StandardMethodOptions<TResponses>, handler: ControllerHandler<TPath, TResponses>): void;
1582
+ get<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | undefined = undefined, TQuery extends RequestSchema | undefined = undefined>(path: TPath, options: StandardMethodOptions<TResponses, TBody, TQuery, TPath>, handler: ControllerHandler<TPath, TResponses, TBody, TQuery>): void;
1317
1583
  /**
1318
1584
  * Register a POST route under this router's base path with type-safe path parameters.
1319
1585
  */
1320
1586
  post<TPath extends string = string>(path: TPath, handler: ControllerHandler<TPath>): void;
1321
- post<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>>(path: TPath, options: StandardMethodOptions<TResponses>, handler: ControllerHandler<TPath, TResponses>): void;
1587
+ post<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | undefined = undefined, TQuery extends RequestSchema | undefined = undefined>(path: TPath, options: StandardMethodOptions<TResponses, TBody, TQuery, TPath>, handler: ControllerHandler<TPath, TResponses, TBody, TQuery>): void;
1322
1588
  /**
1323
1589
  * Register a PATCH route under this router's base path with type-safe path parameters.
1324
1590
  */
1325
1591
  patch<TPath extends string = string>(path: TPath, handler: ControllerHandler<TPath>): void;
1326
- patch<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>>(path: TPath, options: StandardMethodOptions<TResponses>, handler: ControllerHandler<TPath, TResponses>): void;
1592
+ patch<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | undefined = undefined, TQuery extends RequestSchema | undefined = undefined>(path: TPath, options: StandardMethodOptions<TResponses, TBody, TQuery, TPath>, handler: ControllerHandler<TPath, TResponses, TBody, TQuery>): void;
1327
1593
  /**
1328
1594
  * Register a PUT route under this router's base path with type-safe path parameters.
1329
1595
  */
1330
1596
  put<TPath extends string = string>(path: TPath, handler: ControllerHandler<TPath>): void;
1331
- put<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>>(path: TPath, options: StandardMethodOptions<TResponses>, handler: ControllerHandler<TPath, TResponses>): void;
1597
+ put<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | undefined = undefined, TQuery extends RequestSchema | undefined = undefined>(path: TPath, options: StandardMethodOptions<TResponses, TBody, TQuery, TPath>, handler: ControllerHandler<TPath, TResponses, TBody, TQuery>): void;
1332
1598
  /**
1333
1599
  * Register a DELETE route under this router's base path with type-safe path parameters.
1334
1600
  */
1335
1601
  delete<TPath extends string = string>(path: TPath, handler: ControllerHandler<TPath>): void;
1336
- delete<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>>(path: TPath, options: StandardMethodOptions<TResponses>, handler: ControllerHandler<TPath, TResponses>): void;
1602
+ delete<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | undefined = undefined, TQuery extends RequestSchema | undefined = undefined>(path: TPath, options: StandardMethodOptions<TResponses, TBody, TQuery, TPath>, handler: ControllerHandler<TPath, TResponses, TBody, TQuery>): void;
1337
1603
  /**
1338
1604
  * Register an OPTIONS route under this router's base path with type-safe path parameters.
1339
1605
  */
1340
1606
  options<TPath extends string = string>(path: TPath, handler: ControllerHandler<TPath>): void;
1341
- options<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>>(path: TPath, options: StandardMethodOptions<TResponses>, handler: ControllerHandler<TPath, TResponses>): void;
1607
+ options<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | undefined = undefined, TQuery extends RequestSchema | undefined = undefined>(path: TPath, options: StandardMethodOptions<TResponses, TBody, TQuery, TPath>, handler: ControllerHandler<TPath, TResponses, TBody, TQuery>): void;
1342
1608
  /**
1343
1609
  * Register an HEAD route under this router's base path with type-safe path parameters.
1344
1610
  */
1345
1611
  head<TPath extends string = string>(path: TPath, handler: ControllerHandler<TPath>): void;
1346
- head<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>>(path: TPath, options: StandardMethodOptions<TResponses>, handler: ControllerHandler<TPath, TResponses>): void;
1612
+ head<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | undefined = undefined, TQuery extends RequestSchema | undefined = undefined>(path: TPath, options: StandardMethodOptions<TResponses, TBody, TQuery, TPath>, handler: ControllerHandler<TPath, TResponses, TBody, TQuery>): void;
1347
1613
  /**
1348
1614
  * Create a grouped router that shares a base path and middlewares.
1349
1615
  * The callback receives a child router where routes are defined; routes
@@ -1379,7 +1645,12 @@ interface Route {
1379
1645
  handler: ServerRouteHandler;
1380
1646
  swaggerOptions?: SwaggerRouteOptions;
1381
1647
  /**
1382
- * Compiled response schemas from swagger.responses, indexed by status code.
1648
+ * Response schemas from route options, indexed by status code.
1649
+ * Used by swagger plugin for documentation and fast JSON serialization.
1650
+ */
1651
+ responses?: RouteResponseSchemas;
1652
+ /**
1653
+ * Compiled response schemas, indexed by status code.
1383
1654
  * Used for automatic fast JSON serialization in Response.json()
1384
1655
  */
1385
1656
  responseSchemas?: RouteResponseSchemas;
@@ -1398,6 +1669,74 @@ interface Route {
1398
1669
  */
1399
1670
  type ClientRouter = Omit<Router, "applyGlobalMiddlewaresToAllRoutes" | "addOrUpdate">;
1400
1671
 
1672
+ /**
1673
+ * Core cache service implementation wrapping a CacheProvider.
1674
+ *
1675
+ * Handles all cache operations including:
1676
+ * - Get/Set with optional compression
1677
+ * - Tag-based invalidation
1678
+ * - Pattern-based invalidation
1679
+ * - Thundering herd protection (lock acquisition)
1680
+ * - Statistics tracking
1681
+ */
1682
+ declare class CacheService implements CacheServiceInterface {
1683
+ private readonly log;
1684
+ private readonly provider;
1685
+ private readonly options;
1686
+ private stats;
1687
+ constructor(provider: CacheProvider, options: CachePluginOptionsResolved);
1688
+ /**
1689
+ * Get a cached value by key.
1690
+ * Handles decompression if the entry was stored compressed.
1691
+ */
1692
+ get<T = unknown>(key: string): Promise<T | null>;
1693
+ /**
1694
+ * Set a cached value with optional compression and tag registration.
1695
+ */
1696
+ set(key: string, value: unknown, ttl: number, opts?: {
1697
+ compressed?: boolean;
1698
+ tags?: string[];
1699
+ }): Promise<void>;
1700
+ /**
1701
+ * Invalidate all cache entries with any of the given tags.
1702
+ */
1703
+ invalidate(tags: string[]): Promise<number>;
1704
+ /**
1705
+ * Invalidate a specific cache key.
1706
+ */
1707
+ invalidateKey(key: string): Promise<boolean>;
1708
+ /**
1709
+ * Invalidate all keys matching a pattern.
1710
+ */
1711
+ invalidatePattern(pattern: string): Promise<number>;
1712
+ /**
1713
+ * Acquire a lock for thundering herd protection.
1714
+ * @returns true if lock was acquired, false if already held
1715
+ */
1716
+ acquireLock(key: string): Promise<boolean>;
1717
+ /**
1718
+ * Release a lock after cache population.
1719
+ */
1720
+ releaseLock(key: string): Promise<void>;
1721
+ /**
1722
+ * Wait for cache to be populated by another request.
1723
+ */
1724
+ waitForCache<T>(key: string, timeoutMs: number): Promise<T | null>;
1725
+ /**
1726
+ * Get current cache statistics.
1727
+ */
1728
+ getStats(): CacheStats;
1729
+ /**
1730
+ * Get the underlying cache provider.
1731
+ */
1732
+ getProvider(): CacheProvider;
1733
+ /**
1734
+ * Disconnect the underlying provider.
1735
+ */
1736
+ disconnect(): Promise<void>;
1737
+ private updateHitRate;
1738
+ }
1739
+
1401
1740
  /**
1402
1741
  * The server class that is used to create and manage the server
1403
1742
  */
@@ -1429,6 +1768,7 @@ declare class Server<H extends NodeHttpClient = NodeHttpClient> implements Serve
1429
1768
  get host(): string;
1430
1769
  get routes(): Route[];
1431
1770
  get fs(): typeof nativeFs;
1771
+ get cache(): CacheService;
1432
1772
  hash(data: string): Promise<string>;
1433
1773
  compareHash(hash: string, data: string): Promise<boolean>;
1434
1774
  getEnvironment(): Record<string, string>;
@@ -1477,6 +1817,11 @@ declare class Server<H extends NodeHttpClient = NodeHttpClient> implements Serve
1477
1817
  * @internal
1478
1818
  */
1479
1819
  private bootstrap;
1820
+ /**
1821
+ * Initialize the cache service and embed it on the server instance.
1822
+ * @internal
1823
+ */
1824
+ private initializeCache;
1480
1825
  /**
1481
1826
  * Handles not found routes by delegating to custom handler or default error response
1482
1827
  * Checks if the path exists for other methods and returns 405 if so
@@ -1496,7 +1841,7 @@ declare class Server<H extends NodeHttpClient = NodeHttpClient> implements Serve
1496
1841
  }
1497
1842
 
1498
1843
  declare class MockResponse<T = any> {
1499
- private readonly response;
1844
+ readonly response: Response$1;
1500
1845
  constructor(response: Response$1);
1501
1846
  body(): T;
1502
1847
  statusCode(): number;
@@ -2064,6 +2409,22 @@ type ServerOptions<H extends NodeHttpClient = NodeHttpClient> = {
2064
2409
  * By passing the "path" option, the UI will be enabled at the given path.
2065
2410
  */
2066
2411
  cronUI?: CronUIOptions;
2412
+ /**
2413
+ * Cache configuration for the server.
2414
+ * When provided, enables the cache system and exposes it via `server.cache`.
2415
+ *
2416
+ * @example
2417
+ * ```ts
2418
+ * const server = new Server({
2419
+ * cache: { provider: 'memory', defaultTtl: 300 }
2420
+ * });
2421
+ * // or with Redis
2422
+ * const server = new Server({
2423
+ * cache: { provider: 'redis', redis: { host: 'localhost', port: 6379 } }
2424
+ * });
2425
+ * ```
2426
+ */
2427
+ cache?: CachePluginOptions;
2067
2428
  } & (H extends "https" | "http2-secure" ? HttpsOptions<H> : {});
2068
2429
  /** Internal resolved server options with all required properties */
2069
2430
  type ResolvedServerOptions = {
@@ -2077,6 +2438,7 @@ type ResolvedServerOptions = {
2077
2438
  graphql?: GraphQLOptions;
2078
2439
  abortSignal?: AbortSignal;
2079
2440
  cronUI?: CronUIOptions;
2441
+ cache?: CachePluginOptions;
2080
2442
  };
2081
2443
  type ServerErrorHandler = (req: Request, res: Response$1, next: NextFunction, error: Error) => SyncOrAsync;
2082
2444
  interface ServerInterface {
@@ -2300,16 +2662,19 @@ interface ServerInterface {
2300
2662
  */
2301
2663
  exit: (code?: number) => void;
2302
2664
  }
2303
- type StandardMethodOptions<TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>> = {
2665
+ type StandardMethodOptions<TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | unknown = unknown, TQuery extends RequestSchema | unknown = unknown, TPath extends string = string> = {
2304
2666
  middlewares?: ServerRouteMiddleware[] | ServerRouteMiddleware;
2305
- body?: RequestSchema;
2306
- query?: RequestSchema;
2667
+ body?: TBody;
2668
+ query?: TQuery;
2307
2669
  all?: RequestSchema;
2308
- swagger?: SwaggerRouteOptions<TResponses>;
2670
+ responses?: TResponses;
2671
+ swagger?: SwaggerRouteOptions;
2672
+ /** Cache configuration for this route. Requires cache to be configured in ServerOptions. */
2673
+ cache?: TypedCacheRouteConfig<TBody, TQuery, TPath>;
2309
2674
  };
2310
2675
  type ServerHook = () => SyncOrAsync;
2311
2676
  type SignalEvent = Deno.Signal | NodeJS.Signals;
2312
- type ControllerHandler<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>> = (req: Request<ExtractParams<TPath>>, res: Response$1<InferResponseMap<TResponses>>, ...args: any[]) => ServerHandlerReturnType;
2677
+ type ControllerHandler<TPath extends string = string, TResponses extends Record<number, RequestSchema> = Record<number, RequestSchema>, TBody extends RequestSchema | unknown = unknown, TQuery extends RequestSchema | unknown = unknown> = (req: Request<ExtractParams<TPath>, InferBodyType<TBody>, InferQueryType<TQuery> extends Record<string, any> ? InferQueryType<TQuery> : Record<string, unknown>>, res: Response$1<InferResponseMap<TResponses>>) => ServerHandlerReturnType;
2313
2678
 
2314
2679
  type RunTimeType = "bun" | "node" | "deno";
2315
2680
 
@@ -4031,9 +4396,65 @@ declare class PolicyManager<T extends Record<string, PolicyProvider>> {
4031
4396
 
4032
4397
  declare const createPolicyDecorator: <T extends Record<string, PolicyProvider>>(manager: PolicyManager<T>) => PolicyDecorator<T>;
4033
4398
 
4399
+ /**
4400
+ * In-memory cache provider using Map with TTL expiration.
4401
+ * Suitable for development, testing, and single-instance deployments.
4402
+ */
4403
+ declare class MemoryCacheProvider implements CacheProvider {
4404
+ private store;
4405
+ private sets;
4406
+ private locks;
4407
+ get(key: string): Promise<string | null>;
4408
+ set(key: string, value: string, ttlSeconds: number): Promise<void>;
4409
+ del(key: string): Promise<boolean>;
4410
+ delMany(keys: string[]): Promise<number>;
4411
+ addToSet(key: string, members: string[], ttlSeconds?: number): Promise<void>;
4412
+ getSetMembers(key: string): Promise<string[]>;
4413
+ acquireLock(key: string, ttlMs: number): Promise<boolean>;
4414
+ releaseLock(key: string): Promise<void>;
4415
+ scan(pattern: string): AsyncIterable<string[]>;
4416
+ disconnect(): Promise<void>;
4417
+ }
4418
+
4419
+ /**
4420
+ * Redis-backed cache provider using ioredis (dynamically imported).
4421
+ * Requires `ioredis` as a peer dependency.
4422
+ */
4423
+ declare class RedisCacheProvider implements CacheProvider {
4424
+ private redis;
4425
+ private options;
4426
+ constructor(options?: CacheRedisOptions);
4427
+ /** Lazily connect to Redis on first use */
4428
+ private getClient;
4429
+ get(key: string): Promise<string | null>;
4430
+ set(key: string, value: string, ttlSeconds: number): Promise<void>;
4431
+ del(key: string): Promise<boolean>;
4432
+ delMany(keys: string[]): Promise<number>;
4433
+ addToSet(key: string, members: string[], ttlSeconds?: number): Promise<void>;
4434
+ getSetMembers(key: string): Promise<string[]>;
4435
+ acquireLock(key: string, ttlMs: number): Promise<boolean>;
4436
+ releaseLock(key: string): Promise<void>;
4437
+ scan(pattern: string): AsyncIterable<string[]>;
4438
+ disconnect(): Promise<void>;
4439
+ }
4440
+
4441
+ /**
4442
+ * Response header name for cache status.
4443
+ */
4444
+ declare const CACHE_STATUS_HEADER = "x-cache";
4445
+ /**
4446
+ * Cache status values for response header.
4447
+ */
4448
+ declare enum CacheStatus {
4449
+ Hit = "HIT",
4450
+ Miss = "MISS",
4451
+ Wait = "WAIT",
4452
+ Bypass = "BYPASS"
4453
+ }
4454
+
4034
4455
  /**
4035
4456
  * Singleton main router instance that handles all route registrations inside the balda server
4036
4457
  */
4037
4458
  declare const router: ClientRouter;
4038
4459
 
4039
- export { type AsyncLocalStorageContextSetters, AzureBlobStorageProvider, BaseCron, BasePlugin, type BaseStorageProviderOptions, type BlobStorageProviderOptions, BullMQConfiguration, type BullMQConfigurationOptions, BullMQPubSub, Command, type CommandOptions, CommandRegistry, type CompressionOptions, type CookieMiddlewareOptions, type CorsOptions, type CronSchedule, type CronScheduleParams, CronService, type CronUIOptions, CustomAdapter, type CustomQueueConfiguration, type CustomStorageProviderOptions, CustomTypedQueue, type CustomValidationError, EdgeAdapter, EjsAdapter, type ExtractParams, GraphQL, type GraphQLContext, type GraphQLOptions, type GraphQLResolverFunction, type GraphQLResolverMap, type GraphQLResolverType, type GraphQLResolvers, type GraphQLSchemaInput, type GraphQLTypeDef, HandlebarsAdapter, type HelmetOptions, type HttpMethod, type HttpsOptions, type InferResponseMap, type InferSchemaType, LocalStorageProvider, type LocalStorageProviderOptions, type LogOptions, type LoggerOptions, type MailOptions, MailOptionsBuilder, MailProvider, type MailProviderInterface, Mailer, type MailerInterface, type MailerOptions, type MailerProviderOptions, MemoryPubSub, type MethodOverrideOptions, MockResponse, MockServer, type MockServerOptions, type MqttConnectionOptions, type MqttHandler, type MqttPublishOptions, MqttService, type MqttSubscribeOptions, type MqttSubscription, type MqttTopics, MustacheAdapter, type NextFunction, type NodeHttpClient, type NodeServer as NodeHttpServerClient, PGBossConfiguration, type PGBossConfigurationOptions, PGBossPubSub, type PolicyDecorator, PolicyManager, type PolicyProvider, type PublishTopic, QueueManager, QueueService, type RateLimiterKeyOptions, Request, type RequestSchema, Response$1 as Response, type ResponseBodyForStatus, type RuntimeServer, S3StorageProvider, type S3StorageProviderOptions, SQSConfiguration, type SQSConfigurationOptions, SQSPubSub, type CacheMetrics as SchemaCacheMetrics, type SerializeOptions, Server, type ServerConnectInput, type ServerErrorHandler, type ServerHook, type ServerInterface, type ServerListenCallback, type ServerOptions, type ServerRouteHandler, type ServerRouteMiddleware, type ServerTapOptions, type SessionOptions, type SignalEvent, type StaticPluginOptions, Storage, type StorageInterface, type StorageOptions, type StorageProviderOptions, type TemplateMailOptions, type TimeoutOptions, type TrustProxyOptions, type TypedHandler, TypedQueue, type TypedRouteMetadata, type ValidatedData, type ValidationOptions, arg, asyncLocalStorage, asyncStorage, bullmqQueue, clearAllCaches as clearAllSchemaCaches, commandRegistry, compression, controller, cookie, cors, createExpressAdapter, createPolicyDecorator, createQueue, cron, Server as default, defineQueueConfiguration, del, expressHandler, expressMiddleware, flag, get, getCacheMetrics as getSchemaCacheMetrics, hash, helmet, log, logCacheMetrics as logSchemaCacheMetrics, logger, memoryQueue, methodOverride, middleware, mountExpressRouter, mqtt, patch, pgbossQueue, post, put, rateLimiter, router, serialize, serveStatic, session, setCronGlobalErrorHandler, setMqttGlobalErrorHandler, sqsQueue, timeout as timeoutMw, trustProxy, validate };
4460
+ export { type AsyncLocalStorageContextSetters, AzureBlobStorageProvider, BaseCron, BasePlugin, type BaseStorageProviderOptions, type BlobStorageProviderOptions, BullMQConfiguration, type BullMQConfigurationOptions, BullMQPubSub, CACHE_STATUS_HEADER, type CacheKeyIncludes, type CachePluginOptions, type CacheProvider, type CacheRedisOptions, type CacheRouteConfig, CacheService, type CacheServiceInterface, type CacheStats, CacheStatus, Command, type CommandOptions, CommandRegistry, type CompressionOptions, type CookieMiddlewareOptions, type CorsOptions, type CronSchedule, type CronScheduleParams, CronService, type CronUIOptions, CustomAdapter, type CustomQueueConfiguration, type CustomStorageProviderOptions, CustomTypedQueue, type CustomValidationError, EdgeAdapter, EjsAdapter, type ExtractParams, GraphQL, type GraphQLContext, type GraphQLOptions, type GraphQLResolverFunction, type GraphQLResolverMap, type GraphQLResolverType, type GraphQLResolvers, type GraphQLSchemaInput, type GraphQLTypeDef, HandlebarsAdapter, type HelmetOptions, type HttpMethod, type HttpsOptions, type InferResponseMap, type InferSchemaType, LocalStorageProvider, type LocalStorageProviderOptions, type LockBehavior, type LogOptions, type LoggerOptions, type MailOptions, MailOptionsBuilder, MailProvider, type MailProviderInterface, Mailer, type MailerInterface, type MailerOptions, type MailerProviderOptions, MemoryCacheProvider, MemoryPubSub, type MethodOverrideOptions, MockResponse, MockServer, type MockServerOptions, type MqttConnectionOptions, type MqttHandler, type MqttPublishOptions, MqttService, type MqttSubscribeOptions, type MqttSubscription, type MqttTopics, MustacheAdapter, type NextFunction, type NodeHttpClient, type NodeServer as NodeHttpServerClient, PGBossConfiguration, type PGBossConfigurationOptions, PGBossPubSub, type PolicyDecorator, PolicyManager, type PolicyProvider, type PublishTopic, QueueManager, QueueService, type RateLimiterKeyOptions, RedisCacheProvider, Request, type RequestSchema, Response$1 as Response, type ResponseBodyForStatus, type RuntimeServer, S3StorageProvider, type S3StorageProviderOptions, SQSConfiguration, type SQSConfigurationOptions, SQSPubSub, type CacheMetrics as SchemaCacheMetrics, type SerializeOptions, Server, type ServerConnectInput, type ServerErrorHandler, type ServerHook, type ServerInterface, type ServerListenCallback, type ServerOptions, type ServerRouteHandler, type ServerRouteMiddleware, type ServerTapOptions, type SessionOptions, type SignalEvent, type StaticPluginOptions, Storage, type StorageInterface, type StorageOptions, type StorageProviderOptions, type TemplateMailOptions, type TimeoutOptions, type TrustProxyOptions, type TypedCacheKeyIncludes, type TypedCacheRouteConfig, type TypedHandler, TypedQueue, type TypedRouteMetadata, type ValidatedData, type ValidationOptions, arg, asyncLocalStorage, asyncStorage, bullmqQueue, cache, clearAllCaches as clearAllSchemaCaches, commandRegistry, compression, controller, cookie, cors, createExpressAdapter, createPolicyDecorator, createQueue, cron, Server as default, defineQueueConfiguration, del, expressHandler, expressMiddleware, flag, get, getCacheMetrics as getSchemaCacheMetrics, hash, helmet, log, logCacheMetrics as logSchemaCacheMetrics, logger, memoryQueue, methodOverride, middleware, mountExpressRouter, mqtt, patch, pgbossQueue, post, put, rateLimiter, router, serialize, serveStatic, session, setCronGlobalErrorHandler, setMqttGlobalErrorHandler, sqsQueue, timeout as timeoutMw, trustProxy, validate };