@coderbuzz/ken 0.1.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.
@@ -0,0 +1,2783 @@
1
+ /**
2
+ * Ken Framework - Context Types
3
+ * Shared type definitions for all Context implementations
4
+ *
5
+ * MIT License - Copyright (c) 2025 Indra Gunawan
6
+ */
7
+ /**
8
+ * Remote information including address and port.
9
+ */
10
+ interface RemoteInfo {
11
+ address: string;
12
+ port: number;
13
+ }
14
+ type Validator = (val: any, ctx?: any) => any;
15
+ /**
16
+ * Flatten intersection types into a single object type for better IDE display.
17
+ * Converts A & B & C into a single flat object with all properties.
18
+ */
19
+ type Flatten<T> = T extends infer U ? {
20
+ [K in keyof U]: U[K];
21
+ } : never;
22
+ /**
23
+ * Type-level utilities to extract param names from route path patterns.
24
+ */
25
+ type ExtractParams<Path extends string> = Path extends `${infer Segment}/${infer Rest}` ? ExtractParams<Segment> & ExtractParams<Rest> : Path extends `:${infer Param}?` ? {
26
+ [K in Param]?: string;
27
+ } : Path extends `:${infer Param}` ? {
28
+ [K in Param]: string;
29
+ } : Path extends `*` ? {
30
+ "*": string;
31
+ } : {};
32
+ type ParamsFromPath<Path extends string> = Flatten<ExtractParams<Path>>;
33
+ /**
34
+ * Error handler function signature.
35
+ * Receives the thrown error and request context, returns a Response.
36
+ * Route-level onError takes priority over app-level.
37
+ */
38
+ type ErrorHandler = (error: unknown, ctx: Context<any, any>) => Response | Promise<Response>;
39
+ /**
40
+ * Route Schema definition for validation and type inference.
41
+ * Flat architecture for best DX.
42
+ */
43
+ interface Schema {
44
+ onError?: ErrorHandler;
45
+ state?: StateMiddleware;
46
+ params?: Record<string, Validator>;
47
+ query?: Record<string, Validator>;
48
+ headers?: Record<string, Validator>;
49
+ cookies?: Record<string, Validator>;
50
+ json?: Validator;
51
+ text?: Validator;
52
+ form?: Record<string, Validator>;
53
+ }
54
+ /**
55
+ * Middleware handler function signature.
56
+ * Used for side-effect middleware (logger, CORS, etc.) that don't produce state values.
57
+ */
58
+ type MiddlewareHandler = (ctx: Context<any, any>) => void | Response | Promise<void | Response>;
59
+ /**
60
+ * State Middleware definition and type inference.
61
+ * Middleware functions that can return values (auth, user data) or void (guards, loggers).
62
+ */
63
+ type StateMiddleware = {
64
+ [key: string]: (ctx: Context<any, any>) => any;
65
+ };
66
+ type ContainsUndefined<T> = [T] extends [Exclude<T, undefined>] ? false : true;
67
+ type InferObject<T, Default> = T extends Record<string, Validator> ? Flatten<{
68
+ [K in keyof T as ContainsUndefined<ReturnType<T[K]>> extends true ? K : never]?: ReturnType<T[K]>;
69
+ } & {
70
+ [K in keyof T as ContainsUndefined<ReturnType<T[K]>> extends true ? never : K]: ReturnType<T[K]>;
71
+ }> : Default;
72
+ type InferValidator<T, Default> = T extends Validator ? ReturnType<T> : Default;
73
+ /**
74
+ * Context Interface - What route handlers see
75
+ * All runtime-specific Context classes must implement this
76
+ *
77
+ * @template S - Schema type for validation
78
+ * @template Path - Route path string for param extraction
79
+ * @template TState - Accumulated state from middleware
80
+ */
81
+ interface Context<S extends Schema = {}, Path extends string = string, TState = {}> {
82
+ /**
83
+ * Request URL
84
+ */
85
+ readonly url: string;
86
+ /**
87
+ * HTTP method
88
+ */
89
+ readonly method: string;
90
+ /**
91
+ * Raw body stream (runtime-specific)
92
+ */
93
+ readonly body: any;
94
+ /**
95
+ * State from executed middleware. Only includes middleware with non-void returns.
96
+ */
97
+ state: Flatten<TState & (S extends {
98
+ state: infer M extends StateMiddleware;
99
+ } ? {
100
+ [K in keyof M as Awaited<ReturnType<M[K]>> extends void ? never : K]: Exclude<Awaited<ReturnType<M[K]>>, Response>;
101
+ } : {})>;
102
+ /**
103
+ * Remote information (IP address and port)
104
+ */
105
+ readonly remoteInfo: RemoteInfo;
106
+ /**
107
+ * Route parameters (lazily parsed and validated)
108
+ */
109
+ readonly params: InferObject<S['params'], ParamsFromPath<Path>>;
110
+ /**
111
+ * Query parameters (lazily parsed and validated)
112
+ */
113
+ readonly query: InferObject<S['query'], Record<string, string>>;
114
+ /**
115
+ * Headers (lazily parsed and validated)
116
+ */
117
+ readonly headers: InferObject<S['headers'], Record<string, string>>;
118
+ /**
119
+ * Cookies (lazily parsed and validated)
120
+ */
121
+ readonly cookies: InferObject<S['cookies'], Record<string, string>>;
122
+ /**
123
+ * JSON body (lazily parsed and validated)
124
+ */
125
+ readonly json: Promise<InferValidator<S['json'], any>>;
126
+ /**
127
+ * Text body (lazily parsed and validated)
128
+ */
129
+ readonly text: Promise<InferValidator<S['text'], string>>;
130
+ /**
131
+ * Form body (lazily parsed and validated)
132
+ */
133
+ readonly form: Promise<InferObject<S['form'], FormData>>;
134
+ /**
135
+ * Register callback to be called after handler finishes (success or failure).
136
+ * Useful for middleware that needs to perform cleanup or logging after response.
137
+ */
138
+ onFinish(callback: (resp?: Response) => void): void;
139
+ /**
140
+ * Set a cookie to be sent with the response.
141
+ * Cookies are collected and applied to the response via onFinish callback.
142
+ *
143
+ * @param name - Cookie name
144
+ * @param value - Cookie value
145
+ * @param options - Cookie options (path, domain, maxAge, expires, httpOnly, secure, sameSite)
146
+ */
147
+ setCookie(name: string, value: string, options?: {
148
+ path?: string;
149
+ domain?: string;
150
+ maxAge?: number;
151
+ expires?: Date;
152
+ httpOnly?: boolean;
153
+ secure?: boolean;
154
+ sameSite?: 'Strict' | 'Lax' | 'None';
155
+ }): void;
156
+ /**
157
+ * Internal: Execute all registered onFinish callbacks.
158
+ * Called by framework after handler completes.
159
+ */
160
+ _executeFinishCallbacks(resp?: Response): void;
161
+ }
162
+
163
+ /**
164
+ * Ken Framework - Core Router
165
+ * High-performance radix tree router with type-safe route matching
166
+ *
167
+ * MIT License - Copyright (c) 2025 Indra Gunawan
168
+ */
169
+
170
+ /**
171
+ * Handler function type - receives Context and returns any value
172
+ * The return value is converted to Response by the runtime
173
+ */
174
+ type Handler = (ctx: any) => any;
175
+ /**
176
+ * Raw route registration entry
177
+ * Stored during registration phase, compiled by runtime
178
+ */
179
+ interface RouteRegistration {
180
+ method: string;
181
+ path: string;
182
+ schema?: Schema;
183
+ handler?: Handler;
184
+ staticValue?: unknown;
185
+ }
186
+ /**
187
+ * Route info returned by getRoutes()
188
+ */
189
+ interface RouteInfo {
190
+ method: string;
191
+ path: string;
192
+ }
193
+ /**
194
+ * Match result from router
195
+ */
196
+ interface MatchResult {
197
+ handler: (...args: any[]) => any;
198
+ schema?: Schema;
199
+ params: Record<string, string>;
200
+ response?: Response;
201
+ }
202
+ /**
203
+ * Router - Core routing implementation
204
+ *
205
+ * Stores raw route registrations during registration phase.
206
+ * Runtimes compile routes with their native executors.
207
+ */
208
+ declare class Router {
209
+ private staticRoutes;
210
+ private dynamicRoot;
211
+ routes: RouteRegistration[];
212
+ /**
213
+ * Register a compiled route (called by runtime during compilation)
214
+ */
215
+ registerCompiled(method: string, path: string, handler: (...args: any[]) => any, schema?: Schema, response?: Response): void;
216
+ private insertDynamic;
217
+ /**
218
+ * Create matcher function for route lookup
219
+ */
220
+ matcher(): (method: string, pathname: string) => MatchResult | undefined;
221
+ /**
222
+ * Dynamic-only matcher (skips static routes)
223
+ * Used when native routing handles static routes
224
+ */
225
+ dynamicOnlyMatcher(): (method: string, pathname: string) => MatchResult | undefined;
226
+ /**
227
+ * Get all registered routes as an array of { method, path } objects.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * const routes = app.getRoutes();
232
+ * // [{ method: 'GET', path: '/' }, { method: 'POST', path: '/users' }, ...]
233
+ * ```
234
+ */
235
+ getRoutes(): RouteInfo[];
236
+ /**
237
+ * Clear all compiled routes (for recompilation)
238
+ */
239
+ clear(): void;
240
+ }
241
+
242
+ /**
243
+ * Ken Framework - WebSocket Types
244
+ * Runtime-agnostic WebSocket abstractions
245
+ *
246
+ * MIT License - Copyright (c) 2025 Indra Gunawan
247
+ */
248
+ /**
249
+ * WebSocket message data type
250
+ */
251
+ type WsMessageData = string | ArrayBuffer | Uint8Array;
252
+ /**
253
+ * WebSocket ready states
254
+ */
255
+ declare const WsReadyState: {
256
+ readonly CONNECTING: 0;
257
+ readonly OPEN: 1;
258
+ readonly CLOSING: 2;
259
+ readonly CLOSED: 3;
260
+ };
261
+ type WsReadyStateValue = (typeof WsReadyState)[keyof typeof WsReadyState];
262
+ /**
263
+ * WebSocket peer - represents a single connected client.
264
+ *
265
+ * Provides send/close/subscribe/unsubscribe/publish methods.
266
+ * The `data` field carries user-defined per-connection state
267
+ * set via the `upgrade` handler.
268
+ *
269
+ * @template T - User-defined per-connection data type
270
+ */
271
+ interface WsPeer<T = unknown> {
272
+ /** User-defined per-connection data (set in upgrade handler) */
273
+ readonly data: T;
274
+ /** Remote address of the connected client */
275
+ readonly remoteAddress: string;
276
+ /** Current ready state of the connection */
277
+ readonly readyState: WsReadyStateValue;
278
+ /**
279
+ * Send a message to this peer.
280
+ * @param data - Message to send (string, ArrayBuffer, or Uint8Array)
281
+ * @param compress - Whether to compress this message (default: false)
282
+ * @returns Number of bytes sent, or -1 if buffered/failed
283
+ */
284
+ send(data: WsMessageData, compress?: boolean): number;
285
+ /**
286
+ * Close the connection.
287
+ * @param code - Close status code (default: 1000)
288
+ * @param reason - Close reason string
289
+ */
290
+ close(code?: number, reason?: string): void;
291
+ /**
292
+ * Subscribe this peer to a topic for pub/sub messaging.
293
+ * @param topic - Topic name to subscribe to
294
+ */
295
+ subscribe(topic: string): void;
296
+ /**
297
+ * Unsubscribe this peer from a topic.
298
+ * @param topic - Topic name to unsubscribe from
299
+ */
300
+ unsubscribe(topic: string): void;
301
+ /**
302
+ * Publish a message to all subscribers of a topic (excluding this peer).
303
+ * @param topic - Topic to publish to
304
+ * @param data - Message data to publish
305
+ * @param compress - Whether to compress this message (default: false)
306
+ */
307
+ publish(topic: string, data: WsMessageData, compress?: boolean): void;
308
+ /**
309
+ * Check if this peer is subscribed to a topic.
310
+ * @param topic - Topic name to check
311
+ */
312
+ isSubscribed(topic: string): boolean;
313
+ /**
314
+ * Send a ping frame to this peer.
315
+ * @param data - Optional ping payload
316
+ */
317
+ ping(data?: WsMessageData): void;
318
+ /**
319
+ * Send a pong frame to this peer.
320
+ * @param data - Optional pong payload
321
+ */
322
+ pong(data?: WsMessageData): void;
323
+ }
324
+ /**
325
+ * WebSocket event handlers.
326
+ *
327
+ * @template T - User-defined per-connection data type
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * app.ws<{ userId: string }>('/chat', {
332
+ * upgrade(req) {
333
+ * const token = req.headers.get('authorization');
334
+ * return { userId: verifyToken(token) };
335
+ * },
336
+ * open(peer) {
337
+ * peer.subscribe('chat');
338
+ * peer.publish('chat', `${peer.data.userId} joined`);
339
+ * },
340
+ * message(peer, message) {
341
+ * peer.publish('chat', `${peer.data.userId}: ${message}`);
342
+ * },
343
+ * close(peer, code, reason) {
344
+ * peer.publish('chat', `${peer.data.userId} left`);
345
+ * },
346
+ * });
347
+ * ```
348
+ */
349
+ interface WsHandler<T = unknown> {
350
+ /**
351
+ * Called during HTTP→WebSocket upgrade.
352
+ * Return per-connection data, or a Response to reject the upgrade.
353
+ * If not provided, upgrades are accepted with `undefined` data.
354
+ */
355
+ upgrade?: (req: Request) => T | Response | Promise<T | Response>;
356
+ /**
357
+ * Called when a connection is established.
358
+ */
359
+ open?: (peer: WsPeer<T>) => void | Promise<void>;
360
+ /**
361
+ * Called when a message is received from the client.
362
+ */
363
+ message: (peer: WsPeer<T>, message: WsMessageData) => void | Promise<void>;
364
+ /**
365
+ * Called when the connection is closed.
366
+ */
367
+ close?: (peer: WsPeer<T>, code: number, reason: string) => void | Promise<void>;
368
+ /**
369
+ * Called when a ping frame is received.
370
+ */
371
+ ping?: (peer: WsPeer<T>, data: WsMessageData) => void;
372
+ /**
373
+ * Called when a pong frame is received.
374
+ */
375
+ pong?: (peer: WsPeer<T>, data: WsMessageData) => void;
376
+ /**
377
+ * Called when an error occurs on the connection.
378
+ */
379
+ error?: (peer: WsPeer<T>, error: Error) => void;
380
+ }
381
+ /**
382
+ * WebSocket configuration options.
383
+ */
384
+ interface WsOptions {
385
+ /**
386
+ * Maximum message size in bytes.
387
+ * Messages exceeding this limit will close the connection.
388
+ * @default 16_777_216 (16 MB)
389
+ */
390
+ maxPayloadLength?: number;
391
+ /**
392
+ * Maximum number of bytes that can be buffered for sending.
393
+ * @default 16_777_216 (16 MB)
394
+ */
395
+ backpressureLimit?: number;
396
+ /**
397
+ * Interval in seconds between server-initiated ping frames.
398
+ * Set to 0 to disable automatic pings.
399
+ * @default 30
400
+ */
401
+ pingInterval?: number;
402
+ /**
403
+ * Timeout in seconds to wait for a pong response before closing.
404
+ * @default 10
405
+ */
406
+ pongTimeout?: number;
407
+ /**
408
+ * Whether to enable per-message compression.
409
+ * @default false
410
+ */
411
+ perMessageDeflate?: boolean;
412
+ /**
413
+ * Idle timeout in seconds. Connections idle for this long are closed.
414
+ * Set to 0 to disable.
415
+ * @default 120
416
+ */
417
+ idleTimeout?: number;
418
+ }
419
+ /**
420
+ * Internal WebSocket route registration
421
+ */
422
+ interface WsRoute<T = unknown> {
423
+ path: string;
424
+ handler: WsHandler<T>;
425
+ options?: WsOptions;
426
+ }
427
+
428
+ /**
429
+ * Ken Framework - App
430
+ * Type-safe routing with schema validation and middleware support
431
+ *
432
+ * MIT License - Copyright (c) 2025 Indra Gunawan
433
+ */
434
+
435
+ /**
436
+ * Infer state shape from middleware definitions.
437
+ * Excludes middleware with void return types.
438
+ */
439
+ type InferState<T extends StateMiddleware> = Flatten<{
440
+ [K in keyof T as Awaited<ReturnType<T[K]>> extends void ? never : K]: Exclude<Awaited<ReturnType<T[K]>>, Response>;
441
+ }>;
442
+ /**
443
+ * Typed handler function
444
+ */
445
+ type TypedHandler<S extends Schema = {}, P extends string = string, TState = {}> = (ctx: Context<S, P, TState>) => any;
446
+ /**
447
+ * App - Type-safe router with schema validation
448
+ *
449
+ * Provides full type inference for:
450
+ * - Route parameters from path patterns
451
+ * - Query/headers/cookies/body from schema validators
452
+ * - State from middleware definitions
453
+ *
454
+ * @template TState - Accumulated middleware state type
455
+ */
456
+ declare class App<TState extends StateMiddleware = {}> extends Router {
457
+ private middleware;
458
+ /** App-level error handler */
459
+ private _onError?;
460
+ /** Not-found handler for this app */
461
+ private _notFoundHandler?;
462
+ /** Accumulated not-found entries from child apps (via .use() and .define()) */
463
+ private _notFoundEntries;
464
+ /** WebSocket route registrations */
465
+ wsRoutes: WsRoute<any>[];
466
+ /**
467
+ * Set app-level error handler.
468
+ * Called when route handlers or middleware throw errors.
469
+ * Route-level onError (in schema) takes priority over app-level.
470
+ *
471
+ * @param handler - Error handler function
472
+ *
473
+ * @example
474
+ * ```ts
475
+ * app.onError((error, ctx) => {
476
+ * console.error(ctx.method, ctx.url, error);
477
+ * return Response.json(
478
+ * { status: 500, message: error instanceof Error ? error.message : 'Internal Server Error' },
479
+ * { status: 500 }
480
+ * );
481
+ * });
482
+ * ```
483
+ */
484
+ onError(handler: ErrorHandler): void;
485
+ /**
486
+ * Set custom 404 Not Found handler.
487
+ * Called when no route matches the request.
488
+ * Supports nested configuration via .use() prefix scoping.
489
+ *
490
+ * @param handler - Handler function that receives Context
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * // App-level
495
+ * app.notFound((ctx) => {
496
+ * return Response.json({ error: 'Not Found', path: ctx.url }, { status: 404 });
497
+ * });
498
+ *
499
+ * // Sub-app with prefix scoping
500
+ * const api = new App();
501
+ * api.notFound((ctx) => Response.json({ error: 'API Not Found' }, { status: 404 }));
502
+ * app.use('/api', api);
503
+ *
504
+ * // Define scope with middleware state
505
+ * app.define({ auth: (ctx) => verifyAuth(ctx) }, (app) => {
506
+ * app.notFound((ctx) => Response.json({ error: 'Protected', user: ctx.state.auth }, { status: 404 }));
507
+ * });
508
+ * ```
509
+ */
510
+ notFound(handler: TypedHandler<{}, string, TState>): void;
511
+ /**
512
+ * Apply middleware to routes matching pattern.
513
+ * Supports both side-effect middleware (function) and state-producing middleware (object).
514
+ *
515
+ * @param pattern - Route pattern (default "/*" for all routes)
516
+ * @param stateOrHandler - State middleware object or handler function
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * // Side-effect middleware (no state, just side effects)
521
+ * app.apply('/*', (ctx) => { console.log(ctx.method, ctx.url); });
522
+ *
523
+ * // State-producing middleware (adds to ctx.state)
524
+ * app.apply('/*', { auth: (ctx) => verifyAuth(ctx) });
525
+ * ```
526
+ */
527
+ apply(pattern: string, handler: MiddlewareHandler): void;
528
+ apply(pattern: string, state: StateMiddleware): void;
529
+ /**
530
+ * Define middleware with lexical scoping and automatic type inference.
531
+ * Routes registered in the callback inherit middleware state types.
532
+ *
533
+ * @param state - State middleware object
534
+ * @param callback - Callback that receives scoped app with accumulated state types
535
+ */
536
+ define<S extends StateMiddleware>(state: S, callback: (app: App<Flatten<TState & InferState<S>>>) => void): void;
537
+ /**
538
+ * Match middleware patterns against route path.
539
+ */
540
+ matchMiddleware(path: string): StateMiddleware[];
541
+ /**
542
+ * Simple wildcard pattern matching.
543
+ */
544
+ private matchPattern;
545
+ /**
546
+ * Merge multiple middleware states with route schema.
547
+ */
548
+ mergeSchemas(middlewareStates: StateMiddleware[], routeSchema?: Schema): Schema | undefined;
549
+ /**
550
+ * Store raw route registration
551
+ */
552
+ private storeRoute;
553
+ /**
554
+ * Internal route registration logic
555
+ */
556
+ private register;
557
+ get<P extends string>(path: P, handler: TypedHandler<{}, P, TState>): void;
558
+ get<S extends Schema, P extends string>(path: P, schema: S, handler: TypedHandler<S, P, TState>): void;
559
+ get<P extends string, T = unknown>(path: P, staticValue: T): void;
560
+ get<S extends Schema, P extends string, T = unknown>(path: P, schema: S, staticValue: T): void;
561
+ post<P extends string>(path: P, handler: TypedHandler<{}, P, TState>): void;
562
+ post<S extends Schema, P extends string>(path: P, schema: S, handler: TypedHandler<S, P, TState>): void;
563
+ post<P extends string, T = unknown>(path: P, staticValue: T): void;
564
+ post<S extends Schema, P extends string, T = unknown>(path: P, schema: S, staticValue: T): void;
565
+ put<P extends string>(path: P, handler: TypedHandler<{}, P, TState>): void;
566
+ put<S extends Schema, P extends string>(path: P, schema: S, handler: TypedHandler<S, P, TState>): void;
567
+ put<P extends string, T = unknown>(path: P, staticValue: T): void;
568
+ put<S extends Schema, P extends string, T = unknown>(path: P, schema: S, staticValue: T): void;
569
+ patch<P extends string>(path: P, handler: TypedHandler<{}, P, TState>): void;
570
+ patch<S extends Schema, P extends string>(path: P, schema: S, handler: TypedHandler<S, P, TState>): void;
571
+ patch<P extends string, T = unknown>(path: P, staticValue: T): void;
572
+ patch<S extends Schema, P extends string, T = unknown>(path: P, schema: S, staticValue: T): void;
573
+ delete<P extends string>(path: P, handler: TypedHandler<{}, P, TState>): void;
574
+ delete<S extends Schema, P extends string>(path: P, schema: S, handler: TypedHandler<S, P, TState>): void;
575
+ delete<P extends string, T = unknown>(path: P, staticValue: T): void;
576
+ delete<S extends Schema, P extends string, T = unknown>(path: P, schema: S, staticValue: T): void;
577
+ head<P extends string>(path: P, handler: TypedHandler<{}, P, TState>): void;
578
+ head<S extends Schema, P extends string>(path: P, schema: S, handler: TypedHandler<S, P, TState>): void;
579
+ head<P extends string, T = unknown>(path: P, staticValue: T): void;
580
+ head<S extends Schema, P extends string, T = unknown>(path: P, schema: S, staticValue: T): void;
581
+ options<P extends string>(path: P, handler: TypedHandler<{}, P, TState>): void;
582
+ options<S extends Schema, P extends string>(path: P, schema: S, handler: TypedHandler<S, P, TState>): void;
583
+ options<P extends string, T = unknown>(path: P, staticValue: T): void;
584
+ options<S extends Schema, P extends string, T = unknown>(path: P, schema: S, staticValue: T): void;
585
+ /**
586
+ * Mount another App with optional prefix
587
+ */
588
+ use(app: App): void;
589
+ use(prefix: string, app: App): void;
590
+ /**
591
+ * Register a WebSocket handler at the given path.
592
+ *
593
+ * The generic type parameter `T` defines per-connection data,
594
+ * which is set in the `upgrade` handler and accessible via `peer.data`.
595
+ *
596
+ * @template T - Per-connection data type
597
+ * @param path - URL path to handle WebSocket upgrades
598
+ * @param handler - WebSocket event handlers
599
+ *
600
+ * @example
601
+ * ```ts
602
+ * // Simple echo server
603
+ * app.ws('/ws', {
604
+ * message(peer, message) {
605
+ * peer.send(message);
606
+ * },
607
+ * });
608
+ *
609
+ * // Chat with authentication and pub/sub
610
+ * app.ws<{ userId: string }>('/chat', {
611
+ * upgrade(req) {
612
+ * const token = req.headers.get('authorization');
613
+ * return { userId: verifyToken(token) };
614
+ * },
615
+ * open(peer) {
616
+ * peer.subscribe('chat');
617
+ * peer.publish('chat', `${peer.data.userId} joined`);
618
+ * },
619
+ * message(peer, message) {
620
+ * peer.publish('chat', `${peer.data.userId}: ${message}`);
621
+ * },
622
+ * close(peer) {
623
+ * peer.publish('chat', `${peer.data.userId} left`);
624
+ * },
625
+ * });
626
+ *
627
+ * // With options
628
+ * app.ws('/live', {
629
+ * message(peer, message) { peer.send(message); },
630
+ * }, {
631
+ * pingInterval: 15,
632
+ * maxPayloadLength: 1024 * 1024,
633
+ * });
634
+ * ```
635
+ */
636
+ ws<T = unknown>(path: string, handler: WsHandler<T>, options?: WsOptions): void;
637
+ /**
638
+ * Print all registered routes to the console with colored formatting.
639
+ *
640
+ * Each HTTP method is color-coded:
641
+ * - GET (green), POST (blue), PUT (yellow), PATCH (cyan)
642
+ * - DELETE (red), HEAD (magenta), OPTIONS (white)
643
+ *
644
+ * @example
645
+ * ```ts
646
+ * app.get('/', 'Hello!');
647
+ * app.post('/users', handler);
648
+ * app.get('/users/:id', handler);
649
+ * app.ws('/chat', wsHandler);
650
+ * app.printRoutes();
651
+ * // ┌──────────┬────────────────────┐
652
+ * // │ Method │ Path │
653
+ * // ├──────────┼────────────────────┤
654
+ * // │ GET │ / │
655
+ * // │ POST │ /users │
656
+ * // │ GET │ /users/:id │
657
+ * // │ WS │ /chat │
658
+ * // └──────────┴────────────────────┘
659
+ * ```
660
+ */
661
+ printRoutes(): void;
662
+ /**
663
+ * Combine prefix with path
664
+ */
665
+ private combinePath;
666
+ }
667
+
668
+ /**
669
+ * Ken Framework - AppServer
670
+ * Combines App routing with automatic runtime detection and server lifecycle.
671
+ *
672
+ * Usage:
673
+ * ```ts
674
+ * import { AppServer } from 'ken';
675
+ * const app = new AppServer({ port: 3000 });
676
+ * app.get('/hello', 'Hello, World!');
677
+ * await app.run();
678
+ * ```
679
+ *
680
+ * MIT License - Copyright (c) 2025 Indra Gunawan
681
+ */
682
+
683
+ /**
684
+ * AppServer initialization options.
685
+ */
686
+ interface AppServerInit {
687
+ port?: number;
688
+ hostname?: string;
689
+ }
690
+ /**
691
+ * AppServer - App with built-in server lifecycle.
692
+ *
693
+ * Extends App with `run()` and `stop()` methods.
694
+ * Automatically detects the current runtime (Bun, Deno, Node.js)
695
+ * and lazily loads only the needed adapter.
696
+ *
697
+ * @template TState - Accumulated middleware state type
698
+ */
699
+ declare class AppServer<TState extends StateMiddleware = {}> extends App<TState> {
700
+ /** Configured hostname */
701
+ hostname: string;
702
+ /** Configured port */
703
+ port: number;
704
+ private _server;
705
+ constructor(init?: AppServerInit);
706
+ /**
707
+ * Start the server with automatic runtime detection.
708
+ * Returns the resolved hostname and port.
709
+ *
710
+ * @example
711
+ * ```ts
712
+ * const app = new AppServer({ port: 3000 });
713
+ * app.get('/', 'Hello!');
714
+ * const { hostname, port } = await app.run();
715
+ * console.log(`Listening on ${hostname}:${port}`);
716
+ * ```
717
+ */
718
+ run(): Promise<{
719
+ hostname: string;
720
+ port: number;
721
+ }>;
722
+ /**
723
+ * Stop the server gracefully.
724
+ */
725
+ stop(): Promise<void>;
726
+ }
727
+
728
+ /**
729
+ * Ken Framework - Runtime Compiler
730
+ * Generic executor factory for all runtimes
731
+ *
732
+ * MIT License - Copyright (c) 2025 Indra Gunawan
733
+ */
734
+
735
+ /**
736
+ * Default error handler - returns standard JSON error response.
737
+ * Response errors are passed through directly.
738
+ *
739
+ * Error response format:
740
+ * ```json
741
+ * { "status": 500, "message": "Internal Server Error" }
742
+ * ```
743
+ */
744
+ declare function defaultErrorHandler(err: unknown): Response;
745
+
746
+ /**
747
+ * Ken Framework - Runtime Adapters
748
+ * Auto-detection with lazy loading for each runtime
749
+ *
750
+ * Detection order:
751
+ * 1. Deno → loads deno runtime
752
+ * 2. Bun → loads bun runtime
753
+ * 3. Node.js → checks UWS env / uWebSockets.js availability
754
+ * - UWS=1|true → use uWebSockets.js (error if not installed)
755
+ * - UWS=0|false → use node:http
756
+ * - Not set → try uWebSockets.js, fallback to node:http
757
+ *
758
+ * MIT License - Copyright (c) 2025 Indra Gunawan
759
+ */
760
+
761
+ /** True when running on Deno */
762
+ declare const isDeno: boolean;
763
+ /** True when running on Bun */
764
+ declare const isBun: boolean;
765
+ /** True when running on Node.js (excludes Bun which also exposes process) */
766
+ declare const isNode: boolean;
767
+ /**
768
+ * Server instance returned by all runtime adapters.
769
+ */
770
+ interface Server {
771
+ /** Start listening. Returns the resolved hostname and port. */
772
+ run(): Promise<{
773
+ hostname: string;
774
+ port: number;
775
+ }>;
776
+ /** Gracefully stop the server. */
777
+ stop(): void | Promise<void>;
778
+ }
779
+ /**
780
+ * Options accepted by every server factory.
781
+ */
782
+ interface ServerOptions {
783
+ port?: number;
784
+ hostname?: string;
785
+ router: Router;
786
+ }
787
+
788
+ /**
789
+ * Ken Framework - Pathname Utilities
790
+ * Fast pathname extraction from URLs
791
+ *
792
+ * MIT License - Copyright (c) 2025 Indra Gunawan
793
+ */
794
+ /**
795
+ * Fast pathname extraction from URL string
796
+ * Optimized for hot path - avoids URL object creation
797
+ */
798
+ declare function getPathname(url: string): string;
799
+
800
+ /**
801
+ * Ken Framework - Encryption Utilities
802
+ * High-performance AES-GCM encryption with key caching
803
+ *
804
+ * MIT License - Copyright (c) 2025 Indra Gunawan
805
+ */
806
+ /**
807
+ * Generate a random 256-bit secret key as a base64 string
808
+ */
809
+ declare function generateSecretKey(): string;
810
+ /**
811
+ * Encrypt a string using AES-GCM.
812
+ * Returns base64-encoded: IV (12 bytes) + ciphertext.
813
+ * Uses cached CryptoKeys and manual base64 for maximum throughput.
814
+ */
815
+ declare function encryptString(value: string, password: string): Promise<string>;
816
+ /**
817
+ * Decrypt a base64-encoded encrypted string using AES-GCM.
818
+ * Uses cached CryptoKeys, manual base64 decode, and subarray (zero-copy) for IV/ciphertext split.
819
+ */
820
+ declare function decryptString(encrypted: string, password: string): Promise<string>;
821
+
822
+ /**
823
+ * Ken Framework - Memoize Utility
824
+ * High-performance function memoization with configurable cache strategy
825
+ *
826
+ * MIT License - Copyright (c) 2025 Indra Gunawan
827
+ */
828
+ /**
829
+ * Options for configuring memoize behavior.
830
+ */
831
+ interface MemoizeOptions<Args extends unknown[]> {
832
+ /**
833
+ * Maximum number of cached entries. When exceeded, the oldest entry is evicted (LRU via insertion order).
834
+ * @default 256
835
+ */
836
+ maxSize?: number;
837
+ /**
838
+ * Time-to-live in milliseconds. Entries older than this are considered stale.
839
+ * When set to 0 or omitted, entries never expire.
840
+ * @default 0
841
+ */
842
+ ttl?: number;
843
+ /**
844
+ * Custom key resolver. Receives the same arguments as the memoized function
845
+ * and must return a string or number to use as the cache key.
846
+ * Defaults to using the first argument directly (fast path for single-arg functions).
847
+ */
848
+ key?: (...args: Args) => string | number;
849
+ }
850
+ interface CacheEntry<T> {
851
+ value: T;
852
+ expiry: number;
853
+ }
854
+ /** Memoized function with exposed cache and clear method */
855
+ type MemoizedSync<Args extends unknown[], R> = ((...args: Args) => R) & {
856
+ cache: Map<string | number, CacheEntry<R>>;
857
+ clear: () => void;
858
+ };
859
+ /** Memoized async function with exposed cache, inflight map, and clear method */
860
+ type MemoizedAsync<Args extends unknown[], R> = ((...args: Args) => Promise<R>) & {
861
+ cache: Map<string | number, CacheEntry<R>>;
862
+ inflight: Map<string | number, Promise<R>>;
863
+ clear: () => void;
864
+ };
865
+ /**
866
+ * Create a memoized version of a function. Auto-detects async functions
867
+ * at creation time and selects the optimal strategy:
868
+ *
869
+ * - **Sync**: Map-based cache with LRU eviction and TTL expiry.
870
+ * - **Async**: Same cache plus in-flight deduplication — concurrent calls
871
+ * for the same key share a single Promise (thundering herd protection).
872
+ *
873
+ * Detection uses `fn.constructor.name === 'AsyncFunction'` (compile-time branch,
874
+ * zero per-call overhead).
875
+ *
876
+ * @example
877
+ * ```ts
878
+ * // Sync — auto-detected
879
+ * const expensive = memoize((id: string) => computeResult(id));
880
+ * expensive('abc'); // computes
881
+ * expensive('abc'); // cached
882
+ *
883
+ * // Async — auto-detected, with TTL and max size
884
+ * const fetchUser = memoize(
885
+ * async (id: string) => db.users.findById(id),
886
+ * { ttl: 30_000, maxSize: 500 }
887
+ * );
888
+ *
889
+ * // Multi-arg with custom key
890
+ * const multi = memoize(
891
+ * (a: number, b: number) => a + b,
892
+ * { key: (a, b) => `${a}:${b}` }
893
+ * );
894
+ * ```
895
+ */
896
+ declare function memoize<Args extends unknown[], R>(fn: (...args: Args) => Promise<R>, options?: MemoizeOptions<Args>): MemoizedAsync<Args, R>;
897
+ declare function memoize<Args extends unknown[], R>(fn: (...args: Args) => R, options?: MemoizeOptions<Args>): MemoizedSync<Args, R>;
898
+
899
+ /**
900
+ * Ken Framework - Compression Utilities
901
+ * High-performance string compression for cookies and JSON storage.
902
+ *
903
+ * Uses Web Standard CompressionStream/DecompressionStream API
904
+ * for cross-runtime compatibility (Bun, Node.js, Deno, Cloudflare Workers).
905
+ * Output is base64url-encoded (no +, /, or = characters) — safe for
906
+ * cookies, URLs, and JSON values without escaping.
907
+ *
908
+ * Default format is `brotli` — best compression ratio for cookie/session
909
+ * storage where every byte counts.
910
+ *
911
+ * MIT License - Copyright (c) 2025 Indra Gunawan
912
+ */
913
+ /** Supported compression formats — extends the Web Standard with brotli (supported by Bun, Node.js, Deno). */
914
+ type SupportedCompressionFormat = CompressionFormat | 'brotli';
915
+ /**
916
+ * Options for {@link compressString}.
917
+ *
918
+ * The `level` range depends on the chosen `encoding`:
919
+ * - **`brotli`**: 0–11 (default: `11` — best compression, slowest)
920
+ * - **`deflate` / `gzip` / `deflate-raw`**: 0–9 (default: runtime default, typically `6`)
921
+ *
922
+ * Lower values = faster encoding + larger output.
923
+ * Higher values = slower encoding + smaller output.
924
+ */
925
+ interface CompressOptions$1 {
926
+ /** Compression format. Default: `'brotli'`. */
927
+ encoding?: SupportedCompressionFormat;
928
+ /**
929
+ * Compression quality level.
930
+ * - brotli: 0–11 (default 11)
931
+ * - deflate / gzip / deflate-raw: 0–9 (default ~6)
932
+ */
933
+ level?: number;
934
+ }
935
+ /** Options for {@link decompressString}. */
936
+ interface DecompressOptions {
937
+ /** Compression format used during compression. Default: `'brotli'`. */
938
+ encoding?: SupportedCompressionFormat;
939
+ }
940
+ /**
941
+ * Compress a string and return a base64url-encoded result.
942
+ * Output is safe for cookies, URLs, and JSON values (no +, /, or = characters).
943
+ *
944
+ * Uses `brotli` by default for best compression ratio — produces smaller
945
+ * output than `deflate` or `gzip`, ideal for cookie/session storage.
946
+ *
947
+ * @param input - The string to compress
948
+ * @param options - Compression options: `encoding` (default `'brotli'`) and optional `level`
949
+ * @returns Base64url-encoded compressed string
950
+ *
951
+ * @example
952
+ * ```ts
953
+ * // Best compression (default brotli)
954
+ * const compressed = await compressString('Hello, World!');
955
+ *
956
+ * // Faster, lighter brotli
957
+ * const fast = await compressString('Hello, World!', { level: 4 });
958
+ *
959
+ * // gzip with max compression
960
+ * const gz = await compressString('Hello, World!', { encoding: 'gzip', level: 9 });
961
+ * ```
962
+ */
963
+ declare function compressString(input: string, options?: CompressOptions$1): Promise<string>;
964
+ /**
965
+ * Decompress a base64url-encoded compressed string back to the original.
966
+ *
967
+ * @param compressed - Base64url-encoded compressed string from {@link compressString}
968
+ * @param options - Decompression options: `encoding` must match what was used to compress (default `'brotli'`)
969
+ * @returns The original decompressed string
970
+ *
971
+ * @example
972
+ * ```ts
973
+ * const original = await decompressString(compressed);
974
+ * // 'Hello, World!'
975
+ *
976
+ * const original = await decompressString(compressed, { encoding: 'gzip' });
977
+ * ```
978
+ */
979
+ declare function decompressString(compressed: string, options?: DecompressOptions): Promise<string>;
980
+
981
+ /**
982
+ * Ken Framework - File Utilities
983
+ * Cross-runtime file operations: serve, list, upload, save
984
+ *
985
+ * Optimized paths per runtime:
986
+ * - Bun: Uses Bun.file() for zero-copy sendfile responses
987
+ * - Node.js / Deno: Uses node:fs streams with backpressure-aware ReadableStream
988
+ *
989
+ * MIT License - Copyright (c) 2025 Indra Gunawan
990
+ */
991
+ /**
992
+ * Get MIME type from a file path or extension.
993
+ * Falls back to `application/octet-stream` for unknown types.
994
+ *
995
+ * @example
996
+ * ```ts
997
+ * getMimeType('photo.jpg') // 'image/jpeg'
998
+ * getMimeType('.css') // 'text/css; charset=utf-8'
999
+ * ```
1000
+ */
1001
+ declare function getMimeType(filePath: string): string;
1002
+ /**
1003
+ * Options for {@link sendFile}.
1004
+ */
1005
+ interface SendFileOptions {
1006
+ /** Override Content-Type (auto-detected from extension by default) */
1007
+ contentType?: string;
1008
+ /** Trigger browser download. `true` uses original filename, string sets a custom filename. */
1009
+ download?: boolean | string;
1010
+ /** Cache-Control header value (e.g., `'public, max-age=3600'`) */
1011
+ cacheControl?: string;
1012
+ /** Additional response headers */
1013
+ headers?: Record<string, string>;
1014
+ /** HTTP status code override for the full-file response (default: 200) */
1015
+ status?: number;
1016
+ /**
1017
+ * Incoming request headers for conditional and range request support.
1018
+ * Pass `ctx.headers` or the raw `Headers` object.
1019
+ * Enables ETag / If-None-Match, Last-Modified / If-Modified-Since,
1020
+ * and Range (206 Partial Content) handling.
1021
+ */
1022
+ reqHeaders?: Headers | Record<string, string>;
1023
+ }
1024
+ /**
1025
+ * Options for {@link listDirectory}.
1026
+ */
1027
+ interface ListDirectoryOptions {
1028
+ /** Recurse into subdirectories (default: `false`) */
1029
+ recursive?: boolean;
1030
+ /** Maximum recursion depth when `recursive` is `true` (default: `10`) */
1031
+ maxDepth?: number;
1032
+ /** Include file stats — size & modifiedAt (default: `true`) */
1033
+ stats?: boolean;
1034
+ /** Filter entries. Return `true` to include, `false` to skip. */
1035
+ filter?: (entry: FileEntry) => boolean;
1036
+ }
1037
+ /**
1038
+ * A single file or directory entry returned by {@link listDirectory}.
1039
+ */
1040
+ interface FileEntry {
1041
+ /** Filename (including extension) */
1042
+ name: string;
1043
+ /** Relative path from the listed directory root */
1044
+ path: string;
1045
+ /** `true` if the entry is a directory */
1046
+ isDirectory: boolean;
1047
+ /** Size in bytes (`0` for directories, `0` if stats disabled) */
1048
+ size: number;
1049
+ /** Last modified timestamp (epoch 0 if stats disabled) */
1050
+ modifiedAt: Date;
1051
+ }
1052
+ /**
1053
+ * Options for {@link receiveFiles}.
1054
+ */
1055
+ interface ReceiveFileOptions {
1056
+ /** Maximum individual file size in bytes */
1057
+ maxFileSize?: number;
1058
+ /** Maximum number of files to accept */
1059
+ maxFiles?: number;
1060
+ /** Allowed MIME types (e.g., `['image/png', 'image/jpeg']`) */
1061
+ allowedTypes?: string[];
1062
+ /** Only extract files from these form field names */
1063
+ fields?: string[];
1064
+ }
1065
+ /**
1066
+ * A file extracted by {@link receiveFiles}.
1067
+ */
1068
+ interface UploadedFile {
1069
+ /** Form field name the file was submitted under */
1070
+ fieldName: string;
1071
+ /** Original filename from the client */
1072
+ fileName: string;
1073
+ /** MIME type */
1074
+ type: string;
1075
+ /** Size in bytes */
1076
+ size: number;
1077
+ /** Raw file contents */
1078
+ data: ArrayBuffer;
1079
+ }
1080
+ /**
1081
+ * Serve a file as an HTTP `Response` with correct headers.
1082
+ *
1083
+ * Features:
1084
+ * - Auto-detected Content-Type from file extension
1085
+ * - Range request support (206 Partial Content) for streaming / resumable downloads
1086
+ * - Conditional requests (ETag, Last-Modified → 304 Not Modified)
1087
+ * - Content-Disposition for triggering downloads
1088
+ * - Uses `Bun.file()` on Bun for zero-copy `sendfile` performance
1089
+ *
1090
+ * Returns a **404** `Response` when the file does not exist (no throw).
1091
+ *
1092
+ * @example
1093
+ * ```ts
1094
+ * // Basic usage
1095
+ * app.get('/files/:name', (ctx) =>
1096
+ * sendFile(`./public/${ctx.params.name}`)
1097
+ * );
1098
+ *
1099
+ * // With caching and range support
1100
+ * app.get('/media/:file', (ctx) =>
1101
+ * sendFile(`./media/${ctx.params.file}`, {
1102
+ * reqHeaders: ctx.headers,
1103
+ * cacheControl: 'public, max-age=86400',
1104
+ * })
1105
+ * );
1106
+ *
1107
+ * // Force download
1108
+ * app.get('/download/:file', (ctx) =>
1109
+ * sendFile(`./files/${ctx.params.file}`, { download: true })
1110
+ * );
1111
+ * ```
1112
+ */
1113
+ declare function sendFile(filePath: string, options?: SendFileOptions): Promise<Response>;
1114
+ /**
1115
+ * List the contents of a directory with optional metadata.
1116
+ *
1117
+ * Stat calls within each directory level are parallelized via `Promise.all`
1118
+ * for maximum throughput. Uses an iterative stack (no recursion overhead).
1119
+ *
1120
+ * @example
1121
+ * ```ts
1122
+ * // Flat listing
1123
+ * app.get('/browse', async () => {
1124
+ * return listDirectory('./data');
1125
+ * });
1126
+ *
1127
+ * // Recursive with filter
1128
+ * app.get('/browse/images', async () => {
1129
+ * return listDirectory('./public', {
1130
+ * recursive: true,
1131
+ * filter: (e) => e.isDirectory || e.name.endsWith('.png'),
1132
+ * });
1133
+ * });
1134
+ * ```
1135
+ */
1136
+ declare function listDirectory(dirPath: string, options?: ListDirectoryOptions): Promise<FileEntry[]>;
1137
+ /**
1138
+ * Extract uploaded files from `FormData` with validation.
1139
+ *
1140
+ * @example
1141
+ * ```ts
1142
+ * app.post('/upload', async (ctx) => {
1143
+ * const files = await receiveFiles(await ctx.form, {
1144
+ * maxFileSize: 10 * 1024 * 1024, // 10 MB
1145
+ * maxFiles: 5,
1146
+ * allowedTypes: ['image/png', 'image/jpeg'],
1147
+ * });
1148
+ *
1149
+ * for (const file of files) {
1150
+ * await saveFile(`./uploads/${file.fileName}`, file.data);
1151
+ * }
1152
+ * return { uploaded: files.length };
1153
+ * });
1154
+ * ```
1155
+ */
1156
+ declare function receiveFiles(formData: FormData, options?: ReceiveFileOptions): Promise<UploadedFile[]>;
1157
+ /**
1158
+ * Save data to a file. Auto-creates parent directories when needed.
1159
+ * Works across Bun, Node.js, and Deno.
1160
+ *
1161
+ * @example
1162
+ * ```ts
1163
+ * await saveFile('./uploads/photo.jpg', file.data);
1164
+ * await saveFile('./data/config.json', JSON.stringify(config));
1165
+ * ```
1166
+ */
1167
+ declare function saveFile(filePath: string, data: Blob | ArrayBuffer | Uint8Array | string): Promise<void>;
1168
+
1169
+ /**
1170
+ * Ken Framework - Basic Authentication Middleware
1171
+ * Validates HTTP Basic Authentication credentials
1172
+ *
1173
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1174
+ */
1175
+
1176
+ /**
1177
+ * Options for the Basic Auth middleware.
1178
+ */
1179
+ interface BasicAuthOptions {
1180
+ /** Expected username (required if verifyUser is not provided) */
1181
+ username?: string;
1182
+ /** Expected password (required if verifyUser is not provided) */
1183
+ password?: string;
1184
+ /** Realm for WWW-Authenticate header (default: "Secure Area") */
1185
+ realm?: string;
1186
+ /** Custom verification function. Return true to allow access. */
1187
+ verifyUser?: (username: string, password: string, ctx: Context) => boolean | Promise<boolean>;
1188
+ }
1189
+ /**
1190
+ * Basic Authentication middleware.
1191
+ *
1192
+ * Validates the `Authorization: Basic <base64>` header.
1193
+ * Returns 401 Response if credentials are missing or invalid.
1194
+ *
1195
+ * @example
1196
+ * ```ts
1197
+ * // Static credentials
1198
+ * app.get("/admin", {
1199
+ * state: { auth: basicAuth({ username: 'admin', password: 'secret' }) }
1200
+ * }, (ctx) => Response.json({ user: ctx.state.auth.username }));
1201
+ *
1202
+ * // Custom verification
1203
+ * app.get("/admin", {
1204
+ * state: { auth: basicAuth({ verifyUser: (u, p) => u === 'admin' && p === 'secret' }) }
1205
+ * }, (ctx) => Response.json({ user: ctx.state.auth.username }));
1206
+ * ```
1207
+ */
1208
+ declare function basicAuth(options: BasicAuthOptions): ((ctx: Context) => Promise<{
1209
+ username: string;
1210
+ } | Response>) | ((ctx: Context) => {
1211
+ username: string;
1212
+ } | Response);
1213
+
1214
+ /**
1215
+ * Ken Framework - Bearer Authentication Middleware
1216
+ * Validates Bearer token in the Authorization header
1217
+ *
1218
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1219
+ */
1220
+
1221
+ /**
1222
+ * Options for the Bearer Auth middleware.
1223
+ */
1224
+ interface BearerAuthOptions {
1225
+ /** Expected token(s). Ignored if verifyToken is provided. */
1226
+ token?: string | string[];
1227
+ /** Custom token verification function. Return true to allow access. */
1228
+ verifyToken?: (token: string, ctx: Context) => boolean | Promise<boolean>;
1229
+ /** Realm for WWW-Authenticate header (default: "") */
1230
+ realm?: string;
1231
+ /** Prefix/scheme for the Authorization header (default: "Bearer") */
1232
+ prefix?: string;
1233
+ /** Header name to read token from (default: "authorization") */
1234
+ headerName?: string;
1235
+ }
1236
+ /**
1237
+ * Bearer Authentication middleware.
1238
+ *
1239
+ * Validates the `Authorization: Bearer <token>` header.
1240
+ * Returns 401 Response if token is missing or invalid.
1241
+ *
1242
+ * @example
1243
+ * ```ts
1244
+ * app.get("/api/data", {
1245
+ * state: { auth: bearerAuth({ token: 'my-api-token' }) }
1246
+ * }, (ctx) => Response.json({ token: ctx.state.auth.token }));
1247
+ *
1248
+ * // Multiple valid tokens
1249
+ * app.get("/api/data", {
1250
+ * state: { auth: bearerAuth({ token: ['token-a', 'token-b'] }) }
1251
+ * }, (ctx) => Response.json({ token: ctx.state.auth.token }));
1252
+ *
1253
+ * // Custom verification
1254
+ * app.get("/api/data", {
1255
+ * state: { auth: bearerAuth({ verifyToken: async (t) => t === 'dynamic' }) }
1256
+ * }, (ctx) => Response.json({ token: ctx.state.auth.token }));
1257
+ * ```
1258
+ */
1259
+ declare function bearerAuth(options: BearerAuthOptions): ((ctx: Context) => Promise<{
1260
+ token: string;
1261
+ } | Response>) | ((ctx: Context) => {
1262
+ token: string;
1263
+ } | Response);
1264
+
1265
+ /**
1266
+ * Ken Framework - Body Limit Middleware
1267
+ * Rejects requests with bodies exceeding the configured size limit
1268
+ *
1269
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1270
+ */
1271
+
1272
+ /**
1273
+ * Options for the Body Limit middleware.
1274
+ */
1275
+ interface BodyLimitOptions {
1276
+ /** Maximum body size in bytes */
1277
+ maxSize: number;
1278
+ /** Custom error handler. By default throws 413 Payload Too Large. */
1279
+ onError?: (ctx: Context) => Response;
1280
+ }
1281
+ /**
1282
+ * Body Limit middleware.
1283
+ *
1284
+ * Checks the `Content-Length` header and rejects requests exceeding the limit.
1285
+ * Returns 413 Payload Too Large response if the body is too large.
1286
+ *
1287
+ * @example
1288
+ * ```ts
1289
+ * // Limit body to 1MB
1290
+ * app.post("/upload", {
1291
+ * state: { limit: bodyLimit({ maxSize: 1024 * 1024 }) }
1292
+ * }, async (ctx) => {
1293
+ * const body = await ctx.json;
1294
+ * return Response.json({ received: true });
1295
+ * });
1296
+ * ```
1297
+ */
1298
+ declare function bodyLimit(options: BodyLimitOptions): (ctx: Context) => void | Response;
1299
+
1300
+ /**
1301
+ * Ken Framework - Cache Middleware
1302
+ * Sets Cache-Control and related caching headers on responses
1303
+ *
1304
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1305
+ */
1306
+
1307
+ /**
1308
+ * Options for the Cache middleware.
1309
+ */
1310
+ interface CacheOptions {
1311
+ /** Cache-Control max-age in seconds (default: 0) */
1312
+ maxAge?: number;
1313
+ /** Cache-Control s-maxage in seconds (for shared caches/CDNs) */
1314
+ sMaxAge?: number;
1315
+ /** Cache-Control stale-while-revalidate in seconds */
1316
+ staleWhileRevalidate?: number;
1317
+ /** Cache-Control stale-if-error in seconds */
1318
+ staleIfError?: number;
1319
+ /** Set `public` directive (default: false) */
1320
+ public?: boolean;
1321
+ /** Set `private` directive (default: false) */
1322
+ private?: boolean;
1323
+ /** Set `no-cache` directive (default: false) */
1324
+ noCache?: boolean;
1325
+ /** Set `no-store` directive (default: false) */
1326
+ noStore?: boolean;
1327
+ /** Set `must-revalidate` directive (default: false) */
1328
+ mustRevalidate?: boolean;
1329
+ /** Set `proxy-revalidate` directive (default: false) */
1330
+ proxyRevalidate?: boolean;
1331
+ /** Set `immutable` directive (default: false) */
1332
+ immutable?: boolean;
1333
+ /** Set `no-transform` directive (default: false) */
1334
+ noTransform?: boolean;
1335
+ /** Custom Vary header value */
1336
+ vary?: string;
1337
+ }
1338
+ /**
1339
+ * Cache middleware.
1340
+ *
1341
+ * Adds `Cache-Control` and optionally `Vary` headers to responses.
1342
+ *
1343
+ * @example
1344
+ * ```ts
1345
+ * // Cache for 1 hour
1346
+ * app.get("/static", {
1347
+ * state: { caching: cache({ maxAge: 3600, public: true }) }
1348
+ * }, () => new Response("static content"));
1349
+ *
1350
+ * // No caching
1351
+ * app.get("/dynamic", {
1352
+ * state: { caching: cache({ noStore: true }) }
1353
+ * }, () => Response.json({ time: Date.now() }));
1354
+ * ```
1355
+ */
1356
+ declare function cache(options?: CacheOptions): (ctx: Context) => void;
1357
+
1358
+ /**
1359
+ * Ken Framework - Compress Middleware
1360
+ * Detects client-supported compression and provides encoding information
1361
+ *
1362
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1363
+ */
1364
+
1365
+ /**
1366
+ * Supported compression encodings.
1367
+ */
1368
+ type CompressionEncoding = 'gzip' | 'deflate' | 'br';
1369
+ /**
1370
+ * Options for the Compress middleware.
1371
+ */
1372
+ interface CompressOptions {
1373
+ /** Preferred encoding order (default: ['br', 'gzip', 'deflate']) */
1374
+ preferred?: CompressionEncoding[];
1375
+ /** Minimum body size in bytes to compress (default: 1024) */
1376
+ threshold?: number;
1377
+ }
1378
+ /**
1379
+ * Compress middleware.
1380
+ *
1381
+ * Parses the `Accept-Encoding` header and returns the best encoding
1382
+ * in state. Adds `Vary: Accept-Encoding` to responses.
1383
+ *
1384
+ * Note: Ken delegates actual body compression to the runtime layer.
1385
+ * Bun, Deno, and most reverse proxies handle compression natively.
1386
+ * This middleware provides encoding preference in state and sets
1387
+ * appropriate response headers.
1388
+ *
1389
+ * @example
1390
+ * ```ts
1391
+ * app.get("/data", {
1392
+ * state: { encoding: compress() }
1393
+ * }, (ctx) => {
1394
+ * // ctx.state.encoding.encoding is 'gzip' | 'deflate' | 'br' | null
1395
+ * return Response.json({ data: largePayload });
1396
+ * });
1397
+ * ```
1398
+ */
1399
+ declare function compress(options?: CompressOptions): (ctx: Context) => {
1400
+ encoding: CompressionEncoding | null;
1401
+ };
1402
+
1403
+ /**
1404
+ * Ken Framework - CORS Middleware
1405
+ * Cross-Origin Resource Sharing headers
1406
+ *
1407
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1408
+ */
1409
+
1410
+ /**
1411
+ * Options for the CORS middleware.
1412
+ */
1413
+ interface CorsOptions {
1414
+ /**
1415
+ * Allowed origin(s).
1416
+ * - `'*'` allows all origins (default)
1417
+ * - A string matches exactly
1418
+ * - An array of strings matches any
1419
+ * - A function receives the origin and returns the allowed origin
1420
+ */
1421
+ origin?: string | string[] | ((origin: string, ctx: Context) => string);
1422
+ /** Allowed HTTP methods (default: ['GET','HEAD','PUT','POST','DELETE','PATCH']) */
1423
+ allowMethods?: string[];
1424
+ /** Allowed request headers */
1425
+ allowHeaders?: string[];
1426
+ /** Headers exposed to the client */
1427
+ exposeHeaders?: string[];
1428
+ /** Max age for preflight cache in seconds */
1429
+ maxAge?: number;
1430
+ /** Allow credentials (cookies, authorization headers) */
1431
+ credentials?: boolean;
1432
+ }
1433
+ /**
1434
+ * CORS middleware.
1435
+ *
1436
+ * Returns an App instance that automatically handles preflight OPTIONS requests
1437
+ * and adds CORS headers to all responses for the specified path pattern.
1438
+ *
1439
+ * @example.
1440
+ *
1441
+ * @example
1442
+ * ```ts
1443
+ * // Use CORS for all routes - mount at root
1444
+ * const corsApp = cors();
1445
+ * corsApp.get("/api/data", () => Response.json({ data: 1 }));
1446
+ * app.use(corsApp);
1447
+ *
1448
+ * // Use CORS for specific path prefix
1449
+ * const apiCors = cors({ origin: 'https://example.com', credentials: true });
1450
+ * apiCors.get("/data", () => Response.json({ data: 1 }));
1451
+ * app.use('/api', apiCors);
1452
+ * ```
1453
+ */
1454
+ declare function cors(options?: CorsOptions): App;
1455
+
1456
+ /**
1457
+ * Ken Framework - CSRF Protection Middleware
1458
+ * Cross-Site Request Forgery protection
1459
+ *
1460
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1461
+ */
1462
+
1463
+ /**
1464
+ * Options for the CSRF middleware.
1465
+ */
1466
+ interface CsrfOptions {
1467
+ /**
1468
+ * Allowed origin(s).
1469
+ * - A string matches exactly
1470
+ * - An array of strings matches any
1471
+ * - A function receives the origin and returns true to allow
1472
+ * - If not set, only same-origin requests are allowed
1473
+ */
1474
+ origin?: string | string[] | ((origin: string, ctx: Context) => boolean);
1475
+ }
1476
+ /**
1477
+ * CSRF Protection middleware.
1478
+ *
1479
+ * Validates the `Origin` header on unsafe requests (POST, PUT, PATCH, DELETE)
1480
+ * with form-compatible content types. Returns 403 if the origin is not allowed.
1481
+ * Safe methods (GET, HEAD, OPTIONS) are always allowed.
1482
+ *
1483
+ * @example
1484
+ * ```ts
1485
+ * // Auto-detect same origin
1486
+ * app.define({ protection: csrf() }, (app) => {
1487
+ * app.post("/submit", async (ctx) => {
1488
+ * const body = await ctx.json;
1489
+ * return Response.json({ success: true });
1490
+ * });
1491
+ * });
1492
+ *
1493
+ * // Specific origin
1494
+ * app.post("/submit", {
1495
+ * state: { protection: csrf({ origin: 'https://myapp.example.com' }) }
1496
+ * }, async (ctx) => Response.json({ success: true }));
1497
+ * ```
1498
+ */
1499
+ declare function csrf(options?: CsrfOptions): (ctx: Context) => void | Response;
1500
+
1501
+ /**
1502
+ * Ken Framework - ETag Middleware
1503
+ * Provides If-None-Match header value for conditional requests
1504
+ *
1505
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1506
+ */
1507
+
1508
+ /**
1509
+ * Options for the ETag middleware.
1510
+ */
1511
+ interface ETagOptions {
1512
+ /** Use weak ETags (prefixed with W/) (default: false) */
1513
+ weak?: boolean;
1514
+ }
1515
+ /**
1516
+ * ETag middleware.
1517
+ *
1518
+ * Reads the `If-None-Match` request header and returns its value in state.
1519
+ * The handler can compare this with its computed ETag to return 304.
1520
+ *
1521
+ * @example
1522
+ * ```ts
1523
+ * app.get("/data", {
1524
+ * state: { etagValue: etag() }
1525
+ * }, (ctx) => {
1526
+ * const tag = '"data-v1"';
1527
+ * if (ctx.state.etagValue === tag) {
1528
+ * return new Response(null, { status: 304 });
1529
+ * }
1530
+ * return new Response("content", { headers: { 'ETag': tag } });
1531
+ * });
1532
+ * ```
1533
+ */
1534
+ declare function etag(_options?: ETagOptions): (ctx: Context) => string | null;
1535
+
1536
+ /**
1537
+ * Ken Framework - IP Restriction Middleware
1538
+ * Allow or deny requests based on client IP address
1539
+ *
1540
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1541
+ */
1542
+
1543
+ /**
1544
+ * Options for the IP Restriction middleware.
1545
+ */
1546
+ interface IpRestrictionOptions {
1547
+ /** List of allowed IP addresses. If set, only these IPs are allowed. */
1548
+ allowList?: string[];
1549
+ /** List of denied IP addresses. Checked after allowList. */
1550
+ denyList?: string[];
1551
+ /** Custom error response factory */
1552
+ onError?: (ctx: Context) => Response;
1553
+ }
1554
+ /**
1555
+ * IP Restriction middleware.
1556
+ *
1557
+ * Restricts access based on client IP address using the remote info.
1558
+ * Returns 403 if the IP is not in the allow list or is in the deny list.
1559
+ *
1560
+ * Remote IP detection checks proxy headers (CF-Connecting-IP, X-Real-IP,
1561
+ * X-Forwarded-For, True-Client-IP) before falling back to runtime info.
1562
+ *
1563
+ * @example
1564
+ * ```ts
1565
+ * // Allow only specific IPs
1566
+ * app.get("/admin", {
1567
+ * state: { ipCheck: ipRestriction({ allowList: ['127.0.0.1', '::1'] }) }
1568
+ * }, () => Response.json({ admin: true }));
1569
+ *
1570
+ * // Deny specific IPs
1571
+ * app.get("/public", {
1572
+ * state: { ipCheck: ipRestriction({ denyList: ['10.0.0.1'] }) }
1573
+ * }, () => new Response("public content"));
1574
+ * ```
1575
+ */
1576
+ declare function ipRestriction(options: IpRestrictionOptions): (ctx: Context) => void | Response;
1577
+
1578
+ /**
1579
+ * Ken Framework - JWT Middleware
1580
+ * JSON Web Token verification using Web Crypto API
1581
+ *
1582
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1583
+ */
1584
+
1585
+ /**
1586
+ * JWT payload type
1587
+ */
1588
+ interface JWTPayload {
1589
+ /** Issuer */
1590
+ iss?: string;
1591
+ /** Subject */
1592
+ sub?: string;
1593
+ /** Audience */
1594
+ aud?: string | string[];
1595
+ /** Expiration time (seconds since epoch) */
1596
+ exp?: number;
1597
+ /** Not before (seconds since epoch) */
1598
+ nbf?: number;
1599
+ /** Issued at (seconds since epoch) */
1600
+ iat?: number;
1601
+ /** JWT ID */
1602
+ jti?: string;
1603
+ /** Custom claims */
1604
+ [key: string]: unknown;
1605
+ }
1606
+ /**
1607
+ * Supported HMAC algorithms
1608
+ */
1609
+ type JWTAlgorithm = 'HS256' | 'HS384' | 'HS512';
1610
+ /**
1611
+ * Options for the JWT middleware.
1612
+ */
1613
+ interface JWTOptions {
1614
+ /** HMAC secret key for signature verification */
1615
+ secret: string;
1616
+ /** Expected algorithm (default: 'HS256') */
1617
+ algorithm?: JWTAlgorithm;
1618
+ /** Expected issuer claim */
1619
+ issuer?: string;
1620
+ /** Expected audience claim */
1621
+ audience?: string;
1622
+ /** Header name (default: 'authorization') */
1623
+ headerName?: string;
1624
+ /** Token prefix (default: 'Bearer') */
1625
+ prefix?: string;
1626
+ /** Clock tolerance in seconds for exp/nbf checks (default: 0) */
1627
+ clockTolerance?: number;
1628
+ }
1629
+ /**
1630
+ * Verify a JWT token and return the payload.
1631
+ *
1632
+ * @param token - The JWT token string
1633
+ * @param secret - The HMAC secret key
1634
+ * @param options - Verification options
1635
+ * @returns The decoded JWT payload
1636
+ * @throws Error if the token is invalid
1637
+ */
1638
+ declare function verifyJwt(token: string, secret: string, options?: {
1639
+ algorithm?: JWTAlgorithm;
1640
+ issuer?: string;
1641
+ audience?: string;
1642
+ clockTolerance?: number;
1643
+ }): Promise<JWTPayload>;
1644
+ /**
1645
+ * Sign a JWT payload and return the token string.
1646
+ *
1647
+ * @param payload - The JWT payload
1648
+ * @param secret - The HMAC secret key
1649
+ * @param algorithm - The algorithm to use (default: 'HS256')
1650
+ * @returns The signed JWT token string
1651
+ */
1652
+ declare function signJwt(payload: JWTPayload, secret: string, algorithm?: JWTAlgorithm): Promise<string>;
1653
+ /**
1654
+ * Decode a JWT token without verification (unsafe - for debugging only).
1655
+ */
1656
+ declare function decodeJwt(token: string): {
1657
+ header: any;
1658
+ payload: JWTPayload;
1659
+ };
1660
+ /**
1661
+ * JWT middleware.
1662
+ *
1663
+ * Verifies the JWT token from the Authorization header using HMAC.
1664
+ * Returns 401 if the token is missing, invalid, or expired.
1665
+ * Returns the decoded payload.
1666
+ *
1667
+ * @example
1668
+ * ```ts
1669
+ * app.get("/protected", {
1670
+ * state: { auth: jwt({ secret: 'my-secret-key' }) }
1671
+ * }, (ctx) => Response.json({ user: ctx.state.auth.sub }));
1672
+ * ```
1673
+ */
1674
+ declare function jwt(options: JWTOptions): (ctx: Context) => Promise<JWTPayload | Response>;
1675
+
1676
+ /**
1677
+ * Ken Framework - JWK Middleware
1678
+ * JSON Web Key Set (JWKS) based JWT verification
1679
+ *
1680
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1681
+ */
1682
+
1683
+ /**
1684
+ * JWK key object
1685
+ */
1686
+ interface JWK {
1687
+ kty: string;
1688
+ kid?: string;
1689
+ use?: string;
1690
+ alg?: string;
1691
+ n?: string;
1692
+ e?: string;
1693
+ crv?: string;
1694
+ x?: string;
1695
+ y?: string;
1696
+ [key: string]: unknown;
1697
+ }
1698
+ /**
1699
+ * JWKS response
1700
+ */
1701
+ interface JWKS {
1702
+ keys: JWK[];
1703
+ }
1704
+ /**
1705
+ * Options for the JWK middleware.
1706
+ */
1707
+ interface JWKOptions {
1708
+ /** URL of the JWKS endpoint */
1709
+ jwksUrl?: string;
1710
+ /** Pre-loaded JWK keys (alternative to jwksUrl) */
1711
+ keys?: JWK[];
1712
+ /** Expected issuer claim */
1713
+ issuer?: string;
1714
+ /** Expected audience claim */
1715
+ audience?: string;
1716
+ /** Header name (default: 'authorization') */
1717
+ headerName?: string;
1718
+ /** Token prefix (default: 'Bearer') */
1719
+ prefix?: string;
1720
+ /** Clock tolerance in seconds for exp/nbf checks (default: 0) */
1721
+ clockTolerance?: number;
1722
+ /** Cache TTL for JWKS keys in milliseconds (default: 600000 = 10 min) */
1723
+ cacheTtl?: number;
1724
+ }
1725
+ /**
1726
+ * JWK middleware.
1727
+ *
1728
+ * Verifies JWT tokens using JSON Web Key Set (JWKS).
1729
+ * Supports RSA (RS256/384/512) and ECDSA (ES256/384/512) algorithms.
1730
+ * Keys are cached and refreshed automatically.
1731
+ *
1732
+ * @example
1733
+ * ```ts
1734
+ * // With JWKS URL (e.g., from Auth0, Firebase, etc.)
1735
+ * app.get("/protected", {
1736
+ * state: { auth: jwk({ jwksUrl: 'https://auth.example.com/.well-known/jwks.json' }) }
1737
+ * }, (ctx) => Response.json({ user: ctx.state.auth.sub }));
1738
+ *
1739
+ * // With pre-loaded keys
1740
+ * app.get("/protected", {
1741
+ * state: { auth: jwk({ keys: [{ kty: 'RSA', n: '...', e: '...' }] }) }
1742
+ * }, (ctx) => Response.json({ user: ctx.state.auth.sub }));
1743
+ * ```
1744
+ */
1745
+ declare function jwk(options: JWKOptions): (ctx: Context) => Promise<JWTPayload | Response>;
1746
+
1747
+ /**
1748
+ * Ken Framework - Logger Middleware
1749
+ * Logs request/response information
1750
+ *
1751
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1752
+ */
1753
+
1754
+ /**
1755
+ * Options for the Logger middleware.
1756
+ */
1757
+ interface LoggerOptions {
1758
+ /** Custom log function (default: console.log) */
1759
+ logFn?: (message: string) => void;
1760
+ /**
1761
+ * Custom log format function.
1762
+ * Receives request info and returns the log message.
1763
+ */
1764
+ format?: (info: {
1765
+ method: string;
1766
+ url: string;
1767
+ status: number;
1768
+ duration: number;
1769
+ }) => string;
1770
+ }
1771
+ /**
1772
+ * Logger middleware.
1773
+ *
1774
+ * Returns an App instance that logs all requests for all HTTP methods.
1775
+ * Captures method, URL, status code, and response time.
1776
+ *
1777
+ * @example
1778
+ * ```ts
1779
+ * // Use logger for all routes (default console.log with colors)
1780
+ * app.use(logger());
1781
+ * app.get("/api/data", () => Response.json({ data: 1 }));
1782
+ *
1783
+ * // Use logger for specific path prefix
1784
+ * app.use('/api', logger());
1785
+ *
1786
+ * // Custom log function
1787
+ * const logs: string[] = [];
1788
+ * app.use(logger({ logFn: (msg) => logs.push(msg) }));
1789
+ * ```
1790
+ */
1791
+ declare function logger(options?: LoggerOptions): App;
1792
+
1793
+ /**
1794
+ * Ken Framework - Request ID Middleware
1795
+ * Generates a unique request identifier
1796
+ *
1797
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1798
+ */
1799
+
1800
+ /**
1801
+ * Options for the Request ID middleware.
1802
+ */
1803
+ interface RequestIdOptions {
1804
+ /** Custom ID generator (default: crypto.randomUUID) */
1805
+ generator?: () => string;
1806
+ /** Header name to set on the response (default: 'X-Request-Id') */
1807
+ headerName?: string;
1808
+ /** Header name to read from the incoming request. If present, reuses the ID. */
1809
+ requestHeaderName?: string;
1810
+ }
1811
+ /**
1812
+ * Request ID middleware.
1813
+ *
1814
+ * Generates a unique ID for each request and returns it in state.
1815
+ * Also adds the ID to the response via the specified header.
1816
+ *
1817
+ * @example
1818
+ * ```ts
1819
+ * app.get("/api/data", {
1820
+ * state: { reqId: requestId() }
1821
+ * }, (ctx) => Response.json({ id: ctx.state.reqId }));
1822
+ * ```
1823
+ */
1824
+ declare function requestId(options?: RequestIdOptions): (ctx: Context) => string;
1825
+
1826
+ /**
1827
+ * Ken Framework - Secure Headers Middleware
1828
+ * Adds security-related HTTP headers to responses
1829
+ *
1830
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1831
+ */
1832
+
1833
+ /**
1834
+ * Options for the Secure Headers middleware.
1835
+ * Set a header to `false` to disable it, or a string to override its value.
1836
+ */
1837
+ interface SecureHeadersOptions {
1838
+ /** Content-Security-Policy (default: not set) */
1839
+ contentSecurityPolicy?: string | false;
1840
+ /** Cross-Origin-Embedder-Policy (default: not set) */
1841
+ crossOriginEmbedderPolicy?: string | false;
1842
+ /** Cross-Origin-Opener-Policy (default: 'same-origin') */
1843
+ crossOriginOpenerPolicy?: string | false;
1844
+ /** Cross-Origin-Resource-Policy (default: 'same-origin') */
1845
+ crossOriginResourcePolicy?: string | false;
1846
+ /** Origin-Agent-Cluster (default: '?1') */
1847
+ originAgentCluster?: string | false;
1848
+ /** Referrer-Policy (default: 'no-referrer') */
1849
+ referrerPolicy?: string | false;
1850
+ /** Strict-Transport-Security (default: 'max-age=15552000; includeSubDomains') */
1851
+ strictTransportSecurity?: string | false;
1852
+ /** X-Content-Type-Options (default: 'nosniff') */
1853
+ xContentTypeOptions?: string | false;
1854
+ /** X-DNS-Prefetch-Control (default: 'off') */
1855
+ xDnsPrefetchControl?: string | false;
1856
+ /** X-Download-Options (default: 'noopen') */
1857
+ xDownloadOptions?: string | false;
1858
+ /** X-Frame-Options (default: 'SAMEORIGIN') */
1859
+ xFrameOptions?: string | false;
1860
+ /** X-Permitted-Cross-Domain-Policies (default: 'none') */
1861
+ xPermittedCrossDomainPolicies?: string | false;
1862
+ /** X-XSS-Protection (default: '0') */
1863
+ xXssProtection?: string | false;
1864
+ /** Remove X-Powered-By header (default: true) */
1865
+ removePoweredBy?: boolean;
1866
+ }
1867
+ /**
1868
+ * Secure Headers middleware.
1869
+ *
1870
+ * Adds security-related HTTP headers to responses. Uses sensible
1871
+ * defaults inspired by Helmet. Individual headers can be overridden
1872
+ * or disabled.
1873
+ *
1874
+ * @example
1875
+ * ```ts
1876
+ * // Use sensible defaults
1877
+ * app.define({ sec: secureHeaders() }, (app) => {
1878
+ * app.get("/", () => new Response("secure"));
1879
+ * });
1880
+ *
1881
+ * // Customize
1882
+ * app.define({ sec: secureHeaders({
1883
+ * xFrameOptions: 'DENY',
1884
+ * contentSecurityPolicy: "default-src 'self'",
1885
+ * }) }, (app) => {
1886
+ * app.get("/", () => new Response("secure"));
1887
+ * });
1888
+ * ```
1889
+ */
1890
+ declare function secureHeaders(options?: SecureHeadersOptions): (ctx: Context) => void;
1891
+
1892
+ /**
1893
+ * Ken Framework - Session Middleware
1894
+ * Flexible session management with customizable validation logic.
1895
+ * Auto-detects sync vs async validation at initialization time.
1896
+ *
1897
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1898
+ */
1899
+
1900
+ /**
1901
+ * Options for the Session middleware.
1902
+ */
1903
+ interface SessionOptions<T = any> {
1904
+ /** Name of the cookie to read session from */
1905
+ cookieName: string;
1906
+ /**
1907
+ * Custom validation function.
1908
+ * Receives the cookie value and context.
1909
+ * Return session data on success, or throw/return a Response to deny access.
1910
+ * Returning null/undefined also triggers the unauthorized handler.
1911
+ */
1912
+ validate: (cookieValue: string, ctx: Context) => T | Response | Promise<T | Response>;
1913
+ /**
1914
+ * Custom unauthorized response factory.
1915
+ * Called when cookie is missing, validation returns null, or throws a non-Response error.
1916
+ * Defaults to `new Response('Unauthorized', { status: 401 })`.
1917
+ */
1918
+ onUnauthorized?: (ctx: Context) => Response;
1919
+ }
1920
+ /**
1921
+ * Session middleware with auto-detection of sync vs async validation.
1922
+ *
1923
+ * Reads a cookie, passes it to a user-supplied `validate` function,
1924
+ * and either returns session data into `ctx.state` or short-circuits
1925
+ * with a `Response`. The sync/async branch is resolved once at
1926
+ * initialization time via `constructor.name` check — zero per-request overhead.
1927
+ *
1928
+ * @example
1929
+ * ```ts
1930
+ * // Sync — in-memory lookup
1931
+ * const auth = session({
1932
+ * cookieName: '_sid',
1933
+ * validate: (sid) => {
1934
+ * const user = USERS.get(sid);
1935
+ * if (!user) throw new Response('Unauthorized', { status: 401 });
1936
+ * return user;
1937
+ * },
1938
+ * });
1939
+ *
1940
+ * // Async — encrypted cookie + DB lookup
1941
+ * const auth = session({
1942
+ * cookieName: '_ak',
1943
+ * validate: async (cookieValue, ctx) => {
1944
+ * const apiKey = await decryptString(cookieValue, secretKey);
1945
+ * if (!MEDIA_OWNERS[apiKey]?.active) {
1946
+ * throw new Response(null, {
1947
+ * status: 302,
1948
+ * headers: { 'Location': '/auth?redirect=' + ctx.url },
1949
+ * });
1950
+ * }
1951
+ * return apiKey;
1952
+ * },
1953
+ * });
1954
+ *
1955
+ * app.get('/', { state: { session: auth } }, (ctx) => {
1956
+ * return Response.json({ key: ctx.state.session });
1957
+ * });
1958
+ * ```
1959
+ */
1960
+ declare function session<T = any>(options: SessionOptions<T>): ((ctx: Context) => Promise<T | Response>) | ((ctx: Context) => T | Response);
1961
+
1962
+ /**
1963
+ * Ken Framework - Timeout Middleware
1964
+ * Provides request timeout capabilities via AbortSignal
1965
+ *
1966
+ * MIT License - Copyright (c) 2025 Indra Gunawan
1967
+ */
1968
+
1969
+ /**
1970
+ * Options for the Timeout middleware.
1971
+ */
1972
+ interface TimeoutOptions {
1973
+ /** Timeout duration in milliseconds */
1974
+ duration: number;
1975
+ /** Custom timeout response (default: 408 Request Timeout) */
1976
+ onTimeout?: (ctx: Context) => Response;
1977
+ }
1978
+ /**
1979
+ * Timeout middleware.
1980
+ *
1981
+ * Creates an AbortController with a timeout. Returns the AbortSignal
1982
+ * in state so the handler can check for timeout or pass it to fetch calls.
1983
+ * The timer is automatically cleared when the response finishes.
1984
+ *
1985
+ * @example
1986
+ * ```ts
1987
+ * app.get("/slow-endpoint", {
1988
+ * state: { timeoutSig: timeout({ duration: 5000 }) }
1989
+ * }, async (ctx) => {
1990
+ * // Pass signal to downstream fetch calls
1991
+ * const data = await fetch('https://api.example.com/data', {
1992
+ * signal: ctx.state.timeoutSig.signal,
1993
+ * });
1994
+ * return Response.json(await data.json());
1995
+ * });
1996
+ * ```
1997
+ */
1998
+ declare function timeout(options: TimeoutOptions): (ctx: Context) => {
1999
+ signal: AbortSignal;
2000
+ } | Response;
2001
+
2002
+ /**
2003
+ * Ken Framework - Timing Middleware
2004
+ * Server-Timing header for performance monitoring
2005
+ *
2006
+ * MIT License - Copyright (c) 2025 Indra Gunawan
2007
+ */
2008
+
2009
+ /**
2010
+ * Options for the Timing middleware.
2011
+ */
2012
+ interface TimingOptions {
2013
+ /** Metric name (default: 'total') */
2014
+ name?: string;
2015
+ /** Description for the Server-Timing entry */
2016
+ description?: string;
2017
+ /** Whether to include the timing header (default: true) */
2018
+ enabled?: boolean;
2019
+ }
2020
+ /**
2021
+ * Timing middleware.
2022
+ *
2023
+ * Measures request processing time and adds a `Server-Timing` header
2024
+ * to the response. Useful for performance monitoring.
2025
+ *
2026
+ * @example
2027
+ * ```ts
2028
+ * app.define({ perf: timing() }, (app) => {
2029
+ * app.get("/api/data", () => Response.json({ data: 1 }));
2030
+ * });
2031
+ * // Response header: Server-Timing: total;dur=2.5
2032
+ *
2033
+ * // Custom metric name
2034
+ * app.get("/api/data", {
2035
+ * state: { perf: timing({ name: 'app', description: 'Application processing' }) }
2036
+ * }, () => Response.json({ data: 1 }));
2037
+ * // Response header: Server-Timing: app;desc="Application processing";dur=2.5
2038
+ * ```
2039
+ */
2040
+ declare function timing(options?: TimingOptions): (ctx: Context) => void;
2041
+
2042
+ /**
2043
+ * Ken Framework - WSClient Protocol Middleware
2044
+ * Transparently handles the Ken Binary WebSocket Protocol (KBWP) for WSClient users.
2045
+ *
2046
+ * Includes:
2047
+ * - wsClientProtocol: registers a WebSocket route with full KBWP support
2048
+ *
2049
+ * MIT License - Copyright (c) 2025 Indra Gunawan
2050
+ */
2051
+
2052
+ /**
2053
+ * Combined handler + options definition passed to {@link wsClientProtocol}.
2054
+ *
2055
+ * Merges all {@link WsHandler} callbacks and {@link WsOptions} fields into
2056
+ * a single object so there is only one place to configure a WebSocket route.
2057
+ *
2058
+ * @template T - Per-connection data type
2059
+ */
2060
+ interface WsDefinition<T = unknown> extends WsHandler<T> {
2061
+ /** @see WsOptions.maxPayloadLength */
2062
+ maxPayloadLength?: number;
2063
+ /** @see WsOptions.backpressureLimit */
2064
+ backpressureLimit?: number;
2065
+ /** @see WsOptions.pingInterval */
2066
+ pingInterval?: number;
2067
+ /** @see WsOptions.pongTimeout */
2068
+ pongTimeout?: number;
2069
+ /** @see WsOptions.perMessageDeflate */
2070
+ perMessageDeflate?: boolean;
2071
+ /** @see WsOptions.idleTimeout */
2072
+ idleTimeout?: number;
2073
+ /**
2074
+ * Authentication handler. When provided, all connections must authenticate
2075
+ * via post-connect AUTH binary frame or URL query parameter (dev/staging).
2076
+ *
2077
+ * Called with the token string. Return user data (`T`) on success,
2078
+ * or `null` to reject the connection.
2079
+ *
2080
+ * When not provided, auth is disabled — all connections pass through.
2081
+ *
2082
+ * @example
2083
+ * ```ts
2084
+ * wsClientProtocol<{ userId: string }>({
2085
+ * authenticate: async (token) => {
2086
+ * const user = await verifyJwt(token);
2087
+ * if (!user) return null;
2088
+ * return { userId: user.id };
2089
+ * },
2090
+ * message(peer, msg) {
2091
+ * // peer.data.userId is available here
2092
+ * },
2093
+ * });
2094
+ * ```
2095
+ */
2096
+ authenticate?: (token: string) => T | null | Promise<T | null>;
2097
+ /**
2098
+ * URL query parameter name for token-based auth (dev/staging support).
2099
+ * When a connection URL contains this parameter, the token is validated
2100
+ * during upgrade and the connection is pre-authenticated.
2101
+ * Only used when `authenticate` is provided.
2102
+ * @default 'token'
2103
+ */
2104
+ tokenParam?: string;
2105
+ }
2106
+ /**
2107
+ * WebSocket middleware with full WSClient binary protocol (KBWP) support.
2108
+ *
2109
+ * Registers a WebSocket route at '/' and transparently handles the Ken
2110
+ * Binary WebSocket Protocol — heartbeat (ping/pong), pub/sub, and
2111
+ * request-response — so your `message` handler only receives domain messages.
2112
+ *
2113
+ * Returns an `App` instance so it composes naturally with `app.use()`.
2114
+ * The path is supplied via the `app.use()` call, which prepends it to the
2115
+ * WebSocket route.
2116
+ *
2117
+ * **Binary protocol messages intercepted (client → server):**
2118
+ * - PING (0x01) → responds with PONG (0x02)
2119
+ * - SUBSCRIBE (0x05) → calls `peer.subscribe(topic)`
2120
+ * - UNSUBSCRIBE (0x06) → calls `peer.unsubscribe(topic)`
2121
+ * - PUBLISH (0x07) → calls `peer.publish(topic, jsonEnvelope)`
2122
+ * so all topic subscribers receive a `{"type":"message",...}` envelope
2123
+ * that `WSClient` knows how to route.
2124
+ * - REQUEST (0x03) → calls user `message` handler with decoded payload;
2125
+ * the first `peer.send()` is automatically wrapped in a RESPONSE (0x04)
2126
+ * frame echoing the correlation ID.
2127
+ *
2128
+ * **Non-binary messages** (plain strings) are forwarded directly to the
2129
+ * user `message` handler without any protocol interception.
2130
+ *
2131
+ * All `WsOptions` fields can be co-located in the `config` object.
2132
+ *
2133
+ * @example
2134
+ * ```ts
2135
+ * // Mount at a path (no auth)
2136
+ * app.use('/chat', wsClientProtocol({
2137
+ * pingInterval: 20,
2138
+ * idleTimeout: 60,
2139
+ * upgrade(req) {
2140
+ * const name = new URL(req.url).searchParams.get('name') ?? 'anon';
2141
+ * return { username: name };
2142
+ * },
2143
+ * open(peer) { peer.subscribe('chat'); },
2144
+ * message(peer, msg) { peer.send(`echo: ${msg}`); },
2145
+ * close(peer) { peer.unsubscribe('chat'); },
2146
+ * }));
2147
+ *
2148
+ * // With post-connect binary auth (secure, invisible in logs)
2149
+ * app.use('/ws', wsClientProtocol<{ userId: string }>({
2150
+ * authenticate: async (token) => {
2151
+ * const user = await verifyJwt(token);
2152
+ * return user ? { userId: user.id } : null;
2153
+ * },
2154
+ * // tokenParam: 'token', // also accepts ?token= in URL (for Insomnia/dev)
2155
+ * open(peer) { console.log('authed:', peer.data.userId); },
2156
+ * message(peer, msg) { peer.send(msg); },
2157
+ * }));
2158
+ * ```
2159
+ *
2160
+ * @template T - Per-connection data type
2161
+ * @param config - Handler callbacks and optional WsOptions, co-located in one object
2162
+ * @returns An `App` instance ready to be passed to `app.use()`
2163
+ */
2164
+ declare function wsClientProtocol<T = unknown>(config: WsDefinition<T>): App;
2165
+
2166
+ /**
2167
+ * Ken Framework - Binary WebSocket Protocol Codec (KBWP)
2168
+ *
2169
+ * Compact binary framing for WebSocket messages optimized for bandwidth-
2170
+ * constrained environments (4G, IoT, mobile). Eliminates JSON protocol
2171
+ * overhead while preserving full pub/sub and request-response semantics.
2172
+ *
2173
+ * Frame layout (all multi-byte integers are big-endian):
2174
+ *
2175
+ * ┌──────────┬──────────────────────────────────────────┐
2176
+ * │ Type (1B)│ Type-specific fields (variable) │
2177
+ * └──────────┴──────────────────────────────────────────┘
2178
+ *
2179
+ * Message types:
2180
+ * 0x01 PING [] 1 byte
2181
+ * 0x02 PONG [] 1 byte
2182
+ * 0x03 REQUEST [corrId:u32] [payload…] 5+ bytes
2183
+ * 0x04 RESPONSE [corrId:u32] [payload…] 5+ bytes
2184
+ * 0x05 SUBSCRIBE [topicLen:u8] [topic…] 2+ bytes
2185
+ * 0x06 UNSUBSCRIBE [topicLen:u8] [topic…] 2+ bytes
2186
+ * 0x07 PUBLISH [topicLen:u8] [topic…] [payload…] 2+ bytes
2187
+ * 0x08 MESSAGE [topicLen:u8] [topic…] [payload…] 2+ bytes
2188
+ * 0x09 AUTH [payload…] 1+ bytes
2189
+ * 0x0A AUTH_OK [payload…] 1+ bytes
2190
+ * 0x0B AUTH_FAIL [payload…] 1+ bytes
2191
+ *
2192
+ * Payload is the remaining bytes after fixed-length fields — no length
2193
+ * prefix needed because WebSocket frames are already length-delimited.
2194
+ *
2195
+ * Bandwidth comparison vs JSON:
2196
+ * Ping: 1 byte vs 15 bytes ({"type":"ping"}) → 93% reduction
2197
+ * Subscribe: ~6 bytes vs ~35 bytes → 83% reduction
2198
+ * Request: 5 + N vs 20 + N (JSON _kid overhead) → significant
2199
+ *
2200
+ * MIT License - Copyright (c) 2025 Indra Gunawan
2201
+ */
2202
+ /** Binary protocol message type identifiers */
2203
+ declare const MsgType: {
2204
+ readonly PING: 1;
2205
+ readonly PONG: 2;
2206
+ readonly REQUEST: 3;
2207
+ readonly RESPONSE: 4;
2208
+ readonly SUBSCRIBE: 5;
2209
+ readonly UNSUBSCRIBE: 6;
2210
+ readonly PUBLISH: 7;
2211
+ readonly MESSAGE: 8;
2212
+ readonly AUTH: 9;
2213
+ readonly AUTH_OK: 10;
2214
+ readonly AUTH_FAIL: 11;
2215
+ };
2216
+ type MsgTypeValue = (typeof MsgType)[keyof typeof MsgType];
2217
+ interface DecodedPing {
2218
+ readonly type: typeof MsgType.PING;
2219
+ }
2220
+ interface DecodedPong {
2221
+ readonly type: typeof MsgType.PONG;
2222
+ }
2223
+ interface DecodedRequest {
2224
+ readonly type: typeof MsgType.REQUEST;
2225
+ readonly corrId: number;
2226
+ /** Raw JSON string payload (caller parses if needed) */
2227
+ readonly payload: string;
2228
+ }
2229
+ interface DecodedResponse {
2230
+ readonly type: typeof MsgType.RESPONSE;
2231
+ readonly corrId: number;
2232
+ /** Raw JSON string payload (caller parses if needed) */
2233
+ readonly payload: string;
2234
+ }
2235
+ interface DecodedSubscribe {
2236
+ readonly type: typeof MsgType.SUBSCRIBE;
2237
+ readonly topic: string;
2238
+ }
2239
+ interface DecodedUnsubscribe {
2240
+ readonly type: typeof MsgType.UNSUBSCRIBE;
2241
+ readonly topic: string;
2242
+ }
2243
+ interface DecodedPublish {
2244
+ readonly type: typeof MsgType.PUBLISH;
2245
+ readonly topic: string;
2246
+ /** Raw JSON string payload (caller parses if needed) */
2247
+ readonly payload: string;
2248
+ }
2249
+ interface DecodedMessage {
2250
+ readonly type: typeof MsgType.MESSAGE;
2251
+ readonly topic: string;
2252
+ /** Raw JSON string payload (caller parses if needed) */
2253
+ readonly payload: string;
2254
+ }
2255
+ interface DecodedAuth {
2256
+ readonly type: typeof MsgType.AUTH;
2257
+ /** Auth token */
2258
+ readonly payload: string;
2259
+ }
2260
+ interface DecodedAuthOk {
2261
+ readonly type: typeof MsgType.AUTH_OK;
2262
+ /** Optional data from server (may be empty) */
2263
+ readonly payload: string;
2264
+ }
2265
+ interface DecodedAuthFail {
2266
+ readonly type: typeof MsgType.AUTH_FAIL;
2267
+ /** Reason string */
2268
+ readonly payload: string;
2269
+ }
2270
+ type DecodedFrame = DecodedPing | DecodedPong | DecodedRequest | DecodedResponse | DecodedSubscribe | DecodedUnsubscribe | DecodedPublish | DecodedMessage | DecodedAuth | DecodedAuthOk | DecodedAuthFail;
2271
+ /** Returns the pre-allocated 1-byte PING frame. Do NOT mutate. */
2272
+ declare function encodePing(): Uint8Array;
2273
+ /** Returns the pre-allocated 1-byte PONG frame. Do NOT mutate. */
2274
+ declare function encodePong(): Uint8Array;
2275
+ /**
2276
+ * Encode a REQUEST frame: [0x03][corrId:u32][payload UTF-8…]
2277
+ *
2278
+ * @param corrId - Correlation ID (uint32, 0–4294967295)
2279
+ * @param payload - JSON string payload
2280
+ */
2281
+ declare function encodeRequest(corrId: number, payload: string): Uint8Array;
2282
+ /**
2283
+ * Encode a RESPONSE frame: [0x04][corrId:u32][payload UTF-8…]
2284
+ *
2285
+ * @param corrId - Correlation ID echoed from the REQUEST
2286
+ * @param payload - Response data (string or binary)
2287
+ */
2288
+ declare function encodeResponse(corrId: number, payload: string | Uint8Array): Uint8Array;
2289
+ /**
2290
+ * Encode a SUBSCRIBE frame: [0x05][topicLen:u8][topic UTF-8…]
2291
+ *
2292
+ * @param topic - Topic name (max 255 UTF-8 bytes)
2293
+ */
2294
+ declare function encodeSubscribe(topic: string): Uint8Array;
2295
+ /**
2296
+ * Encode an UNSUBSCRIBE frame: [0x06][topicLen:u8][topic UTF-8…]
2297
+ *
2298
+ * @param topic - Topic name (max 255 UTF-8 bytes)
2299
+ */
2300
+ declare function encodeUnsubscribe(topic: string): Uint8Array;
2301
+ /**
2302
+ * Encode a PUBLISH frame: [0x07][topicLen:u8][topic UTF-8…][payload UTF-8…]
2303
+ *
2304
+ * @param topic - Topic name (max 255 UTF-8 bytes)
2305
+ * @param payload - JSON string payload
2306
+ */
2307
+ declare function encodePublish(topic: string, payload: string): Uint8Array;
2308
+ /**
2309
+ * Encode a MESSAGE frame: [0x08][topicLen:u8][topic UTF-8…][payload UTF-8…]
2310
+ *
2311
+ * @param topic - Topic name (max 255 UTF-8 bytes)
2312
+ * @param payload - JSON string payload
2313
+ */
2314
+ declare function encodeMessage(topic: string, payload: string): Uint8Array;
2315
+ /**
2316
+ * Decode a binary WebSocket frame into a typed message.
2317
+ *
2318
+ * @param data - Raw binary data (ArrayBuffer or Uint8Array)
2319
+ * @returns Decoded frame, or `null` if the type byte is unrecognized or
2320
+ * the frame is too short for the declared type.
2321
+ */
2322
+ declare function decode(data: ArrayBuffer | Uint8Array): DecodedFrame | null;
2323
+ /**
2324
+ * Check if a binary buffer starts with a known Ken protocol type byte.
2325
+ * Use this as a fast pre-check before calling `decode()`.
2326
+ *
2327
+ * @param data - Binary data (first byte is checked)
2328
+ * @returns true if byte 0 is a recognized MsgType
2329
+ */
2330
+ declare function isKenBinaryFrame(data: ArrayBuffer | Uint8Array): boolean;
2331
+
2332
+ /**
2333
+ * Ken Framework - WSClient
2334
+ * High-performance, fault-tolerant WebSocket client with binary protocol.
2335
+ * Zero-dependency. Works on Bun, Deno, and Node.js.
2336
+ *
2337
+ * Uses the Ken Binary WebSocket Protocol (KBWP) for minimal bandwidth
2338
+ * overhead — optimized for 24/7 operation on bandwidth-constrained devices.
2339
+ *
2340
+ * MIT License - Copyright (c) 2025 Indra Gunawan
2341
+ */
2342
+
2343
+ /** Connection state enum — use numeric values for fast comparison */
2344
+ declare const WsClientState: {
2345
+ readonly CONNECTING: 0;
2346
+ readonly OPEN: 1;
2347
+ readonly CLOSING: 2;
2348
+ readonly CLOSED: 3;
2349
+ readonly RECONNECTING: 4;
2350
+ };
2351
+ type WsClientStateValue = (typeof WsClientState)[keyof typeof WsClientState];
2352
+ /**
2353
+ * WSClient configuration options
2354
+ */
2355
+ interface WsClientOptions {
2356
+ /**
2357
+ * Auth token. Sent as a binary AUTH frame (post-connect, secure)
2358
+ * or as a `?token=` URL param depending on `authMode`.
2359
+ */
2360
+ token?: string;
2361
+ /**
2362
+ * How to send the auth token:
2363
+ * - `'message'` — Post-connect binary AUTH frame (secure, invisible in logs). Default.
2364
+ * - `'query'` — URL query parameter (visible in logs, for dev tools like Insomnia).
2365
+ *
2366
+ * Only relevant when `token` is set. Ignored otherwise.
2367
+ * @default 'message'
2368
+ */
2369
+ authMode?: 'message' | 'query';
2370
+ /**
2371
+ * Milliseconds to wait for AUTH_OK after sending AUTH frame.
2372
+ * Only used when `authMode` is `'message'`.
2373
+ * @default 10_000
2374
+ */
2375
+ authTimeout?: number;
2376
+ /**
2377
+ * Maximum number of reconnect attempts. Default: Infinity.
2378
+ */
2379
+ maxRetries?: number;
2380
+ /**
2381
+ * Multiplier applied to the backoff delay after each failed attempt.
2382
+ * Default: 2
2383
+ */
2384
+ backoffFactor?: number;
2385
+ /**
2386
+ * Maximum backoff delay in ms. Default: 30_000
2387
+ */
2388
+ backoffMax?: number;
2389
+ /**
2390
+ * Base reconnect delay in ms. Default: 500
2391
+ */
2392
+ backoffBase?: number;
2393
+ /**
2394
+ * Milliseconds between application-level heartbeat pings.
2395
+ * Set to 0 to disable. Default: 30_000
2396
+ */
2397
+ heartbeatInterval?: number;
2398
+ /**
2399
+ * Milliseconds before a `sendWait()` call times out. Default: 10_000
2400
+ */
2401
+ requestTimeout?: number;
2402
+ /**
2403
+ * Event hook — called for every incoming binary protocol frame.
2404
+ * Receives the decoded frame object. Fires after internal routing
2405
+ * (REQUEST/RESPONSE correlation, pub/sub dispatch) has been applied.
2406
+ */
2407
+ onFrame?: (frame: DecodedFrame) => void;
2408
+ /**
2409
+ * Event hook — called when the connection is established.
2410
+ */
2411
+ onConnect?: () => void;
2412
+ /**
2413
+ * Event hook — called when the connection is lost.
2414
+ * Receives the WebSocket close code and reason.
2415
+ */
2416
+ onDisconnect?: (code: number, reason: string) => void;
2417
+ /**
2418
+ * Event hook — called before each reconnect attempt.
2419
+ * Receives the attempt number (1-based) and the delay in ms.
2420
+ */
2421
+ onReconnectAttempt?: (attempt: number, delay: number) => void;
2422
+ /**
2423
+ * Event hook — called for every incoming message.
2424
+ */
2425
+ onData?: (data: string | ArrayBuffer) => void;
2426
+ }
2427
+ /**
2428
+ * Ken WSClient — A fault-tolerant, high-performance WebSocket client
2429
+ * with binary protocol transport.
2430
+ *
2431
+ * Features:
2432
+ * - Ken Binary WebSocket Protocol (KBWP) — 80-93% bandwidth reduction
2433
+ * over JSON for protocol messages (ping/pong, subscribe, publish)
2434
+ * - State machine (CONNECTING / OPEN / CLOSING / CLOSED / RECONNECTING)
2435
+ * - Exponential backoff + jitter reconnection
2436
+ * - 1-byte binary heartbeat (ping/pong)
2437
+ * - Request-response via `sendWait()` with uint32 correlation IDs
2438
+ * (no field collision — corrId lives in binary header, not payload)
2439
+ * - Pub/sub with binary framing (subscribe/unsubscribe/publish/message)
2440
+ * - Full lifecycle hooks: onConnect, onDisconnect, onReconnectAttempt, onData
2441
+ * - Zero memory leaks — all timers and listeners are strictly cleaned up
2442
+ *
2443
+ * @example
2444
+ * ```ts
2445
+ * const client = new WSClient('wss://api.example.com/ws', {
2446
+ * token: 'my-jwt',
2447
+ * onConnect: () => console.log('Connected'),
2448
+ * onDisconnect: (code) => console.log('Disconnected', code),
2449
+ * });
2450
+ *
2451
+ * await client.connect();
2452
+ *
2453
+ * // Fire-and-forget (raw string — not wrapped in binary protocol)
2454
+ * client.send('hello');
2455
+ *
2456
+ * // Request-response (binary REQUEST frame, no _kid collision)
2457
+ * const result = await client.sendWait({ type: 'getData', id: 42 });
2458
+ *
2459
+ * // Pub/sub (binary SUBSCRIBE/PUBLISH frames)
2460
+ * client.subscribe('events', (data) => console.log(data));
2461
+ * client.publish('events', { action: 'click' });
2462
+ *
2463
+ * await client.close();
2464
+ * ```
2465
+ */
2466
+ declare class WSClient {
2467
+ /** Current state of the connection */
2468
+ state: WsClientStateValue;
2469
+ private readonly _url;
2470
+ private readonly _authTimeout;
2471
+ private readonly _maxRetries;
2472
+ private readonly _backoffFactor;
2473
+ private readonly _backoffMax;
2474
+ private readonly _backoffBase;
2475
+ private readonly _heartbeatInterval;
2476
+ private readonly _requestTimeout;
2477
+ private readonly _onConnect;
2478
+ private readonly _onDisconnect;
2479
+ private readonly _onReconnectAttempt;
2480
+ private readonly _onData;
2481
+ private readonly _onFrame;
2482
+ /** Active WebSocket instance */
2483
+ private _ws;
2484
+ /** Number of consecutive failed reconnect attempts */
2485
+ private _retries;
2486
+ /** Correlation registry: corrId (uint32) → PendingRequest */
2487
+ private _pending;
2488
+ /**
2489
+ * Active pub/sub subscriptions: topic → callback.
2490
+ * Persists across reconnects so topics are re-sent in _onOpen.
2491
+ */
2492
+ private _subscriptions;
2493
+ /** Heartbeat timer handle */
2494
+ private _heartbeatTimer;
2495
+ /** Reconnect delay timer handle */
2496
+ private _reconnectTimer;
2497
+ /** Whether destroy() / close() was explicitly called */
2498
+ private _destroyed;
2499
+ /** Post-connect auth resolve/reject for the connect() promise */
2500
+ private _authResolve;
2501
+ private _authReject;
2502
+ /** Auth timeout timer handle */
2503
+ private _authTimer;
2504
+ /** Whether auth is pending (post-connect, waiting for AUTH_OK/AUTH_FAIL) */
2505
+ private _authPending;
2506
+ /** Pre-encoded AUTH frame — zero per-reconnect allocation */
2507
+ private readonly _authFrame;
2508
+ /**
2509
+ * Monotonically increasing uint32 counter for correlation IDs.
2510
+ * Wraps at 0xFFFFFFFF to stay within 4-byte range.
2511
+ */
2512
+ private _idCounter;
2513
+ private readonly _handleOpen;
2514
+ private readonly _handleMessage;
2515
+ private readonly _handleClose;
2516
+ private readonly _handleError;
2517
+ constructor(url: string, options?: WsClientOptions);
2518
+ /**
2519
+ * Connect to the WebSocket server.
2520
+ * Returns a Promise that resolves when the connection is established.
2521
+ * Safe to call multiple times — subsequent calls while already OPEN are no-ops.
2522
+ */
2523
+ connect(): Promise<void>;
2524
+ /**
2525
+ * Send a raw string or binary message. The connection must be OPEN.
2526
+ * For fire-and-forget use cases.
2527
+ *
2528
+ * @returns true if sent, false if not connected.
2529
+ */
2530
+ send(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>): boolean;
2531
+ /**
2532
+ * Send a JSON message and wait for a correlated response.
2533
+ *
2534
+ * Uses the binary REQUEST frame format: [0x03][corrId:u32][JSON payload].
2535
+ * The correlation ID is carried in the binary header — NOT mixed into the
2536
+ * user payload — eliminating the previous `_kid` field collision issue.
2537
+ *
2538
+ * The server must respond with a binary RESPONSE frame echoing the same
2539
+ * corrId. When using `defineWsHandler`, this is handled automatically.
2540
+ *
2541
+ * @param data - Object to JSON-serialize and send.
2542
+ * @param timeout - Override the default `requestTimeout` for this request.
2543
+ * @returns Promise resolving with the parsed response payload.
2544
+ * @throws If the connection is not open, the request times out, or the
2545
+ * connection closes before a response arrives.
2546
+ */
2547
+ sendWait<T = unknown>(data: Record<string, unknown>, timeout?: number): Promise<T>;
2548
+ /**
2549
+ * Subscribe to a server-side topic.
2550
+ * Sends `{"type":"subscribe","topic":"..."}` to the server and registers a
2551
+ * local callback invoked for each `{"type":"message","topic":"...","data":...}`
2552
+ * frame received on that topic.
2553
+ *
2554
+ * Safe to call before `connect()` — the subscription is registered locally
2555
+ * and sent once the connection opens (or re-opens after reconnect).
2556
+ *
2557
+ * Calling with the same topic replaces the existing callback.
2558
+ *
2559
+ * @param topic - Topic name to subscribe to
2560
+ * @param callback - Called with the `data` field from each topic message
2561
+ */
2562
+ subscribe(topic: string, callback: (data: unknown) => void): void;
2563
+ /**
2564
+ * Unsubscribe from a server-side topic.
2565
+ * Sends `{"type":"unsubscribe","topic":"..."}` if currently connected.
2566
+ * The local callback is removed regardless of connection state.
2567
+ *
2568
+ * @param topic - Topic name to unsubscribe from
2569
+ */
2570
+ unsubscribe(topic: string): void;
2571
+ /**
2572
+ * Publish a message to a server-side topic.
2573
+ * Sends `{"type":"publish","topic":"...","data":...}` to the server.
2574
+ * The server (when using `defineWsHandler`) re-broadcasts the message to all
2575
+ * topic subscribers as `{"type":"message","topic":"...","data":...}`.
2576
+ *
2577
+ * @param topic - Topic to publish to
2578
+ * @param data - Payload (JSON-serializable)
2579
+ * @returns true if sent, false if not connected
2580
+ */
2581
+ publish(topic: string, data: unknown): boolean;
2582
+ /**
2583
+ * Gracefully close the connection.
2584
+ * Does NOT reconnect. Cleans up all resources.
2585
+ */
2586
+ close(code?: number, reason?: string): Promise<void>;
2587
+ private _openConnection;
2588
+ /** Open handler for auth mode — sends pre-encoded AUTH frame */
2589
+ private _onOpenAuth;
2590
+ /** Open handler for no-auth mode — proceeds directly to _finishOpen */
2591
+ private _onOpenNoAuth;
2592
+ /**
2593
+ * Complete the open sequence: re-subscribe, start heartbeat, fire onConnect.
2594
+ * Called directly from _onOpen (no auth) or from _onMessage after AUTH_OK.
2595
+ */
2596
+ private _finishOpen;
2597
+ private _onMessage;
2598
+ private _onClose;
2599
+ private _onError;
2600
+ private _scheduleReconnect;
2601
+ /**
2602
+ * Exponential backoff with full jitter to avoid thundering-herd reconnects.
2603
+ * delay = random(0, min(backoffMax, backoffBase * backoffFactor^attempt))
2604
+ */
2605
+ private _computeBackoff;
2606
+ private _clearReconnectTimer;
2607
+ private _startHeartbeat;
2608
+ private _stopHeartbeat;
2609
+ private _detachHandlers;
2610
+ /**
2611
+ * Returns a monotonically increasing uint32 correlation ID.
2612
+ * Wraps at 0xFFFFFFFF back to 1 (zero is reserved).
2613
+ * No random component needed — IDs are scoped to a single connection
2614
+ * and the uint32 space (4B values) far exceeds concurrent in-flight requests.
2615
+ */
2616
+ private _nextId;
2617
+ }
2618
+
2619
+ /**
2620
+ * Ken Framework - WebSocket Pub/Sub
2621
+ *
2622
+ * Two pub/sub abstractions:
2623
+ *
2624
+ * 1. PubSubHub — low-level exclude-sender pub/sub.
2625
+ * Used by Deno/Node peer adapters for WsPeer.subscribe / WsPeer.publish.
2626
+ * On Bun and uWS, native pub/sub is used directly by the peer adapters.
2627
+ *
2628
+ * 2. WsTopicHub — MQTT/Redis-style per-topic message routing.
2629
+ * Per-topic callbacks, include-all (broadcast) publish, dead peer detection.
2630
+ * Works uniformly across all runtimes.
2631
+ *
2632
+ * MIT License - Copyright (c) 2025 Indra Gunawan
2633
+ */
2634
+
2635
+ /**
2636
+ * Per-topic message callback.
2637
+ * Invoked when hub.dispatch(peer, message) is called for a subscribed peer.
2638
+ *
2639
+ * @template T - Per-connection data type
2640
+ */
2641
+ type TopicCallback<T = unknown> = (peer: WsPeer<T>, message: WsMessageData) => void | Promise<void>;
2642
+ /**
2643
+ * WsTopicHub — MQTT/Redis-style topic-based pub/sub for WebSocket servers.
2644
+ *
2645
+ * **Key differences from WsPeer.subscribe/publish:**
2646
+ * - `hub.subscribe(peer, topic, callback)` registers a per-topic message handler
2647
+ * - `hub.publish(topic, data)` broadcasts to ALL subscribers (including sender)
2648
+ * - `hub.dispatch(peer, msg)` routes a message to the peer's topic callbacks
2649
+ * - Dead peer detection via `markAlive()` + `startDeadPeerCheck()`
2650
+ * - Works uniformly across Bun, Node, Deno, and uWS
2651
+ *
2652
+ * Uses native runtime pub/sub (Bun/uWS `peer.subscribe/publish`) for "exclude self"
2653
+ * semantics when callers use `peer.publish()` directly. Hub publish() always uses
2654
+ * an in-memory iterator for include-all broadcast.
2655
+ *
2656
+ * @template T - Per-connection data type
2657
+ *
2658
+ * @example
2659
+ * ```ts
2660
+ * const hub = new WsTopicHub<{ username: string }>();
2661
+ *
2662
+ * app.ws<{ username: string }>('/chat', {
2663
+ * upgrade(req) {
2664
+ * const name = new URL(req.url).searchParams.get('name') ?? 'anon';
2665
+ * return { username: name };
2666
+ * },
2667
+ * open(peer) {
2668
+ * hub.subscribe(peer, 'chat', (peer, msg) => {
2669
+ * hub.publish('chat', `${peer.data.username}: ${msg}`);
2670
+ * });
2671
+ * },
2672
+ * message(peer, msg) {
2673
+ * hub.dispatch(peer, msg); // routes to topic callbacks
2674
+ * },
2675
+ * close(peer) {
2676
+ * hub.leave(peer); // cleanup all subscriptions
2677
+ * },
2678
+ * pong(peer) {
2679
+ * hub.markAlive(peer); // dead peer detection (Bun/Node/uWS)
2680
+ * },
2681
+ * }, { pingInterval: 30 });
2682
+ *
2683
+ * // Server-initiated broadcast (from any route or CRON)
2684
+ * hub.publish('chat', 'System: restarting in 60s');
2685
+ *
2686
+ * // Start automatic dead peer cleanup
2687
+ * hub.startDeadPeerCheck(5_000, 120_000);
2688
+ * ```
2689
+ */
2690
+ declare class WsTopicHub<T = unknown> {
2691
+ /** topic → Set<peer> — O(1) publish iteration */
2692
+ private _topics;
2693
+ /** peer → Map<topic, callback> — O(1) dispatch and cleanup */
2694
+ private _peerTopics;
2695
+ /** peer → last-pong timestamp (ms) — for dead peer detection */
2696
+ private _lastPong;
2697
+ private _deadPeerTimer;
2698
+ /**
2699
+ * Subscribe a peer to a topic with a per-message callback.
2700
+ *
2701
+ * The callback is invoked whenever `hub.dispatch(peer, msg)` is called while
2702
+ * the peer is subscribed to this topic.
2703
+ *
2704
+ * A peer may be subscribed to multiple topics simultaneously, each with its
2705
+ * own callback. Subsequent calls with the same topic replace the handler.
2706
+ *
2707
+ * @param peer - The connected WebSocket peer
2708
+ * @param topic - Topic name (e.g. 'chat', 'notifications', 'room/42')
2709
+ * @param handler - Called with (peer, message) on each dispatched message
2710
+ */
2711
+ subscribe(peer: WsPeer<T>, topic: string, handler: TopicCallback<T>): void;
2712
+ /**
2713
+ * Unsubscribe a peer from a specific topic.
2714
+ * The peer remains subscribed to any other topics.
2715
+ */
2716
+ unsubscribe(peer: WsPeer<T>, topic: string): void;
2717
+ /**
2718
+ * Remove a peer from ALL topics and clean up liveness state.
2719
+ * Call this from `WsHandler.close()` to prevent memory leaks.
2720
+ */
2721
+ leave(peer: WsPeer<T>): void;
2722
+ /**
2723
+ * Route an incoming message from a peer to all its registered topic handlers.
2724
+ * Call this from `WsHandler.message()`.
2725
+ *
2726
+ * If the peer is subscribed to multiple topics, the message is dispatched
2727
+ * to each topic's callback in insertion order.
2728
+ */
2729
+ dispatch(peer: WsPeer<T>, msg: WsMessageData): void;
2730
+ /**
2731
+ * Broadcast a message to ALL subscribers of a topic, including the sender.
2732
+ *
2733
+ * For "exclude self" semantics (the sender does not receive their own message),
2734
+ * use `peer.publish(topic, data)` instead — which on Bun and uWS routes through
2735
+ * the native C++ pub/sub engine.
2736
+ */
2737
+ publish(topic: string, data: WsMessageData, compress?: boolean): void;
2738
+ /**
2739
+ * Mark a peer as alive. Call from `WsHandler.pong()`.
2740
+ *
2741
+ * Works with native runtime protocol ping/pong (Bun, Node, uWS).
2742
+ * On Deno, calling this has no effect as `handler.pong` is never triggered.
2743
+ */
2744
+ markAlive(peer: WsPeer<T>): void;
2745
+ /**
2746
+ * Returns true if the peer last responded within `timeoutMs` milliseconds.
2747
+ */
2748
+ isPeerAlive(peer: WsPeer<T>, timeoutMs: number): boolean;
2749
+ /**
2750
+ * Close and remove all peers that have not called `markAlive()` within `timeoutMs`.
2751
+ * Recommended value: `(pingInterval + pongTimeout) * 1000`.
2752
+ */
2753
+ pruneDeadPeers(timeoutMs: number): void;
2754
+ /**
2755
+ * Start automatic dead peer detection at a fixed interval.
2756
+ *
2757
+ * @param checkIntervalMs - How often to scan for dead peers (ms)
2758
+ * @param timeoutMs - Peers unseen longer than this are closed and removed
2759
+ *
2760
+ * @example
2761
+ * ```ts
2762
+ * // Check every 5 seconds; close peers silent for 2 minutes
2763
+ * hub.startDeadPeerCheck(5_000, 120_000);
2764
+ *
2765
+ * // Integrate with WsOptions:
2766
+ * // { pingInterval: 30, pongTimeout: 10 }
2767
+ * // → hub.startDeadPeerCheck(5_000, (30 + 10) * 1000);
2768
+ * ```
2769
+ */
2770
+ startDeadPeerCheck(checkIntervalMs: number, timeoutMs: number): void;
2771
+ /**
2772
+ * Stop the automatic dead peer check started by `startDeadPeerCheck()`.
2773
+ */
2774
+ stopDeadPeerCheck(): void;
2775
+ /** Number of peers currently subscribed to a topic. */
2776
+ subscriberCount(topic: string): number;
2777
+ /** Returns true if a peer is subscribed to the given topic. */
2778
+ isSubscribed(peer: WsPeer<T>, topic: string): boolean;
2779
+ /** Returns an array of all active topic names. */
2780
+ topicNames(): string[];
2781
+ }
2782
+
2783
+ export { App, AppServer, type AppServerInit, type BasicAuthOptions, type BearerAuthOptions, type BodyLimitOptions, type CacheOptions, type CompressOptions$1 as CompressOptions, type CompressionEncoding, type Context, type CorsOptions, type CsrfOptions, type DecodedFrame, type DecompressOptions, type ETagOptions, type ErrorHandler, type FileEntry, type Flatten, type InferObject, type InferState, type InferValidator, type IpRestrictionOptions, type JWK, type JWKOptions, type JWKS, type JWTAlgorithm, type JWTOptions, type JWTPayload, type ListDirectoryOptions, type LoggerOptions, type MemoizeOptions, type MiddlewareHandler, MsgType, type MsgTypeValue, type ParamsFromPath, type ReceiveFileOptions, type RemoteInfo, type RequestIdOptions, type RouteInfo, type Schema, type SecureHeadersOptions, type SendFileOptions, type Server, type ServerOptions, type SessionOptions, type StateMiddleware, type TimeoutOptions, type TimingOptions, type TopicCallback, type TypedHandler, type UploadedFile, type Validator, WSClient, type WsClientOptions, WsClientState, type WsClientStateValue, type WsDefinition, type WsHandler, type WsMessageData, type WsOptions, type WsPeer, WsReadyState, type WsReadyStateValue, WsTopicHub, basicAuth, bearerAuth, bodyLimit, cache, compress, compressString, cors, csrf, decode as decodeFrame, decodeJwt, decompressString, decryptString, defaultErrorHandler, encodeMessage, encodePing, encodePong, encodePublish, encodeRequest, encodeResponse, encodeSubscribe, encodeUnsubscribe, encryptString, etag, generateSecretKey, getMimeType, getPathname, ipRestriction, isBun, isDeno, isKenBinaryFrame, isNode, jwk, jwt, listDirectory, logger, memoize, receiveFiles, requestId, saveFile, secureHeaders, sendFile, session, signJwt, timeout, timing, verifyJwt, wsClientProtocol };