routup 5.1.1 → 6.0.0-beta.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,1950 @@
1
+ import QuickLRU from "quick-lru";
2
+ import { FastURL, ServerRequest } from "srvx";
3
+ import { HTTPError, HTTPErrorInput, HTTPErrorInput as HTTPErrorInput$1 } from "@ebec/http";
4
+ import Negotiator from "negotiator";
5
+ import { Key, ParseOptions, PathToRegexpOptions } from "path-to-regexp";
6
+ import { IncomingMessage, ServerResponse } from "node:http";
7
+
8
+ //#region src/error/module.d.ts
9
+ declare const ErrorSymbol: unique symbol;
10
+ declare class AppError extends HTTPError {
11
+ constructor(input?: HTTPErrorInput$1);
12
+ }
13
+ //#endregion
14
+ //#region src/constants.d.ts
15
+ declare const MethodName: {
16
+ readonly GET: "GET";
17
+ readonly POST: "POST";
18
+ readonly PUT: "PUT";
19
+ readonly PATCH: "PATCH";
20
+ readonly DELETE: "DELETE";
21
+ readonly OPTIONS: "OPTIONS";
22
+ readonly HEAD: "HEAD";
23
+ };
24
+ type MethodName = typeof MethodName[keyof typeof MethodName];
25
+ /**
26
+ * `MethodName` plus the open-enum escape hatch for non-standard
27
+ * methods (`PROPFIND`, `MKCOL`, custom verbs). The `(string & {})`
28
+ * intersection is structurally identical to `string` but TypeScript
29
+ * doesn't collapse the union — so callers still get autocomplete
30
+ * for the canonical methods while remaining free to pass anything.
31
+ */
32
+ type MethodNameLike = MethodName | (string & {});
33
+ declare const HeaderName: {
34
+ readonly ACCEPT: "accept";
35
+ readonly ACCEPT_CHARSET: "accept-charset";
36
+ readonly ACCEPT_ENCODING: "accept-encoding";
37
+ readonly ACCEPT_LANGUAGE: "accept-language";
38
+ readonly ACCEPT_RANGES: "accept-ranges";
39
+ readonly ALLOW: "allow";
40
+ readonly CACHE_CONTROL: "cache-control";
41
+ readonly CONTENT_DISPOSITION: "content-disposition";
42
+ readonly CONTENT_ENCODING: "content-encoding";
43
+ readonly CONTENT_LENGTH: "content-length";
44
+ readonly CONTENT_RANGE: "content-range";
45
+ readonly CONTENT_TYPE: "content-type";
46
+ readonly CONNECTION: "connection";
47
+ readonly COOKIE: "cookie";
48
+ readonly ETag: "etag";
49
+ readonly HOST: "host";
50
+ readonly IF_MODIFIED_SINCE: "if-modified-since";
51
+ readonly IF_NONE_MATCH: "if-none-match";
52
+ readonly LAST_MODIFIED: "last-modified";
53
+ readonly LOCATION: "location";
54
+ readonly RANGE: "range";
55
+ readonly RATE_LIMIT_LIMIT: "ratelimit-limit";
56
+ readonly RATE_LIMIT_REMAINING: "ratelimit-remaining";
57
+ readonly RATE_LIMIT_RESET: "ratelimit-reset";
58
+ readonly RETRY_AFTER: "retry-after";
59
+ readonly SET_COOKIE: "set-cookie";
60
+ readonly TRANSFER_ENCODING: "transfer-encoding";
61
+ readonly X_ACCEL_BUFFERING: "x-accel-buffering";
62
+ readonly X_FORWARDED_HOST: "x-forwarded-host";
63
+ readonly X_FORWARDED_FOR: "x-forwarded-for";
64
+ readonly X_FORWARDED_PROTO: "x-forwarded-proto";
65
+ };
66
+ type HeaderName = typeof HeaderName[keyof typeof HeaderName];
67
+ //#endregion
68
+ //#region src/event/types.d.ts
69
+ type AppResponse = {
70
+ status: number;
71
+ headers: Headers;
72
+ };
73
+ type AppRequest = ServerRequest;
74
+ type NextFn = (error?: Error) => unknown | Promise<unknown>;
75
+ interface IAppEvent {
76
+ /**
77
+ * The srvx ServerRequest (extends Web Standard Request).
78
+ */
79
+ readonly request: AppRequest;
80
+ /**
81
+ * Route parameters extracted from the URL path pattern. Values
82
+ * are `string` (or `undefined` for an optional param that
83
+ * didn't match) — both the trie router (`extractTrieParams`)
84
+ * and the linear router (path-to-regexp output) only ever
85
+ * produce string values.
86
+ */
87
+ readonly params: Record<string, string | undefined>;
88
+ /**
89
+ * Current request path, adjusted relative to the mount point during router nesting.
90
+ */
91
+ readonly path: string;
92
+ /**
93
+ * HTTP method (GET, POST, PUT, etc.). Typed as the canonical
94
+ * `MethodName` set with an open-enum escape hatch — non-
95
+ * standard methods (`PROPFIND`, custom verbs) still type-check
96
+ * while standard ones autocomplete.
97
+ */
98
+ readonly method: MethodNameLike;
99
+ /**
100
+ * Accumulated mount path from nested routers.
101
+ */
102
+ readonly mountPath: string;
103
+ /**
104
+ * Web Standard Headers from the request.
105
+ */
106
+ readonly headers: Headers;
107
+ /**
108
+ * Lazily-parsed URL search parameters.
109
+ *
110
+ * For advanced query parsing (arrays, nesting), use `@routup/query`.
111
+ */
112
+ readonly searchParams: URLSearchParams;
113
+ /**
114
+ * Response accumulator — set status/headers before returning a plain value.
115
+ *
116
+ * If the handler returns a `Response` object directly, these values are
117
+ * ignored. They only apply when returning plain values (string, object, etc.)
118
+ * that go through `toResponse()`.
119
+ */
120
+ readonly response: AppResponse;
121
+ /**
122
+ * Per-request store for caching and plugin state.
123
+ *
124
+ * Use symbol keys (e.g., `Symbol.for('routup:body')`) to avoid collisions.
125
+ * Data is garbage collected with the event when the request completes.
126
+ */
127
+ readonly store: Record<string | symbol, unknown>;
128
+ /**
129
+ * Pre-resolved router options for the current dispatch context.
130
+ *
131
+ * Contains merged options from the router path stack with defaults applied.
132
+ */
133
+ readonly appOptions: Readonly<AppOptions>;
134
+ /**
135
+ * Abort signal tied to the request lifecycle.
136
+ *
137
+ * When a `timeout` router option is set, this signal aborts after the
138
+ * specified duration. Handlers performing long I/O (fetch, streams, DB queries)
139
+ * can pass this signal to those operations for cooperative cancellation.
140
+ */
141
+ readonly signal: AbortSignal;
142
+ /**
143
+ * Call the next handler in the pipeline (onion model).
144
+ *
145
+ * The result is cached — calling `next()` multiple times returns the same response.
146
+ * Returns the downstream `Response`, or `undefined` if no handler matched.
147
+ */
148
+ next(error?: Error): Promise<Response | undefined>;
149
+ /**
150
+ * Whether `next()` has been invoked on this event.
151
+ *
152
+ * Used by the dispatch pipeline to disambiguate an `undefined` return value:
153
+ * a handler that returns `undefined` after calling `next()` is forwarding the
154
+ * downstream result; one that returns `undefined` without calling `next()` is
155
+ * unresolved and will wait on `signal` (timeout-bounded).
156
+ */
157
+ readonly nextCalled: boolean;
158
+ /**
159
+ * The cached promise returned by the first `next()` call on this event,
160
+ * or `undefined` if `next()` has not been invoked.
161
+ */
162
+ readonly nextResult: Promise<Response | undefined> | undefined;
163
+ /**
164
+ * Returns a promise that resolves the first time `next()` is invoked on this event.
165
+ *
166
+ * If `next()` has already been called, the returned promise is already resolved.
167
+ * Used by the dispatch pipeline so a handler that returns `undefined` and later
168
+ * calls `next()` asynchronously (e.g. from a `setTimeout`) still propagates the
169
+ * downstream response instead of hanging until `signal` aborts.
170
+ */
171
+ whenNextCalled(): Promise<void>;
172
+ }
173
+ //#endregion
174
+ //#region src/event/module.d.ts
175
+ type AppEventCreateContext = {
176
+ request: AppRequest;
177
+ params: Record<string, string | undefined>;
178
+ path: string;
179
+ method: MethodNameLike;
180
+ mountPath: string;
181
+ headers: Headers;
182
+ searchParams: URLSearchParams;
183
+ response: AppResponse;
184
+ store: Record<string | symbol, unknown>;
185
+ signal: AbortSignal;
186
+ appOptions: Readonly<AppOptions>;
187
+ next: (event: IAppEvent, error?: Error) => Promise<Response | undefined>;
188
+ };
189
+ declare class AppEvent implements IAppEvent {
190
+ readonly request: AppRequest;
191
+ readonly params: Record<string, string | undefined>;
192
+ readonly path: string;
193
+ readonly method: MethodNameLike;
194
+ readonly mountPath: string;
195
+ readonly headers: Headers;
196
+ readonly searchParams: URLSearchParams;
197
+ readonly response: AppResponse;
198
+ readonly store: Record<string | symbol, unknown>;
199
+ readonly signal: AbortSignal;
200
+ readonly appOptions: Readonly<AppOptions>;
201
+ protected _context: AppEventCreateContext;
202
+ protected _nextCalled: boolean;
203
+ protected _nextResult: Promise<Response | undefined> | undefined;
204
+ protected _nextCalledDeferred: {
205
+ promise: Promise<void>;
206
+ resolve: () => void;
207
+ } | undefined;
208
+ constructor(context: AppEventCreateContext);
209
+ get nextCalled(): boolean;
210
+ get nextResult(): Promise<Response | undefined> | undefined;
211
+ whenNextCalled(): Promise<void>;
212
+ next(error?: Error): Promise<Response | undefined>;
213
+ }
214
+ //#endregion
215
+ //#region src/dispatcher/types.d.ts
216
+ interface IDispatcherEvent {
217
+ /**
218
+ * The srvx ServerRequest (extends Web Standard Request).
219
+ */
220
+ readonly request: AppRequest;
221
+ /**
222
+ * Route parameters extracted from the URL path pattern. Values
223
+ * are `string` (or `undefined` for an optional param that
224
+ * didn't match).
225
+ */
226
+ params: Record<string, string | undefined>;
227
+ /**
228
+ * Current request path, adjusted relative to the mount point during router nesting.
229
+ */
230
+ path: string;
231
+ /**
232
+ * HTTP method (GET, POST, PUT, etc.). See `IAppEvent.method`
233
+ * for the open-enum typing rationale.
234
+ */
235
+ readonly method: MethodNameLike;
236
+ /**
237
+ * Accumulated mount path from nested routers.
238
+ */
239
+ mountPath: string;
240
+ /**
241
+ * Response accumulator — set status/headers before returning a plain value.
242
+ */
243
+ readonly response: AppResponse;
244
+ /**
245
+ * Whether a response has been produced.
246
+ */
247
+ dispatched: boolean;
248
+ /**
249
+ * Error that occurred during dispatch, if any.
250
+ */
251
+ error?: AppError;
252
+ /**
253
+ * Options of the App currently dispatching this event. Set on
254
+ * entry to `App.dispatch` and restored on exit so nested apps
255
+ * temporarily override the parent's view.
256
+ */
257
+ appOptions: Readonly<AppOptions>;
258
+ /**
259
+ * `true` while at least one `App.dispatch` is on the call stack
260
+ * for this event. Used by `App.dispatch` to derive whether a
261
+ * given dispatch call is the root (the first one entered) or
262
+ * nested. Saved/restored across the call so nested dispatches
263
+ * leave the flag in the state their caller expects.
264
+ */
265
+ isDispatching: boolean;
266
+ /**
267
+ * Abort signal for cooperative cancellation.
268
+ *
269
+ * When a `timeout` router option is set, this signal aborts after the
270
+ * specified duration. Handlers can pass it to fetch(), streams, or other
271
+ * AbortSignal-aware APIs.
272
+ */
273
+ signal: AbortSignal;
274
+ /**
275
+ * Collected allowed methods for the current path (used for OPTIONS / 405 responses).
276
+ */
277
+ methodsAllowed: Set<string>;
278
+ /**
279
+ * Set the continuation function for this event.
280
+ *
281
+ * Replaces the current continuation. The provided function receives
282
+ * an optional error and may return any value — it will be converted
283
+ * to a `Response` via `toResponse()`.
284
+ *
285
+ * Passing `undefined` clears the continuation function.
286
+ */
287
+ setNext(fn?: NextFn): void;
288
+ /**
289
+ * Build a public AppEvent from the current dispatch state.
290
+ *
291
+ * Creates a lightweight snapshot with shared references (store, response, headers)
292
+ * and the current App's options. This is the event passed to handler functions.
293
+ *
294
+ * @param signal - Optional AbortSignal override. When provided, the built event
295
+ * uses this signal instead of the dispatcher event's own signal.
296
+ * Used by per-handler timeout to provide a handler-scoped signal.
297
+ */
298
+ build(signal?: AbortSignal): IAppEvent;
299
+ }
300
+ interface IDispatcher {
301
+ dispatch(event: IDispatcherEvent): Promise<Response | undefined>;
302
+ }
303
+ //#endregion
304
+ //#region src/dispatcher/module.d.ts
305
+ declare class DispatcherEvent implements IDispatcherEvent {
306
+ readonly request: AppRequest;
307
+ params: Record<string, string | undefined>;
308
+ path: string;
309
+ readonly method: MethodNameLike;
310
+ /**
311
+ * Collected allowed methods (for OPTIONS).
312
+ */
313
+ methodsAllowed: Set<string>;
314
+ mountPath: string;
315
+ error?: AppError;
316
+ /**
317
+ * Options of the App currently dispatching this event. Set on
318
+ * entry to `App.dispatch` and restored on exit (so nested apps
319
+ * temporarily override). Initialized to `{}` so consumers
320
+ * reading before any dispatch get a valid (empty) shape.
321
+ */
322
+ appOptions: Readonly<AppOptions>;
323
+ /**
324
+ * `true` while at least one `App.dispatch` is on the call stack
325
+ * for this event. `App.dispatch` reads this on entry to derive
326
+ * `isRoot` and writes it on entry/exit so nested calls see it
327
+ * already set.
328
+ */
329
+ isDispatching: boolean;
330
+ protected _dispatched: boolean;
331
+ protected _response?: AppResponse;
332
+ protected _store?: Record<string | symbol, unknown>;
333
+ /**
334
+ * Cached parsed URL (avoids double-parsing).
335
+ */
336
+ protected _url: InstanceType<typeof FastURL>;
337
+ /**
338
+ * Continuation function for middleware onion model.
339
+ */
340
+ protected _next?: (event: IAppEvent, error?: Error) => Promise<Response | undefined>;
341
+ protected _signal?: AbortSignal;
342
+ protected _signalCleanup?: () => void;
343
+ /**
344
+ * Whether _next has already been called (guard against double-invocation).
345
+ */
346
+ protected _nextCalled: boolean;
347
+ /**
348
+ * The cached result of the next handler.
349
+ */
350
+ protected _nextResult?: Promise<Response | undefined>;
351
+ constructor(request: AppRequest);
352
+ get response(): AppResponse;
353
+ get signal(): AbortSignal;
354
+ set signal(value: AbortSignal);
355
+ get dispatched(): boolean;
356
+ set dispatched(value: boolean);
357
+ protected next(event: IAppEvent, error?: Error): Promise<Response | undefined>;
358
+ setNext(fn?: NextFn): void;
359
+ build(signal?: AbortSignal): AppEvent;
360
+ protected get store(): Record<string | symbol, unknown>;
361
+ }
362
+ //#endregion
363
+ //#region src/hook/constants.d.ts
364
+ declare const HookName: {
365
+ /**
366
+ * Fired at the start of `App.dispatch`, before the pipeline walk.
367
+ * Once per router per request.
368
+ */
369
+ readonly START: "start";
370
+ /**
371
+ * Fired at the end of `App.dispatch`, after the pipeline walk
372
+ * (and OPTIONS auto-Allow synthesis) completes. Once per router per
373
+ * request.
374
+ */
375
+ readonly END: "end";
376
+ readonly ERROR: "error";
377
+ readonly CHILD_MATCH: "childMatch";
378
+ readonly CHILD_DISPATCH_BEFORE: "childDispatchBefore";
379
+ readonly CHILD_DISPATCH_AFTER: "childDispatchAfter";
380
+ };
381
+ type HookName = typeof HookName[keyof typeof HookName];
382
+ //#endregion
383
+ //#region src/hook/types.d.ts
384
+ type HookErrorListener = (event: IDispatcherEvent) => Promise<unknown> | unknown;
385
+ type HookDefaultListener = (event: IDispatcherEvent) => Promise<unknown> | unknown;
386
+ type HookListener = HookErrorListener | HookDefaultListener;
387
+ type HookUnsubscribeFn = () => void;
388
+ interface IHooks {
389
+ addListener(name: HookName, fn: HookListener, priority?: number): HookUnsubscribeFn;
390
+ removeListener(name: HookName): void;
391
+ removeListener(name: HookName, fn: HookListener): void;
392
+ removeListener(name: HookName, fn?: HookListener): void;
393
+ /**
394
+ * Returns true if at least one listener is registered for the given
395
+ * hook name. Used by the dispatch pipeline to skip the
396
+ * `await trigger(...)` microtask hop on the hot path when nothing
397
+ * is listening.
398
+ */
399
+ hasListeners(name: HookName): boolean;
400
+ /**
401
+ * Returns true if at least one listener is registered across *any*
402
+ * hook name. Cheap (single boolean read). Useful as a coarse
403
+ * fast-path guard before any per-name `hasListeners(...)` lookup
404
+ * for apps that never register a hook — the common workload.
405
+ */
406
+ hasAny(): boolean;
407
+ clone(): IHooks;
408
+ trigger(name: HookName, event: IDispatcherEvent): Promise<void>;
409
+ }
410
+ //#endregion
411
+ //#region src/handler/constants.d.ts
412
+ declare const HandlerType: {
413
+ readonly CORE: "core";
414
+ readonly ERROR: "error";
415
+ };
416
+ type HandlerType = typeof HandlerType[keyof typeof HandlerType];
417
+ declare const HandlerSymbol: unique symbol;
418
+ //#endregion
419
+ //#region src/path/type.d.ts
420
+ type PathMatcherOptions = PathToRegexpOptions & ParseOptions;
421
+ type PathMatcherExecResult = {
422
+ path: string;
423
+ params: Record<string, any>;
424
+ };
425
+ type Path = string;
426
+ interface IPathMatcher {
427
+ test(path: string): boolean;
428
+ exec(path: string): PathMatcherExecResult | undefined;
429
+ }
430
+ //#endregion
431
+ //#region src/path/matcher.d.ts
432
+ declare class PathMatcher implements IPathMatcher {
433
+ protected path: Path;
434
+ protected regexp: RegExp;
435
+ protected regexpKeys: Key[];
436
+ protected regexpOptions: PathMatcherOptions;
437
+ constructor(path: Path, options?: PathMatcherOptions);
438
+ test(path: string): boolean;
439
+ exec(path: string): PathMatcherExecResult | undefined;
440
+ }
441
+ //#endregion
442
+ //#region src/path/utils.d.ts
443
+ declare function isPath(input: unknown): input is Path;
444
+ //#endregion
445
+ //#region src/handler/types-base.d.ts
446
+ type HandlerBaseOptions = {
447
+ method?: Uppercase<MethodName> | Lowercase<MethodName>;
448
+ path?: Path;
449
+ /**
450
+ * Per-handler timeout in milliseconds.
451
+ *
452
+ * Overrides the router's `handlerTimeout` default. Whether this value
453
+ * can extend or only narrow the default is controlled by the router's
454
+ * `handlerTimeoutOverridable` option.
455
+ */
456
+ timeout?: number;
457
+ onError?: HookErrorListener;
458
+ onBefore?: HookDefaultListener;
459
+ onAfter?: HookDefaultListener;
460
+ };
461
+ //#endregion
462
+ //#region src/handler/error/types.d.ts
463
+ type ErrorHandler = (error: AppError, event: IAppEvent) => unknown | Promise<unknown>;
464
+ type ErrorHandlerOptions = HandlerBaseOptions & {
465
+ type: typeof HandlerType.ERROR;
466
+ fn: ErrorHandler;
467
+ };
468
+ //#endregion
469
+ //#region src/handler/error/define.d.ts
470
+ /**
471
+ * Create an error handler.
472
+ *
473
+ * Error handlers receive errors thrown by preceding handlers in the pipeline.
474
+ *
475
+ * @param input - Handler function `(error, event) => value` or options object `{ fn, path? }`
476
+ *
477
+ * @example
478
+ * ```typescript
479
+ * router.use(defineErrorHandler((error, event) => {
480
+ * return { message: error.message };
481
+ * }));
482
+ * ```
483
+ */
484
+ declare function defineErrorHandler(input: Omit<ErrorHandlerOptions, 'type'>): Handler;
485
+ declare function defineErrorHandler(input: ErrorHandler): Handler;
486
+ //#endregion
487
+ //#region src/handler/types.d.ts
488
+ type HandlerOptions = CoreHandlerOptions | ErrorHandlerOptions;
489
+ //#endregion
490
+ //#region src/handler/module.d.ts
491
+ declare class Handler implements IDispatcher {
492
+ protected config: HandlerOptions;
493
+ protected hooks: IHooks;
494
+ readonly method: MethodName | undefined;
495
+ constructor(handler: HandlerOptions);
496
+ get type(): "error" | "core";
497
+ get path(): string | undefined;
498
+ dispatch(event: IDispatcherEvent): Promise<Response | undefined>;
499
+ /**
500
+ * Resolve a handler's return value into the final value handed to `toResponse`.
501
+ *
502
+ * Contract:
503
+ * - non-undefined value → return as-is (becomes the response)
504
+ * - `undefined` + `event.next()` was called → forward downstream result
505
+ * - `undefined` + `event.next()` not yet called → wait until either `next()` is
506
+ * invoked (e.g. from an async callback) or `signal` aborts. A global or
507
+ * per-handler timeout aborts `signal` and surfaces as 408. With no timeout
508
+ * configured and no eventual `next()` call, the request hangs by design.
509
+ */
510
+ protected resolveHandlerResult(invocation: unknown | Promise<unknown>, handlerEvent: IAppEvent): Promise<unknown>;
511
+ protected executeWithTimeout(fn: () => unknown | Promise<unknown>, effectiveTimeout: number | undefined, controller?: AbortController): Promise<unknown>;
512
+ protected resolveTimeout(appOptions: AppOptions): number | undefined;
513
+ protected mountHooks(): void;
514
+ }
515
+ //#endregion
516
+ //#region src/handler/core/types.d.ts
517
+ type CoreHandler = (event: IAppEvent) => unknown | Promise<unknown>;
518
+ type CoreHandlerOptions = HandlerBaseOptions & {
519
+ type: typeof HandlerType.CORE;
520
+ fn: CoreHandler;
521
+ };
522
+ //#endregion
523
+ //#region src/handler/core/define.d.ts
524
+ /**
525
+ * Create a request handler.
526
+ *
527
+ * @param input - Handler function `(event) => value` or options object `{ fn, path?, method? }`
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * // Shorthand — function only
532
+ * router.get('/', defineCoreHandler((event) => 'Hello'));
533
+ *
534
+ * // Verbose — with path and method
535
+ * router.use(defineCoreHandler({
536
+ * path: '/users/:id',
537
+ * method: 'GET',
538
+ * fn: (event) => ({ id: event.params.id }),
539
+ * }));
540
+ * ```
541
+ */
542
+ declare function defineCoreHandler(input: Omit<CoreHandlerOptions, 'type'>): Handler;
543
+ declare function defineCoreHandler(input: CoreHandler): Handler;
544
+ //#endregion
545
+ //#region src/handler/adapters/node/types.d.ts
546
+ type NodeHandler = (req: IncomingMessage, res: ServerResponse) => unknown | Promise<unknown>;
547
+ type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => unknown | Promise<unknown>;
548
+ //#endregion
549
+ //#region src/handler/adapters/node/define.d.ts
550
+ /**
551
+ * Wraps a Node.js `(req, res)` handler for use in the routup pipeline.
552
+ *
553
+ * @example
554
+ * ```typescript
555
+ * import { fromNodeHandler } from 'routup/node';
556
+ *
557
+ * router.use(fromNodeHandler((req, res) => {
558
+ * res.end('Hello');
559
+ * }));
560
+ * ```
561
+ */
562
+ declare function fromNodeHandler(handler: NodeHandler): Handler;
563
+ /**
564
+ * Wraps a Node.js `(req, res, next)` middleware for use in the routup pipeline.
565
+ *
566
+ * @example
567
+ * ```typescript
568
+ * import cors from 'cors';
569
+ * import { fromNodeMiddleware } from 'routup/node';
570
+ *
571
+ * router.use(fromNodeMiddleware(cors()));
572
+ * ```
573
+ */
574
+ declare function fromNodeMiddleware(handler: NodeMiddleware): Handler;
575
+ //#endregion
576
+ //#region src/handler/adapters/web/types.d.ts
577
+ /**
578
+ * A plain function that follows the Web Fetch API signature.
579
+ * Compatible with any framework that exposes a fetch-style entry point.
580
+ */
581
+ interface WebHandler {
582
+ (request: Request): Response | Promise<Response>;
583
+ }
584
+ /**
585
+ * An object with a `fetch` method (e.g. another router, Hono app, etc.).
586
+ */
587
+ interface WebHandlerProvider {
588
+ fetch(request: Request): Response | Promise<Response>;
589
+ }
590
+ //#endregion
591
+ //#region src/handler/adapters/web/define.d.ts
592
+ /**
593
+ * Create a handler from a Web Fetch API-compatible function or object.
594
+ *
595
+ * Wraps an external app (e.g. Hono, another App) so it can be mounted
596
+ * via `router.use()`. The original request is passed through as-is.
597
+ *
598
+ * @param input - Fetch function `(request) => Response` or object with a `fetch` method
599
+ *
600
+ * @experimental
601
+ *
602
+ * @example
603
+ * ```ts
604
+ * // Mount an object with a fetch method
605
+ * router.use('/api', fromWebHandler(honoApp));
606
+ *
607
+ * // Mount a plain fetch function
608
+ * router.use('/proxy', fromWebHandler((req) => fetch(req)));
609
+ * ```
610
+ */
611
+ declare function fromWebHandler(input: WebHandler): Handler;
612
+ declare function fromWebHandler(input: WebHandlerProvider): Handler;
613
+ //#endregion
614
+ //#region src/handler/adapters/web/is.d.ts
615
+ declare function isWebHandlerProvider(input: unknown): input is WebHandlerProvider;
616
+ declare function isWebHandler(input: unknown): input is WebHandler;
617
+ //#endregion
618
+ //#region src/handler/is.d.ts
619
+ declare function isHandlerOptions(input: unknown): input is HandlerOptions;
620
+ declare function isHandler(input: unknown): input is Handler;
621
+ //#endregion
622
+ //#region src/handler/utils.d.ts
623
+ /**
624
+ * Match a request method against a handler's bound method.
625
+ *
626
+ * - When the handler has no method bound, matches every request method.
627
+ * - Otherwise matches when the request method is the same.
628
+ * - HEAD requests additionally match GET handlers.
629
+ */
630
+ declare function matchHandlerMethod(handlerMethod: MethodName | undefined, requestMethod: MethodName): boolean;
631
+ //#endregion
632
+ //#region src/plugin/error/constants.d.ts
633
+ declare const PluginErrorCode: {
634
+ readonly PLUGIN: "PLUGIN";
635
+ readonly NOT_INSTALLED: "PLUGIN_NOT_INSTALLED";
636
+ readonly ALREADY_INSTALLED: "PLUGIN_ALREADY_INSTALLED";
637
+ readonly INSTALL: "PLUGIN_INSTALL";
638
+ };
639
+ type PluginErrorCode = typeof PluginErrorCode[keyof typeof PluginErrorCode];
640
+ //#endregion
641
+ //#region src/plugin/error/module.d.ts
642
+ declare class PluginError extends AppError {
643
+ constructor(input?: HTTPErrorInput);
644
+ }
645
+ //#endregion
646
+ //#region src/plugin/error/is.d.ts
647
+ declare function isPluginError(input: unknown): input is PluginError;
648
+ //#endregion
649
+ //#region src/plugin/error/sub/already-installed.d.ts
650
+ declare class PluginAlreadyInstalledError extends PluginError {
651
+ readonly pluginName: string;
652
+ constructor(pluginName: string);
653
+ }
654
+ //#endregion
655
+ //#region src/plugin/error/sub/install.d.ts
656
+ declare class PluginInstallError extends PluginError {
657
+ readonly pluginName: string;
658
+ constructor(pluginName: string, cause?: Error);
659
+ }
660
+ //#endregion
661
+ //#region src/plugin/error/sub/not-installed.d.ts
662
+ declare class PluginNotInstalledError extends PluginError {
663
+ readonly pluginName: string;
664
+ readonly helperName: string;
665
+ constructor(pluginName: string, helperName: string);
666
+ }
667
+ //#endregion
668
+ //#region src/plugin/types.d.ts
669
+ type PluginInstallFn = (router: IApp) => any;
670
+ type Plugin = {
671
+ /**
672
+ * The name of the plugin.
673
+ */
674
+ name: string;
675
+ /**
676
+ * The version of the plugin (semver).
677
+ */
678
+ version?: string;
679
+ /**
680
+ * The installation function called on registration.
681
+ */
682
+ install: PluginInstallFn;
683
+ };
684
+ type PluginInstallContext = {
685
+ /**
686
+ * By specifying a path, the plugin will be installed as a child router.
687
+ */
688
+ path?: Path;
689
+ };
690
+ //#endregion
691
+ //#region src/plugin/is.d.ts
692
+ declare function isPlugin(input: unknown): input is Plugin;
693
+ //#endregion
694
+ //#region src/utils/etag/types.d.ts
695
+ type EtagOptions = {
696
+ /**
697
+ * Create a weak ETag?
698
+ * Output is prefixed with: /W
699
+ */
700
+ weak?: boolean;
701
+ /**
702
+ * Threshold of bytes from which an etag is generated.
703
+ *
704
+ * default: undefined
705
+ */
706
+ threshold?: number;
707
+ };
708
+ type EtagFn = (body: string, size?: number) => Promise<string | undefined>;
709
+ type EtagInput = boolean | null | EtagOptions | EtagFn;
710
+ //#endregion
711
+ //#region src/utils/trust-proxy/type.d.ts
712
+ type TrustProxyFn = (address: string, hop: number) => boolean;
713
+ type TrustProxyInput = boolean | number | string | string[] | TrustProxyFn;
714
+ //#endregion
715
+ //#region src/app/constants.d.ts
716
+ declare const AppPipelineStep: {
717
+ readonly START: 0;
718
+ readonly LOOKUP: 1;
719
+ readonly CHILD_BEFORE: 2;
720
+ readonly CHILD_DISPATCH: 3;
721
+ readonly CHILD_AFTER: 4;
722
+ readonly FINISH: 5;
723
+ };
724
+ type AppPipelineStep = typeof AppPipelineStep[keyof typeof AppPipelineStep];
725
+ declare const RouteEntryType: {
726
+ readonly APP: "app";
727
+ readonly HANDLER: "handler";
728
+ };
729
+ type RouteEntryType = typeof RouteEntryType[keyof typeof RouteEntryType];
730
+ //#endregion
731
+ //#region src/cache/types.d.ts
732
+ /**
733
+ * Pluggable cache strategy used by `IRouter` implementations to
734
+ * memoize `lookup(path)` results by request path. The default
735
+ * implementation (`LruCache`) is a `quick-lru`-backed bounded LRU;
736
+ * users can supply their own `ICache` (e.g. wrapping `lru-cache` for
737
+ * TTL/size-based eviction) via `BaseRouterOptions.cache`, or pass
738
+ * `null` to disable caching.
739
+ *
740
+ * The cache is opaque about value type so the same `ICache`
741
+ * implementation can be reused for non-router caching needs.
742
+ */
743
+ interface ICache<V> {
744
+ /**
745
+ * Return the cached value for `key`, or `undefined` when the key
746
+ * is absent (or has been evicted). Implementations should treat
747
+ * `undefined` as "no entry" — callers cannot store `undefined`.
748
+ * Other falsy values (`null`, `0`, `''`, `false`) are storable
749
+ * and must be returned unchanged on hit.
750
+ */
751
+ get(key: string): V | undefined;
752
+ /**
753
+ * Store `value` under `key`. Bounded implementations (LRU, TTL,
754
+ * size-based) decide eviction at this point.
755
+ */
756
+ set(key: string, value: V): void;
757
+ /**
758
+ * Remove a single entry. No-op when `key` is absent.
759
+ */
760
+ delete(key: string): void;
761
+ /**
762
+ * Drop every entry. Routers call this from inside `add()` so a
763
+ * newly registered route can never be hidden by stale matches
764
+ * cached against an earlier route set.
765
+ */
766
+ clear(): void;
767
+ /**
768
+ * Return a fresh, **empty** cache of the same shape — same class
769
+ * for leaf implementations. Used by `IRouter.clone()` so the
770
+ * clone preserves the configured cache family (size, eviction
771
+ * policy, …) without inheriting the parent's cached values.
772
+ * Mirrors `IRouter.clone()`.
773
+ */
774
+ clone(): ICache<V>;
775
+ }
776
+ //#endregion
777
+ //#region src/cache/lru.d.ts
778
+ type LruCacheOptions = {
779
+ /**
780
+ * Maximum number of entries before the least-recently-used entry
781
+ * is evicted on `set`. Default: `1024`.
782
+ */
783
+ maxSize?: number;
784
+ };
785
+ /**
786
+ * Default `ICache` implementation — a bounded LRU backed by
787
+ * [`quick-lru`](https://github.com/sindresorhus/quick-lru). Picked for
788
+ * its small footprint (~1kB), ESM-only build (matches routup), and
789
+ * stable API.
790
+ *
791
+ * For TTL, size-based eviction, or dispose hooks, write your own
792
+ * `ICache` (e.g. wrapping `lru-cache`) and pass it via the router's
793
+ * `BaseRouterOptions.cache` slot.
794
+ */
795
+ declare class LruCache<V> implements ICache<V> {
796
+ protected options: LruCacheOptions;
797
+ protected inner: QuickLRU<string, V>;
798
+ constructor(options?: LruCacheOptions);
799
+ get(key: string): V | undefined;
800
+ set(key: string, value: V): void;
801
+ delete(key: string): void;
802
+ clear(): void;
803
+ clone(): ICache<V>;
804
+ }
805
+ //#endregion
806
+ //#region src/types.d.ts
807
+ /**
808
+ * Constraint on `IRouter<T>`'s data slot — routers store object-shaped
809
+ * per-route data (handlers, child apps, custom records). Primitives
810
+ * (`string`, `number`) aren't supported as route data; if you need to
811
+ * carry a primitive, wrap it in an object.
812
+ */
813
+ type ObjectLiteral = Record<string, any>;
814
+ /**
815
+ * A registered route — what `IRouter.add` consumes. Only `path` and
816
+ * `method` are routing-relevant; `data` is opaque to the router and
817
+ * returned as-is on match. Apps store their own discrimination
818
+ * (e.g. handler-vs-nested-app) inside `data`.
819
+ *
820
+ * **Match-semantics convention:**
821
+ * - `method !== undefined` → router treats the entry as method-bound
822
+ * and matches the path **exactly**.
823
+ * - `method === undefined` → entry is method-agnostic (middleware /
824
+ * nested app) and matches by **prefix**.
825
+ *
826
+ * Custom `IRouter` implementations should honor this convention so
827
+ * apps can swap routers transparently.
828
+ */
829
+ type Route<T extends ObjectLiteral = ObjectLiteral> = {
830
+ /**
831
+ * Mount path.
832
+ * - `undefined` means "no path" (route matches every request).
833
+ * - `'/'` behaves like "no path" for method-agnostic prefix routes
834
+ * (middleware / mount-less nested apps).
835
+ * - Method-bound `'/'` is treated as an exact root match
836
+ * (`app.get('/', …)` matches only the root).
837
+ */
838
+ path?: Path;
839
+ /**
840
+ * Bound HTTP method. When set, the router treats this route as an
841
+ * exact match; when undefined, the route matches by prefix.
842
+ */
843
+ method?: MethodName;
844
+ /**
845
+ * Opaque to the router. Returned via `RouteMatch.route.data` on
846
+ * match; consumers (typically `App`) decide what's inside.
847
+ */
848
+ data: T;
849
+ };
850
+ /**
851
+ * A single matched route returned by `IRouter.lookup`. The dispatch
852
+ * loop consumes these instead of walking the raw routes — `params`
853
+ * are pre-extracted at lookup time so we don't re-run the matcher
854
+ * later, and `path` (when set) tells the loop how much of
855
+ * `event.path` to strip when recursing into a child app.
856
+ */
857
+ type RouteMatch<T extends ObjectLiteral = ObjectLiteral> = {
858
+ route: Route<T>;
859
+ /**
860
+ * Registration index in the router. Used by the dispatch loop's
861
+ * `setNext` continuation ("resume from index + 1") and by
862
+ * `App.clone()` to re-register routes in their original order.
863
+ */
864
+ index: number;
865
+ /**
866
+ * Path params extracted from the route's matcher. Values are
867
+ * `string` (or `undefined` for an optional param that didn't
868
+ * match). Empty object when the route has no path or no params.
869
+ */
870
+ params: Record<string, string | undefined>;
871
+ /**
872
+ * For routes with a matcher: the path substring the matcher
873
+ * consumed. Used by `executePipelineStepChildDispatch` to strip
874
+ * the matched prefix off `event.path` before dispatching into a
875
+ * child app. Undefined for routes without a matcher.
876
+ */
877
+ path?: string;
878
+ };
879
+ //#endregion
880
+ //#region src/router/types.d.ts
881
+ /**
882
+ * Options shared by every built-in router. Custom `IRouter`
883
+ * implementations are encouraged to extend this so users can swap
884
+ * routers without rewiring caching.
885
+ *
886
+ * - `cache` (omitted): no caching — every `lookup()` runs the
887
+ * router's full match logic. This is the default.
888
+ * - `cache: <ICache>`: enable lookup memoization. Pass `LruCache`
889
+ * for the built-in bounded LRU, or your own `ICache` (e.g.
890
+ * wrapping `lru-cache` for TTL or size-based eviction).
891
+ *
892
+ * The router is responsible for invalidating its own cache whenever
893
+ * `add()` is called — registering a new route can change the match
894
+ * set for any cached path.
895
+ */
896
+ type BaseRouterOptions<T extends ObjectLiteral = ObjectLiteral> = {
897
+ cache?: ICache<readonly RouteMatch<T>[]>;
898
+ };
899
+ /**
900
+ * Pluggable strategy for storing routes and answering "which entries
901
+ * match this path?". The default `LinearRouter` walks the stored
902
+ * entries linearly. Alternative implementations (radix tree,
903
+ * aggregated regex, …) can swap in via `AppContext.router` to
904
+ * skip the walk entirely on apps with many routes.
905
+ *
906
+ * The router operates on `Route<T>` where `T` is opaque data; the
907
+ * router never inspects `entry.data`. Only `entry.path` and
908
+ * `entry.method` are routing-relevant.
909
+ *
910
+ * **Match-semantics convention** (custom implementations must honor):
911
+ * - `entry.method !== undefined` → match the path **exactly** (the
912
+ * entry is method-bound, e.g. a verb-shortcut handler).
913
+ * - `entry.method === undefined` → match by **prefix** (middleware,
914
+ * nested apps).
915
+ *
916
+ * Method matching against the request method is kept at the dispatch-
917
+ * loop call site, not here, because method semantics differ between
918
+ * handler and nested-app entries (only handler entries are
919
+ * method-bound).
920
+ */
921
+ interface IRouter<T extends ObjectLiteral = ObjectLiteral> {
922
+ /**
923
+ * Register a route. Entries are stored in registration order —
924
+ * the order they were passed to `App.use` / `.get` / `.post` /
925
+ * etc. — and lookup results preserve that order.
926
+ */
927
+ add(route: Route<T>): void;
928
+ /**
929
+ * Return every entry that matches the given path, in registration
930
+ * order. The dispatch loop iterates this list; nested `setNext`
931
+ * re-entries resume from a later index in the same list.
932
+ *
933
+ * `method`, when provided, is the request HTTP method. Routers
934
+ * MAY use it to filter at lookup time (e.g. method-bucketed
935
+ * tries) — but the App's dispatch loop still runs its own
936
+ * method check on every returned match, so a router that
937
+ * ignores `method` and emits more candidates than necessary
938
+ * stays correct, just not optimally fast.
939
+ *
940
+ * When the request method is `OPTIONS` (auto-Allow surface) or
941
+ * `HEAD` (falls through to GET), method-aware routers should
942
+ * widen their emission to cover those cases — see the OPTIONS /
943
+ * HEAD handling notes on `TrieRouter` for the canonical
944
+ * implementation.
945
+ */
946
+ lookup(path: string, method?: MethodNameLike): readonly RouteMatch<T>[];
947
+ /**
948
+ * Return a fresh, **empty** router of the same shape — same class
949
+ * for leaf implementations; composable wrappers should recursively
950
+ * clone their inner router. Used by `App.install()` and
951
+ * `App.clone()` so plugin sub-apps and cloned apps preserve the
952
+ * active router family instead of silently downgrading to
953
+ * `LinearRouter`.
954
+ */
955
+ clone(): IRouter<T>;
956
+ }
957
+ //#endregion
958
+ //#region src/app/types.d.ts
959
+ type AppOptions = {
960
+ /**
961
+ * Global request timeout in milliseconds.
962
+ *
963
+ * Applies to the entire dispatch pipeline in `fetch()`. When exceeded,
964
+ * the request is aborted and a 408 response is returned. The AbortSignal
965
+ * on the event is also aborted for cooperative cancellation.
966
+ */
967
+ timeout?: number;
968
+ /**
969
+ * Default per-handler timeout in milliseconds.
970
+ *
971
+ * Applies individually to each handler's `fn()` execution. Handlers can
972
+ * override this value via their own `timeout` option — see
973
+ * `handlerTimeoutOverridable` to control whether overrides can extend
974
+ * or only narrow this default.
975
+ */
976
+ handlerTimeout?: number;
977
+ /**
978
+ * Whether handlers can extend the `handlerTimeout` default.
979
+ *
980
+ * When `false` (default), a handler's `timeout` is clamped to
981
+ * `Math.min(handlerTimeout, handler.timeout)`. When `true`, the
982
+ * handler's `timeout` fully replaces the router default.
983
+ */
984
+ handlerTimeoutOverridable?: boolean;
985
+ /**
986
+ * Number of trailing labels in the request hostname that make up
987
+ * the registrable domain (e.g. `example.com` → 2). Subdomain
988
+ * helpers strip this many labels from the right before returning
989
+ * the subdomain portion.
990
+ */
991
+ subdomainOffset?: number;
992
+ /**
993
+ * Maximum number of proxy IPs to walk when resolving the client
994
+ * IP from `X-Forwarded-For`. Caps how far back the chain is
995
+ * trusted, regardless of `trustProxy`.
996
+ */
997
+ proxyIpMax?: number;
998
+ /**
999
+ * ETag generator, or `null` to disable ETag/304 entirely.
1000
+ *
1001
+ * - `undefined` (the default): consumers fall back to a
1002
+ * framework-provided `EtagFn`.
1003
+ * - `null`: explicit opt-out — the response pipeline branches
1004
+ * synchronously and skips the `await applyEtag(...)` microtask hop.
1005
+ * - `EtagFn`: the user's own generator.
1006
+ */
1007
+ etag?: EtagFn | null;
1008
+ /**
1009
+ * Predicate that decides whether a given upstream address is a
1010
+ * trusted proxy when resolving the client IP / protocol /
1011
+ * hostname from forwarding headers.
1012
+ */
1013
+ trustProxy?: TrustProxyFn;
1014
+ };
1015
+ /**
1016
+ * User-facing input variant of `AppOptions`.
1017
+ *
1018
+ * Accepts looser shapes for `etag` and `trustProxy` (string,
1019
+ * boolean, list-of-CIDRs, …) which `normalizeAppOptions` lowers to
1020
+ * the resolved `EtagFn | null` / `TrustProxyFn` shape stored on the
1021
+ * App.
1022
+ */
1023
+ type AppOptionsInput = Omit<AppOptions, 'etag' | 'trustProxy'> & {
1024
+ /**
1025
+ * ETag input — accepts an `EtagFn`, `false`/`null` to disable,
1026
+ * or other shapes accepted by `buildEtagFn`. Normalized to
1027
+ * `EtagFn | null` on the App.
1028
+ */
1029
+ etag?: EtagInput;
1030
+ /**
1031
+ * Trust-proxy input — accepts a predicate, a list of trusted
1032
+ * CIDRs, `'loopback'`, etc., as accepted by `buildTrustProxyFn`.
1033
+ * Normalized to `TrustProxyFn` on the App.
1034
+ */
1035
+ trustProxy?: TrustProxyInput;
1036
+ };
1037
+ /**
1038
+ * Constructor input for `App`.
1039
+ *
1040
+ * Splits true runtime options (which propagate to mounted children
1041
+ * via mount-time inheritance) from App-local identity (`name`,
1042
+ * `path`) and constructor injectables (`hooks`, `plugins`,
1043
+ * `router`). Keeping these separate prevents identity from leaking
1044
+ * across the mount boundary — e.g. a parent's `path: '/api'` would
1045
+ * otherwise propagate into a child whose own `path` is unset and
1046
+ * silently double-prefix on registration.
1047
+ */
1048
+ type AppContext = {
1049
+ /**
1050
+ * Optional label for the App instance.
1051
+ */
1052
+ name?: string;
1053
+ /**
1054
+ * Registration-time path prefix for entries registered on this
1055
+ * App.
1056
+ *
1057
+ * When set, every entry registered via `use`, `get`, `post`, …
1058
+ * has this prefix prepended to its mount path.
1059
+ * `new App({ path: '/api' })` followed by `app.get('/users', h)`
1060
+ * is equivalent to `app.get('/api/users', h)` on an App without
1061
+ * a `path`.
1062
+ *
1063
+ * Path matching itself still happens inside the active `IRouter`
1064
+ * — this only affects how entries are registered, not how
1065
+ * lookup is performed. Local to this App; not propagated to
1066
+ * mounted children.
1067
+ */
1068
+ path?: Path;
1069
+ /**
1070
+ * Runtime options that propagate to mounted children via
1071
+ * mount-time inheritance.
1072
+ */
1073
+ options?: AppOptionsInput;
1074
+ /**
1075
+ * Lifecycle hook registry. Defaults to a fresh `Hooks` instance.
1076
+ * Pass an existing registry to share listeners across Apps (used
1077
+ * by `clone()` to seed the copy with the original's listeners).
1078
+ */
1079
+ hooks?: IHooks;
1080
+ /**
1081
+ * Map of installed plugin name → version. Defaults to an empty
1082
+ * map. Used by `clone()` to carry the installed-plugin registry
1083
+ * over so duplicate installs are still rejected on the copy.
1084
+ */
1085
+ plugins?: Map<string, string | undefined>;
1086
+ /**
1087
+ * Pluggable router (route table). Defaults to `LinearRouter` —
1088
+ * walks registered entries linearly per request. Swap in an
1089
+ * alternative (e.g. `TrieRouter`) on apps with many routes.
1090
+ */
1091
+ router?: IRouter<RouteEntry>;
1092
+ };
1093
+ /**
1094
+ * App-internal data that gets stored as `Route.data` in the active
1095
+ * router. Tagged union — the discriminator `type` (HANDLER / APP)
1096
+ * tells the dispatch loop which branch of the union to read.
1097
+ *
1098
+ * `path` and `method` are not part of this type — they live on
1099
+ * `Route<RouteEntry>` itself (the router's routing-relevant fields),
1100
+ * and App resolves the handler's intrinsic method into `Route.method`
1101
+ * at registration time.
1102
+ */
1103
+ type AppRouteEntry = {
1104
+ /**
1105
+ * Discriminator marking this entry as a mounted child App (vs a
1106
+ * leaf handler). The dispatch loop branches on this to recurse
1107
+ * via `IApp.dispatch` rather than invoking a handler `fn`.
1108
+ */
1109
+ type: typeof RouteEntryType.APP;
1110
+ /**
1111
+ * The mounted child App. Path/method live on the wrapping
1112
+ * `Route<RouteEntry>`, not here.
1113
+ */
1114
+ data: IApp;
1115
+ };
1116
+ /**
1117
+ * Leaf entry in the route table: a handler invoked directly by the
1118
+ * dispatch loop.
1119
+ */
1120
+ type HandlerRouteEntry = {
1121
+ /**
1122
+ * Discriminator marking this entry as a leaf handler (vs a
1123
+ * mounted child App).
1124
+ */
1125
+ type: typeof RouteEntryType.HANDLER;
1126
+ /**
1127
+ * The handler invoked when this entry matches. Path/method live
1128
+ * on the wrapping `Route<RouteEntry>`, not here.
1129
+ */
1130
+ data: Handler;
1131
+ };
1132
+ /**
1133
+ * Tagged union of route-table entries. The active `IRouter` stores
1134
+ * one of these as `Route.data` per registered entry; the dispatch
1135
+ * loop reads `type` to choose the right branch.
1136
+ */
1137
+ type RouteEntry = AppRouteEntry | HandlerRouteEntry;
1138
+ type AppPipelineContext = {
1139
+ /**
1140
+ * Current pipeline phase — drives the state machine in
1141
+ * `App.executePipelineStep`. Mutated as the loop advances.
1142
+ */
1143
+ step: AppPipelineStep;
1144
+ /**
1145
+ * The dispatcher event being processed. Carries request, path,
1146
+ * params, and the response accumulator across pipeline steps.
1147
+ */
1148
+ event: IDispatcherEvent;
1149
+ /**
1150
+ * `true` when this dispatch is the outermost App on the call
1151
+ * stack (the root). Captured in `App.dispatch` from the event's
1152
+ * pre-overwrite `appOptions` and used to gate root-only
1153
+ * behaviour like OPTIONS auto-Allow.
1154
+ */
1155
+ isRoot: boolean;
1156
+ /**
1157
+ * Position within `matches`. Replaces the old "raw stack index" —
1158
+ * the dispatch loop now iterates the resolved-matches list rather
1159
+ * than the unfiltered registration order.
1160
+ */
1161
+ matchIndex: number;
1162
+ /**
1163
+ * Resolved matches for the current `event.path`, populated on the
1164
+ * first LOOKUP entry and threaded through `setNext` recursion so
1165
+ * we don't re-run `IRouter.lookup` per cycle. Invalidated
1166
+ * automatically when `event.path` changes (a hook mutating the
1167
+ * path between entries triggers a refresh).
1168
+ */
1169
+ matches?: readonly RouteMatch<RouteEntry>[];
1170
+ /**
1171
+ * The `event.path` that was used to compute `matches`. Stored so
1172
+ * we can detect a mid-walk path mutation and refresh the cache.
1173
+ */
1174
+ matchesPath?: string;
1175
+ /**
1176
+ * The Response produced by the pipeline. Set by handlers (via
1177
+ * `toResponse`) or by terminal pipeline steps; returned from
1178
+ * `App.dispatch` once `FINISH` is reached.
1179
+ */
1180
+ response?: Response;
1181
+ };
1182
+ interface IApp extends IDispatcher {
1183
+ /**
1184
+ * Optional label for the router instance.
1185
+ */
1186
+ readonly name?: string;
1187
+ /**
1188
+ * Public entry point — processes a request through the pipeline
1189
+ * and returns a Response (with 404/500 fallbacks).
1190
+ */
1191
+ fetch(request: AppRequest): Promise<Response>;
1192
+ /**
1193
+ * Return a new router that mirrors this one but owns independent
1194
+ * mountable state — fresh stack of shallow-copied entries (handlers and
1195
+ * child routers shared by reference), fresh `Hooks` seeded with the
1196
+ * current listeners, shallow copy of options, and a fresh plugins map.
1197
+ *
1198
+ * Intended for mounting the same logical router under multiple paths
1199
+ * without sharing mutable state across mount points.
1200
+ */
1201
+ clone(): IApp;
1202
+ /**
1203
+ * Swap the active `IRouter`. Every previously-registered route
1204
+ * is replayed onto the new router so lookups stay correct. Any
1205
+ * cache the previous router carried is dropped along with it.
1206
+ *
1207
+ * Useful when the right router family is only known after
1208
+ * routes are registered (a SmartRouter-style decision), or for
1209
+ * comparing implementations mid-flight without rebuilding the
1210
+ * App.
1211
+ */
1212
+ setRouter(router: IRouter<RouteEntry>): void;
1213
+ /**
1214
+ * Check if a plugin with the given name is installed on this router.
1215
+ */
1216
+ hasPlugin(name: string): boolean;
1217
+ /**
1218
+ * Get the version of an installed plugin by name on this router,
1219
+ * or `undefined` if the plugin is not installed here.
1220
+ */
1221
+ getPluginVersion(name: string): string | undefined;
1222
+ /**
1223
+ * Register a handler, router, or plugin.
1224
+ * When a path is provided, the item is mounted at that path.
1225
+ */
1226
+ use(router: IApp): this;
1227
+ use(handler: Handler | HandlerOptions): this;
1228
+ use(plugin: Plugin): this;
1229
+ use(path: Path, router: IApp): this;
1230
+ use(path: Path, handler: Handler | HandlerOptions): this;
1231
+ use(path: Path, plugin: Plugin): this;
1232
+ /** Register GET handler(s). */
1233
+ get(...handlers: (Handler | HandlerOptions)[]): this;
1234
+ get(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1235
+ /** Register POST handler(s). */
1236
+ post(...handlers: (Handler | HandlerOptions)[]): this;
1237
+ post(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1238
+ /** Register PUT handler(s). */
1239
+ put(...handlers: (Handler | HandlerOptions)[]): this;
1240
+ put(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1241
+ /** Register PATCH handler(s). */
1242
+ patch(...handlers: (Handler | HandlerOptions)[]): this;
1243
+ patch(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1244
+ /** Register DELETE handler(s). */
1245
+ delete(...handlers: (Handler | HandlerOptions)[]): this;
1246
+ delete(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1247
+ /** Register HEAD handler(s). */
1248
+ head(...handlers: (Handler | HandlerOptions)[]): this;
1249
+ head(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1250
+ /** Register OPTIONS handler(s). */
1251
+ options(...handlers: (Handler | HandlerOptions)[]): this;
1252
+ options(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1253
+ /**
1254
+ * Add a hook listener.
1255
+ */
1256
+ on(name: typeof HookName.START | typeof HookName.END | typeof HookName.CHILD_DISPATCH_BEFORE | typeof HookName.CHILD_DISPATCH_AFTER, fn: HookDefaultListener): HookUnsubscribeFn;
1257
+ on(name: typeof HookName.CHILD_MATCH, fn: HookDefaultListener): HookUnsubscribeFn;
1258
+ on(name: typeof HookName.ERROR, fn: HookErrorListener): HookUnsubscribeFn;
1259
+ /**
1260
+ * Remove a specific or all hook listeners for the given hook name.
1261
+ */
1262
+ off(name: HookName): this;
1263
+ off(name: HookName, fn: HookListener): this;
1264
+ }
1265
+ //#endregion
1266
+ //#region src/error/create.d.ts
1267
+ /**
1268
+ * Create an internal error object by
1269
+ * - an existing AppError (returned as-is)
1270
+ * - an HTTPError (wrapped into a AppError preserving status)
1271
+ * - an Error (wrapped preserving message and cause)
1272
+ * - an options object (status, message, etc.)
1273
+ * - a message string
1274
+ *
1275
+ * @param input
1276
+ */
1277
+ declare function createError(input: HTTPErrorInput | unknown): AppError;
1278
+ //#endregion
1279
+ //#region src/error/is.d.ts
1280
+ declare function isError(input: unknown): input is AppError;
1281
+ //#endregion
1282
+ //#region src/response/helpers/cache.d.ts
1283
+ type ResponseCacheHeadersOptions = {
1284
+ maxAge?: number;
1285
+ modifiedTime?: string | Date;
1286
+ cacheControls?: string[];
1287
+ };
1288
+ declare function setResponseCacheHeaders(event: IAppEvent, options?: ResponseCacheHeadersOptions): void;
1289
+ //#endregion
1290
+ //#region src/response/helpers/event-stream/types.d.ts
1291
+ /**
1292
+ * https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format
1293
+ */
1294
+ type EventStreamMessage = {
1295
+ /**
1296
+ * The event ID to set the EventSource object's last event ID value.
1297
+ */
1298
+ id?: string;
1299
+ /**
1300
+ * The reconnection time.
1301
+ * If the connection to the server is lost, the browser will wait for the specified time before attempting to reconnect.
1302
+ * This must be an integer, specifying the reconnection time in milliseconds.
1303
+ */
1304
+ retry?: number;
1305
+ /**
1306
+ * The data field for the message.
1307
+ */
1308
+ data: string;
1309
+ /**
1310
+ * A string identifying the type of event described.
1311
+ */
1312
+ event?: string;
1313
+ };
1314
+ type EventStreamListener<T = any> = (err: Error | null, data: T) => void | Promise<void>;
1315
+ //#endregion
1316
+ //#region src/response/helpers/event-stream/module.d.ts
1317
+ type EventStreamOptions = {
1318
+ maxMessageSize?: number;
1319
+ };
1320
+ type EventStreamHandle = {
1321
+ write(message: string | EventStreamMessage): boolean;
1322
+ end(): void;
1323
+ response: Response;
1324
+ };
1325
+ declare function createEventStream(event: IAppEvent, options?: EventStreamOptions): EventStreamHandle;
1326
+ //#endregion
1327
+ //#region src/response/helpers/event-stream/utils.d.ts
1328
+ declare function serializeEventStreamMessage(message: EventStreamMessage): string;
1329
+ //#endregion
1330
+ //#region src/response/helpers/header.d.ts
1331
+ declare function appendResponseHeader(event: IAppEvent, name: string, value: string | string[]): void;
1332
+ declare function appendResponseHeaderDirective(event: IAppEvent, name: string, value: string | string[]): void;
1333
+ //#endregion
1334
+ //#region src/response/helpers/header-disposition.d.ts
1335
+ declare function setResponseHeaderAttachment(event: IAppEvent, filename?: string): void;
1336
+ declare function setResponseHeaderInline(event: IAppEvent, filename?: string): void;
1337
+ //#endregion
1338
+ //#region src/response/helpers/header-content-type.d.ts
1339
+ declare function setResponseHeaderContentType(event: IAppEvent, input: string, ifNotExists?: boolean): void;
1340
+ //#endregion
1341
+ //#region src/response/helpers/send-accepted.d.ts
1342
+ declare function sendAccepted(event: IAppEvent, data?: unknown): Promise<Response>;
1343
+ //#endregion
1344
+ //#region src/response/helpers/send-created.d.ts
1345
+ declare function sendCreated(event: IAppEvent, data?: unknown): Promise<Response>;
1346
+ //#endregion
1347
+ //#region src/response/helpers/send-file.d.ts
1348
+ type SendFileContentOptions = {
1349
+ end?: number;
1350
+ start?: number;
1351
+ };
1352
+ /**
1353
+ * File metadata used by {@link sendFile}. All fields are optional, but each
1354
+ * missing field disables related response features:
1355
+ *
1356
+ * - `size` — without it, range requests, `Accept-Ranges`, `Content-Length`,
1357
+ * `ETag`, and `Last-Modified` are all omitted (the response is sent
1358
+ * without HTTP-level caching or seekability).
1359
+ * - `mtime` — without it, `Last-Modified` is omitted and the `ETag` is not
1360
+ * emitted (`ETag` requires both `size` and `mtime`).
1361
+ * - `name` — falls back to `SendFileOptions.name` when set; if both are
1362
+ * missing, no `Content-Disposition` or extension-derived
1363
+ * `Content-Type` is set.
1364
+ */
1365
+ type SendFileStats = {
1366
+ size?: number;
1367
+ mtime?: Date | number | string;
1368
+ name?: string;
1369
+ };
1370
+ type SendFileDisposition = 'attachment' | 'inline';
1371
+ type SendFileContent = ReadableStream | ArrayBuffer | Uint8Array;
1372
+ type SendFileOptions = {
1373
+ stats: (() => Promise<SendFileStats> | SendFileStats) | SendFileStats;
1374
+ content: (options: SendFileContentOptions) => Promise<SendFileContent> | SendFileContent;
1375
+ /**
1376
+ * @deprecated Use `disposition: 'attachment'` instead. Kept for backwards
1377
+ * compatibility — when `disposition` is set, it takes precedence.
1378
+ */
1379
+ attachment?: boolean;
1380
+ disposition?: SendFileDisposition;
1381
+ name?: string;
1382
+ };
1383
+ declare function sendFile(event: IAppEvent, options: SendFileOptions): Promise<Response>;
1384
+ //#endregion
1385
+ //#region src/response/helpers/send-format.d.ts
1386
+ type ResponseFormatHandler = () => Response | unknown;
1387
+ type ResponseFormats = {
1388
+ default: ResponseFormatHandler;
1389
+ [key: string]: ResponseFormatHandler;
1390
+ };
1391
+ declare function sendFormat(event: IAppEvent, input: ResponseFormats): Response | unknown | undefined;
1392
+ //#endregion
1393
+ //#region src/response/helpers/send-redirect.d.ts
1394
+ declare function sendRedirect(event: IAppEvent, location: string, statusCode?: number): Response;
1395
+ //#endregion
1396
+ //#region src/response/helpers/send-stream.d.ts
1397
+ declare function sendStream(event: IAppEvent, stream: ReadableStream): Response;
1398
+ //#endregion
1399
+ //#region src/response/helpers/utils.d.ts
1400
+ declare function setResponseContentTypeByFileName(event: IAppEvent, fileName: string): void;
1401
+ //#endregion
1402
+ //#region src/response/to-response.d.ts
1403
+ /**
1404
+ * Convert a handler's return value into a Web `Response`.
1405
+ *
1406
+ * Returns synchronously for the common cases (string, JSON object,
1407
+ * binary, stream, blob) when ETag generation is disabled. Returns a
1408
+ * `Promise` when an ETag must be computed (the generator is async).
1409
+ *
1410
+ * Callers that want the async return uniformly can `await` the result
1411
+ * — `await` on a non-Promise still works but pays a microtask hop.
1412
+ * The App fast path branches on `instanceof Promise` to keep the
1413
+ * sync return truly sync.
1414
+ */
1415
+ declare function toResponse(value: unknown, event: IAppEvent): Response | undefined | Promise<Response | undefined>;
1416
+ //#endregion
1417
+ //#region src/request/helpers/cache.d.ts
1418
+ declare function isRequestCacheable(event: IAppEvent, modifiedTime: string | Date): boolean;
1419
+ //#endregion
1420
+ //#region src/request/helpers/header.d.ts
1421
+ declare function getRequestHeader(event: IAppEvent, name: string): string | null;
1422
+ //#endregion
1423
+ //#region src/request/helpers/header-accept.d.ts
1424
+ declare function getRequestAcceptableContentTypes(event: IAppEvent): string[];
1425
+ declare function getRequestAcceptableContentType(event: IAppEvent, input?: string | string[]): string | undefined;
1426
+ //#endregion
1427
+ //#region src/request/helpers/header-accept-charset.d.ts
1428
+ declare function getRequestAcceptableCharsets(event: IAppEvent): string[];
1429
+ declare function getRequestAcceptableCharset(event: IAppEvent, input: string | string[]): string | undefined;
1430
+ //#endregion
1431
+ //#region src/request/helpers/header-accept-encoding.d.ts
1432
+ declare function getRequestAcceptableEncodings(event: IAppEvent): string[];
1433
+ declare function getRequestAcceptableEncoding(event: IAppEvent, input: string | string[]): string | undefined;
1434
+ //#endregion
1435
+ //#region src/request/helpers/header-accept-language.d.ts
1436
+ declare function getRequestAcceptableLanguages(event: IAppEvent): string[];
1437
+ declare function getRequestAcceptableLanguage(event: IAppEvent, input?: string | string[]): string | undefined;
1438
+ //#endregion
1439
+ //#region src/request/helpers/header-content-type.d.ts
1440
+ declare function matchRequestContentType(event: IAppEvent, contentType: string): boolean;
1441
+ //#endregion
1442
+ //#region src/request/helpers/hostname.d.ts
1443
+ type RequestHostNameOptions = {
1444
+ trustProxy?: TrustProxyInput;
1445
+ };
1446
+ declare function getRequestHostName(event: IAppEvent, options?: RequestHostNameOptions): string | undefined;
1447
+ //#endregion
1448
+ //#region src/request/helpers/ip.d.ts
1449
+ type RequestIpOptions = {
1450
+ trustProxy?: TrustProxyInput;
1451
+ };
1452
+ /**
1453
+ * Get the client IP address from the request.
1454
+ *
1455
+ * When `trustProxy` is configured, walks the `X-Forwarded-For` chain
1456
+ * and returns the rightmost untrusted address (the actual client IP).
1457
+ * Falls back to `event.request.ip` (the direct connection IP).
1458
+ */
1459
+ declare function getRequestIP(event: IAppEvent, options?: RequestIpOptions): string | undefined;
1460
+ //#endregion
1461
+ //#region src/request/helpers/negotiator.d.ts
1462
+ declare function useRequestNegotiator(event: IAppEvent): Negotiator;
1463
+ //#endregion
1464
+ //#region src/request/helpers/protocol.d.ts
1465
+ type RequestProtocolOptions = {
1466
+ trustProxy?: TrustProxyInput;
1467
+ default?: string;
1468
+ };
1469
+ declare function getRequestProtocol(event: IAppEvent, options?: RequestProtocolOptions): string;
1470
+ //#endregion
1471
+ //#region src/router/linear/module.d.ts
1472
+ /**
1473
+ * Default router — walks registered routes linearly per request and
1474
+ * runs each route's mount-level matcher (built via `buildRoutePathMatcher`,
1475
+ * path-to-regexp-backed). Routes without a mount path (mount-less
1476
+ * middleware / nested apps registered via `.use(handler)`) match every
1477
+ * request directly — there is no per-route `matchPath()` fallback.
1478
+ *
1479
+ * Behaviour-preserving wrapper around the previous in-line stack walk
1480
+ * in `executePipelineStepLookup`. The matcher allocations live here
1481
+ * (not on the registered route), so routers using a different matching
1482
+ * strategy (radix tree, aggregated regex, …) can ignore this file
1483
+ * entirely.
1484
+ *
1485
+ * Optional per-router lookup cache: pass an `ICache` via
1486
+ * `BaseRouterOptions.cache` to skip the linear walk on repeated
1487
+ * requests for the same path. Default is no caching.
1488
+ */
1489
+ declare class LinearRouter<T extends ObjectLiteral = ObjectLiteral> implements IRouter<T> {
1490
+ protected _routes: Route<T>[];
1491
+ protected _matchers: (IPathMatcher | undefined)[];
1492
+ protected cache?: ICache<readonly RouteMatch<T>[]>;
1493
+ constructor(options?: BaseRouterOptions<T>);
1494
+ add(route: Route<T>): void;
1495
+ lookup(path: string, _method?: MethodNameLike): readonly RouteMatch<T>[];
1496
+ clone(): IRouter<T>;
1497
+ }
1498
+ //#endregion
1499
+ //#region src/router/smart/module.d.ts
1500
+ type SmartRouterOptions<T extends ObjectLiteral = ObjectLiteral> = BaseRouterOptions<T> & {
1501
+ /**
1502
+ * Route count at or above which `SmartRouter` switches from
1503
+ * `LinearRouter` (faster at small N) to `TrieRouter` (faster
1504
+ * at large N). Default `30`.
1505
+ */
1506
+ threshold?: number;
1507
+ };
1508
+ /**
1509
+ * Auto-selecting router. Accumulates registered routes in a pending
1510
+ * buffer; on the first `lookup()` call, picks `LinearRouter` or
1511
+ * `TrieRouter` based on the registered route count and replays the
1512
+ * pending list onto the chosen inner router. Every subsequent call
1513
+ * — `add`, `lookup`, `clone` — forwards to the inner.
1514
+ *
1515
+ * Use this when you don't want to commit to a router family up-front
1516
+ * (e.g. a library that registers a variable number of routes
1517
+ * depending on configuration). For known workloads, prefer the
1518
+ * concrete router — `SmartRouter` adds one indirection per call.
1519
+ *
1520
+ * Inspired by Hono's `SmartRouter` (which auto-selects across more
1521
+ * candidates including `RegExpRouter`); ours covers the only choice
1522
+ * that matters in routup today: linear-vs-trie at the registration-
1523
+ * size crossover.
1524
+ */
1525
+ declare class SmartRouter<T extends ObjectLiteral = ObjectLiteral> implements IRouter<T> {
1526
+ protected inner?: IRouter<T>;
1527
+ protected pending: Route<T>[];
1528
+ protected readonly threshold: number;
1529
+ /**
1530
+ * Cache handed off to whichever inner router gets chosen. Stays
1531
+ * `undefined` if the user didn't configure one.
1532
+ */
1533
+ protected readonly cache?: ICache<readonly RouteMatch<T>[]>;
1534
+ constructor(options?: SmartRouterOptions<T>);
1535
+ add(route: Route<T>): void;
1536
+ lookup(path: string, method?: string): readonly RouteMatch<T>[];
1537
+ clone(): IRouter<T>;
1538
+ /**
1539
+ * Pick the inner router based on the registered route count.
1540
+ * `LinearRouter` for tiny tables, `TrieRouter` past the
1541
+ * configured threshold.
1542
+ *
1543
+ * @protected
1544
+ */
1545
+ protected choose(): IRouter<T>;
1546
+ }
1547
+ //#endregion
1548
+ //#region src/router/trie/types.d.ts
1549
+ /**
1550
+ * Tagged param-extraction instruction. Built at registration time
1551
+ * during `insertIntoTrie`; consumed at lookup time to build the
1552
+ * params object directly from the request's pre-split segments —
1553
+ * no regex execution per match.
1554
+ *
1555
+ * - `segment`: capture `segments[depth]` as `name`.
1556
+ * - `splat`: capture `segments[depth..]` joined with `/` as `name`.
1557
+ * `name` is `'*'` for the unnamed bare splat (`/files/*`).
1558
+ */
1559
+ type ParamCapture = {
1560
+ kind: 'segment';
1561
+ depth: number;
1562
+ name: string;
1563
+ } | {
1564
+ kind: 'splat';
1565
+ depth: number;
1566
+ name: string;
1567
+ };
1568
+ /**
1569
+ * Per-variant route record stored at a trie leaf.
1570
+ *
1571
+ * `paramsIndexMap` populated for trie-walked variants so lookup can
1572
+ * extract params without running `matcher.exec`. `matcher` is only
1573
+ * populated for universal-bucket routes (registered paths the trie
1574
+ * parser couldn't handle — regex constraints, compound segments,
1575
+ * escapes — that fall back to path-to-regexp).
1576
+ *
1577
+ * `index` is shared across every variant produced from a single
1578
+ * `add()` call so the candidate list deduplicates naturally on
1579
+ * registration order.
1580
+ */
1581
+ type IndexedRoute<T extends ObjectLiteral = ObjectLiteral> = {
1582
+ route: Route<T>;
1583
+ index: number;
1584
+ /**
1585
+ * Universal-bucket-only — populated for paths the trie parser
1586
+ * couldn't handle (regex constraints, compound segments, escapes).
1587
+ * The lookup loop runs `matcher.exec(path)` to confirm the match
1588
+ * and extract params, identical to `LinearRouter`.
1589
+ */
1590
+ matcher?: IPathMatcher;
1591
+ /**
1592
+ * Trie-walked-only — populated for variants that came out of
1593
+ * `parsePath`. The lookup loop walks this list to build the
1594
+ * `params` object directly from request segments, no regex.
1595
+ */
1596
+ paramsIndexMap?: ParamCapture[];
1597
+ /**
1598
+ * Trie-walked-only — how many request segments this variant
1599
+ * consumes when matched. Used to compute `match.path` (the
1600
+ * matched prefix) at lookup time without re-running a matcher.
1601
+ *
1602
+ * - Exact / prefix variants: `segments.length` (consume up to
1603
+ * the leaf).
1604
+ * - Splat variants: depth of the splat segment (it then absorbs
1605
+ * the rest — see `splatTerminated`).
1606
+ */
1607
+ matchDepth?: number;
1608
+ /**
1609
+ * Trie-walked-only — `true` when this variant ends in a splat.
1610
+ * `match.path` is then computed from the *full* request length
1611
+ * (the splat absorbed every remaining segment), not from
1612
+ * `matchDepth` (which is only the depth of the splat node).
1613
+ */
1614
+ splatTerminated?: boolean;
1615
+ };
1616
+ type Segment = {
1617
+ kind: 'static';
1618
+ value: string;
1619
+ } | {
1620
+ kind: 'param';
1621
+ name: string;
1622
+ } | {
1623
+ kind: 'splat';
1624
+ name: string;
1625
+ };
1626
+ /**
1627
+ * Method-keyed bucket of indexed routes. The empty-string key
1628
+ * (`''`) holds method-agnostic entries (handlers registered without
1629
+ * a method binding). Stored as a prototype-less object so user-
1630
+ * controlled method strings can't collide with `Object.prototype`
1631
+ * keys (`__proto__`, `hasOwnProperty`, …).
1632
+ */
1633
+ type MethodBuckets<T extends ObjectLiteral = ObjectLiteral> = Record<string, IndexedRoute<T>[]>;
1634
+ type TrieNode<T extends ObjectLiteral = ObjectLiteral> = {
1635
+ staticChildren: Map<string, TrieNode<T>>;
1636
+ paramChild?: TrieNode<T>;
1637
+ /**
1638
+ * Entries whose path ends with a splat at this depth (`/files/*`,
1639
+ * `/foo/*name`, …). They match the current node and every deeper
1640
+ * request path. Bucketed by HTTP method (`''` = method-agnostic).
1641
+ */
1642
+ splatRoutes: MethodBuckets<T>;
1643
+ /**
1644
+ * Exact-match routes whose path ends at this node — only matched
1645
+ * when the request path is fully consumed at this depth.
1646
+ * Bucketed by HTTP method (`''` = method-agnostic).
1647
+ */
1648
+ exactRoutes: MethodBuckets<T>;
1649
+ /**
1650
+ * Prefix-match routes (middleware / nested apps) whose path ends
1651
+ * at this node — matched whenever this node is reached,
1652
+ * regardless of remaining depth. Not bucketed: middleware is
1653
+ * method-agnostic by design (the inner handler does its own
1654
+ * method discrimination).
1655
+ */
1656
+ prefixRoutes: IndexedRoute<T>[];
1657
+ };
1658
+ //#endregion
1659
+ //#region src/router/trie/module.d.ts
1660
+ /**
1661
+ * Radix-trie router — registers routes into a per-segment tree at
1662
+ * `add()` time and walks the tree at `lookup()` to collect
1663
+ * candidates by structure rather than by linear scan.
1664
+ *
1665
+ * Inspired by Hono's `TrieRouter` and rou3. The trie handles
1666
+ * routup's path vocabulary directly via its own parser
1667
+ * (`./parser.ts`):
1668
+ *
1669
+ * - Static segments (`/users`)
1670
+ * - Named params (`:id`)
1671
+ * - Optional params (`:id?`) — expanded to two route variants at
1672
+ * registration (T2)
1673
+ * - Optional groups (`/users{/edit}`) — same expansion strategy
1674
+ * - Bare and named splats (`/files/*`, `/files/*rest`)
1675
+ *
1676
+ * Per-leaf storage is bucketed by HTTP method (T4) so lookup
1677
+ * narrows to the request method's bucket(s) instead of emitting
1678
+ * every entry at the leaf and letting the dispatcher's filter
1679
+ * discard mismatches.
1680
+ *
1681
+ * Param extraction is `paramsIndexMap`-driven (T3): a pre-built
1682
+ * `Array<{ depth, name }>` per variant lets `extractTrieParams`
1683
+ * read params straight from the request's pre-split segments — no
1684
+ * regex execution per match.
1685
+ *
1686
+ * Paths the trie parser doesn't handle (compound segments like
1687
+ * `/files/:n.ext`, escape sequences `\:`, regex constraints) and
1688
+ * empty/root paths fall through to the `universal` bucket. That
1689
+ * bucket still uses `path-to-regexp` via `buildRoutePathMatcher`,
1690
+ * so correctness is preserved.
1691
+ *
1692
+ * Pure-static-spine fast path (`shortCircuit`): when the request
1693
+ * walks a static spine with no param/splat/prefix siblings on any
1694
+ * traversed node, the leaf's `exactRoutes` (filtered to the request
1695
+ * method's buckets) is the full answer — no need to walk the param
1696
+ * branch or collect prefix candidates at intermediate nodes.
1697
+ */
1698
+ declare class TrieRouter<T extends ObjectLiteral = ObjectLiteral> implements IRouter<T> {
1699
+ /**
1700
+ * Monotonic counter assigned as the registration `index` on each
1701
+ * route — the dispatch loop uses it to preserve registration
1702
+ * order across the candidate list. App owns the canonical
1703
+ * `Route<T>[]` list (Plan 019); the trie no longer keeps a
1704
+ * parallel copy.
1705
+ */
1706
+ protected _routeCount: number;
1707
+ protected root: TrieNode<T>;
1708
+ /**
1709
+ * Routes that bypass the trie — registered with no path, with
1710
+ * the root path `/`, or with a path containing syntax the
1711
+ * parser doesn't recognise. Walked linearly on every lookup,
1712
+ * merged into the result in registration order.
1713
+ */
1714
+ protected universal: IndexedRoute<T>[];
1715
+ protected cache?: ICache<readonly RouteMatch<T>[]>;
1716
+ constructor(options?: BaseRouterOptions<T>);
1717
+ add(route: Route<T>): void;
1718
+ lookup(path: string, method?: MethodNameLike): readonly RouteMatch<T>[];
1719
+ clone(): IRouter<T>;
1720
+ /**
1721
+ * T1: returns the pre-computed candidate list when the request's
1722
+ * static spine has no param sibling, no prefix routes, and no
1723
+ * splats along the way. The leaf node's `exactRoutes` (filtered
1724
+ * to the request method's buckets) is then the complete answer —
1725
+ * no need to walk the param branch or collect prefix/splat
1726
+ * candidates from intermediate nodes. When any branch is
1727
+ * encountered, returns `null` and the caller falls through to
1728
+ * the regular `walk`.
1729
+ */
1730
+ protected shortCircuit(segments: string[], method: MethodNameLike | undefined): IndexedRoute<T>[] | null;
1731
+ protected parseRequestPath(path: string): string[];
1732
+ protected insertIntoTrie(segments: Segment[], route: Route<T>, index: number): void;
1733
+ protected walk(node: TrieNode<T>, segments: string[], depth: number, collected: IndexedRoute<T>[], method: MethodNameLike | undefined): void;
1734
+ protected isExactMatchRoute(route: Route<T>): boolean;
1735
+ /**
1736
+ * T5: copy params onto a prototype-less object so downstream
1737
+ * lookups skip prototype-chain traversal and avoid `__proto__` /
1738
+ * `hasOwnProperty` shadowing from user-controlled segment values.
1739
+ */
1740
+ protected assignParams(source: Record<string, string | undefined>): Record<string, string | undefined>;
1741
+ }
1742
+ //#endregion
1743
+ //#region src/router/utils.d.ts
1744
+ /**
1745
+ * Build a path-to-regexp-backed `PathMatcher` for the route's mount
1746
+ * path, applying the exact-vs-prefix convention every router should
1747
+ * agree on:
1748
+ *
1749
+ * - `route.method !== undefined` → exact match (method-bound route)
1750
+ * - `route.method === undefined` → prefix match (middleware / nested
1751
+ * app)
1752
+ *
1753
+ * Returns `undefined` when the route has no mount path — middleware
1754
+ * registered without a path matches every request.
1755
+ *
1756
+ * Routers are free to ignore this helper and build their own match
1757
+ * mechanism (radix tree, single aggregated regex, etc.) — it's
1758
+ * provided as a convenience for routers that want path-to-regexp
1759
+ * semantics with minimal boilerplate.
1760
+ */
1761
+ declare function buildRoutePathMatcher<T extends ObjectLiteral = ObjectLiteral>(route: Route<T>): IPathMatcher | undefined;
1762
+ //#endregion
1763
+ //#region src/app/module.d.ts
1764
+ declare class App implements IApp {
1765
+ /**
1766
+ * A label for the router instance.
1767
+ */
1768
+ readonly name?: string;
1769
+ /**
1770
+ * Registration-time path prefix for entries registered on this
1771
+ * App. Local to this instance — never inherited from a parent.
1772
+ *
1773
+ * @protected
1774
+ */
1775
+ protected _path?: Path;
1776
+ /**
1777
+ * Pluggable router (route table) — owns the "which entries match
1778
+ * this path?" lookup. Defaults to `LinearRouter` (walks entries
1779
+ * linearly per request); swap in via `AppContext.router`
1780
+ * for a radix/trie implementation on apps with many routes.
1781
+ *
1782
+ * @protected
1783
+ */
1784
+ protected router: IRouter<RouteEntry>;
1785
+ /**
1786
+ * Lifecycle hook registry.
1787
+ *
1788
+ * @protected
1789
+ */
1790
+ protected hooks: IHooks;
1791
+ /**
1792
+ * Normalized options for this App instance.
1793
+ *
1794
+ * Frozen on construction and on every `extendOptions` update —
1795
+ * once published to `event.appOptions` it is shared across all
1796
+ * requests, and a handler must not be able to mutate
1797
+ * router-global state. `extendOptions` therefore uses a
1798
+ * functional update (build a new object, freeze it, replace
1799
+ * the slot) rather than mutating in place.
1800
+ */
1801
+ protected _options: Readonly<AppOptions>;
1802
+ /**
1803
+ * Registry of installed plugins (name → version) on this router.
1804
+ *
1805
+ * @protected
1806
+ */
1807
+ protected plugins: Map<string, string | undefined>;
1808
+ /**
1809
+ * Every route registered on this App, in registration order.
1810
+ *
1811
+ * App owns the canonical list — the `IRouter` contract has no
1812
+ * `routes` field, so cascades / clones / `setRouter` replay
1813
+ * read from here instead of asking the router. Routes are
1814
+ * pushed alongside every `this.router.add()` via the `register`
1815
+ * helper.
1816
+ *
1817
+ * @protected
1818
+ */
1819
+ protected _routes: Route<RouteEntry>[];
1820
+ constructor(input?: AppContext);
1821
+ /**
1822
+ * Register a route with the active router and record it on the
1823
+ * App so we can replay it onto a different router later (see
1824
+ * `setRouter`) and so cascades / clones have a source of truth
1825
+ * independent of the router instance.
1826
+ *
1827
+ * @protected
1828
+ */
1829
+ protected register(route: Route<RouteEntry>): void;
1830
+ /**
1831
+ * Swap the active router. Replays every previously-registered
1832
+ * route onto the new router so lookups stay correct.
1833
+ *
1834
+ * Useful for picking a router after route shape is known (e.g.
1835
+ * a SmartRouter-style decision), or for testing alternatives
1836
+ * mid-flight without rebuilding the App. Any cache the previous
1837
+ * router carried is dropped along with it.
1838
+ */
1839
+ setRouter(router: IRouter<RouteEntry>): void;
1840
+ /**
1841
+ * Public entry point — creates a DispatcherEvent from the request,
1842
+ * runs the pipeline, and returns a Response (with 404/500 fallbacks).
1843
+ */
1844
+ fetch(request: AppRequest): Promise<Response>;
1845
+ protected buildFallbackResponse(request: AppRequest, event: IDispatcherEvent, status: number, message: string): Response;
1846
+ /**
1847
+ * Mount-time option inheritance — fill in any of this App's
1848
+ * unset option keys from the supplied parent options. Called by
1849
+ * `App.use(child)` after narrowing to `App` via `isAppInstance`.
1850
+ *
1851
+ * Public so callers don't need to reach into another App's
1852
+ * protected fields: the parent passes its options by value and
1853
+ * the child decides what to do with them.
1854
+ *
1855
+ * Shallow per-key merge. App-local concerns — `name`, `path`,
1856
+ * `hooks`, `plugins`, `router`, and the router's cache — are
1857
+ * deliberately not propagated; they sit on `AppContext`, not
1858
+ * inside `AppOptions`.
1859
+ *
1860
+ * Cascades to any Apps already mounted on this one — a deeper
1861
+ * grandchild gets the new keys too. Without this, mounting
1862
+ * grandchild → child → parent in that order would leave the
1863
+ * grandchild without parent's options (it adopted from child
1864
+ * before child had them).
1865
+ *
1866
+ * Late mutation of the parent's options after this call does NOT
1867
+ * propagate. `AppOptions` is configured at construction-and-mount;
1868
+ * later changes are not a supported workflow.
1869
+ */
1870
+ extendOptions(incoming: Readonly<AppOptions>): void;
1871
+ protected executePipelineStep(context: AppPipelineContext): Promise<void>;
1872
+ protected executePipelineStepStart(context: AppPipelineContext): Promise<void>;
1873
+ protected executePipelineStepLookup(context: AppPipelineContext): Promise<void>;
1874
+ protected executePipelineStepChildBefore(context: AppPipelineContext): Promise<void>;
1875
+ protected executePipelineStepChildAfter(context: AppPipelineContext): Promise<void>;
1876
+ protected executePipelineStepChildDispatch(context: AppPipelineContext): Promise<void>;
1877
+ protected executePipelineStepFinish(context: AppPipelineContext): Promise<void>;
1878
+ dispatch(event: IDispatcherEvent): Promise<Response | undefined>;
1879
+ delete(...handlers: (Handler | HandlerOptions)[]): this;
1880
+ delete(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1881
+ get(...handlers: (Handler | HandlerOptions)[]): this;
1882
+ get(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1883
+ post(...handlers: (Handler | HandlerOptions)[]): this;
1884
+ post(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1885
+ put(...handlers: (Handler | HandlerOptions)[]): this;
1886
+ put(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1887
+ patch(...handlers: (Handler | HandlerOptions)[]): this;
1888
+ patch(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1889
+ head(...handlers: (Handler | HandlerOptions)[]): this;
1890
+ head(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1891
+ options(...handlers: (Handler | HandlerOptions)[]): this;
1892
+ options(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1893
+ protected useForMethod(method: MethodName, ...input: (Path | Handler | HandlerOptions)[]): void;
1894
+ use(router: IApp): this;
1895
+ use(handler: Handler | HandlerOptions): this;
1896
+ use(plugin: Plugin): this;
1897
+ use(path: Path, router: IApp): this;
1898
+ use(path: Path, handler: Handler | HandlerOptions): this;
1899
+ use(path: Path, plugin: Plugin): this;
1900
+ /**
1901
+ * Check if a plugin with the given name is installed on this router.
1902
+ */
1903
+ hasPlugin(name: string): boolean;
1904
+ /**
1905
+ * Get the version of an installed plugin by name on this router,
1906
+ * or `undefined` if the plugin is not installed here.
1907
+ */
1908
+ getPluginVersion(name: string): string | undefined;
1909
+ protected install(plugin: Plugin, context?: PluginInstallContext): this;
1910
+ /**
1911
+ * Return a new `App` that mirrors this one but owns independent
1912
+ * mountable state.
1913
+ *
1914
+ * The new router has:
1915
+ * - a fresh `stack` array of shallow-copied entries (handlers and child
1916
+ * routers are shared by reference; only the wrapping entries are new)
1917
+ * - the same `pathMatcher` reference (it is stateless)
1918
+ * - a fresh `Hooks` instance seeded with the current listeners
1919
+ * - a shallow copy of `_options`
1920
+ * - a fresh `plugins` map with the same entries
1921
+ *
1922
+ * Use this when the same logical router needs to be mounted under
1923
+ * multiple paths — each mount can receive its own clone so subsequent
1924
+ * mutations on one mount do not bleed into the others.
1925
+ */
1926
+ clone(): IApp;
1927
+ /**
1928
+ * Add a hook listener.
1929
+ *
1930
+ * @param name
1931
+ * @param fn
1932
+ * @param priority
1933
+ */
1934
+ on(name: typeof HookName.START | typeof HookName.END | typeof HookName.CHILD_DISPATCH_BEFORE | typeof HookName.CHILD_DISPATCH_AFTER, fn: HookDefaultListener, priority?: number): HookUnsubscribeFn;
1935
+ on(name: typeof HookName.CHILD_MATCH, fn: HookDefaultListener, priority?: number): HookUnsubscribeFn;
1936
+ on(name: typeof HookName.ERROR, fn: HookErrorListener, priority?: number): HookUnsubscribeFn;
1937
+ /**
1938
+ * Remove single or all hook listeners.
1939
+ *
1940
+ * @param name
1941
+ */
1942
+ off(name: HookName): this;
1943
+ off(name: HookName, fn: HookListener): this;
1944
+ }
1945
+ //#endregion
1946
+ //#region src/app/options.d.ts
1947
+ declare function normalizeAppOptions(input: AppOptionsInput): AppOptions;
1948
+ //#endregion
1949
+ export { createError as $, HandlerSymbol as $t, sendFormat as A, isWebHandlerProvider as At, setResponseHeaderAttachment as B, CoreHandlerOptions as Bt, getRequestAcceptableContentTypes as C, isPluginError as Ct, setResponseContentTypeByFileName as D, isHandler as Dt, toResponse as E, matchHandlerMethod as Et, SendFileStats as F, fromNodeMiddleware as Ft, EventStreamHandle as G, ErrorHandlerOptions as Gt, appendResponseHeader as H, HandlerOptions as Ht, sendFile as I, NodeHandler as It, EventStreamListener as J, PathMatcher as Jt, EventStreamOptions as K, HandlerBaseOptions as Kt, sendCreated as L, NodeMiddleware as Lt, SendFileContentOptions as M, WebHandler as Mt, SendFileDisposition as N, WebHandlerProvider as Nt, sendStream as O, isHandlerOptions as Ot, SendFileOptions as P, fromNodeHandler as Pt, isError as Q, PathMatcherOptions as Qt, sendAccepted as R, defineCoreHandler as Rt, getRequestAcceptableContentType as S, PluginAlreadyInstalledError as St, isRequestCacheable as T, PluginErrorCode as Tt, appendResponseHeaderDirective as U, defineErrorHandler as Ut, setResponseHeaderInline as V, Handler as Vt, serializeEventStreamMessage as W, ErrorHandler as Wt, ResponseCacheHeadersOptions as X, Path as Xt, EventStreamMessage as Y, IPathMatcher as Yt, setResponseCacheHeaders as Z, PathMatcherExecResult as Zt, getRequestAcceptableLanguages as _, Plugin as _t, SmartRouter as a, AppEventCreateContext as an, HandlerRouteEntry as at, getRequestAcceptableCharset as b, PluginNotInstalledError as bt, RequestProtocolOptions as c, IAppEvent as cn, BaseRouterOptions as ct, RequestIpOptions as d, MethodName as dn, Route as dt, HandlerType as en, AppContext as et, getRequestIP as f, MethodNameLike as fn, RouteMatch as ft, getRequestAcceptableLanguage as g, isPlugin as gt, matchRequestContentType as h, HTTPErrorInput$1 as hn, ICache as ht, TrieRouter as i, AppEvent as in, AppRouteEntry as it, SendFileContent as j, fromWebHandler as jt, sendRedirect as k, isWebHandler as kt, getRequestProtocol as l, NextFn as ln, IRouter as lt, getRequestHostName as m, ErrorSymbol as mn, LruCacheOptions as mt, App as n, IDispatcher as nn, AppOptionsInput as nt, SmartRouterOptions as o, AppRequest as on, IApp as ot, RequestHostNameOptions as p, AppError as pn, LruCache as pt, createEventStream as q, isPath as qt, buildRoutePathMatcher as r, IDispatcherEvent as rn, AppPipelineContext as rt, LinearRouter as s, AppResponse as sn, RouteEntry as st, normalizeAppOptions as t, DispatcherEvent as tn, AppOptions as tt, useRequestNegotiator as u, HeaderName as un, ObjectLiteral as ut, getRequestAcceptableEncoding as v, PluginInstallContext as vt, getRequestHeader as w, PluginError as wt, getRequestAcceptableCharsets as x, PluginInstallError as xt, getRequestAcceptableEncodings as y, PluginInstallFn as yt, setResponseHeaderContentType as z, CoreHandler as zt };
1950
+ //# sourceMappingURL=index-kxLRw2Wc.d.mts.map