routup 5.0.0-beta.2 → 5.0.0-beta.4

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.
@@ -1,55 +1,59 @@
1
- import { HTTPError, HTTPErrorInput, HTTPErrorInput as HTTPErrorInput$1 } from "@ebec/http";
2
1
  import { FastURL, ServerRequest } from "srvx";
3
- import { Key, ParseOptions, PathToRegexpOptions } from "path-to-regexp";
2
+ import { HTTPError, HTTPErrorInput, HTTPErrorInput as HTTPErrorInput$1 } from "@ebec/http";
4
3
  import Negotiator from "negotiator";
4
+ import { Key, ParseOptions, PathToRegexpOptions } from "path-to-regexp";
5
5
  import { IncomingMessage, ServerResponse } from "node:http";
6
6
 
7
7
  //#region src/constants.d.ts
8
- declare enum MethodName {
9
- GET = "GET",
10
- POST = "POST",
11
- PUT = "PUT",
12
- PATCH = "PATCH",
13
- DELETE = "DELETE",
14
- OPTIONS = "OPTIONS",
15
- HEAD = "HEAD"
16
- }
17
- declare enum HeaderName {
18
- ACCEPT = "accept",
19
- ACCEPT_CHARSET = "accept-charset",
20
- ACCEPT_ENCODING = "accept-encoding",
21
- ACCEPT_LANGUAGE = "accept-language",
22
- ACCEPT_RANGES = "accept-ranges",
23
- ALLOW = "allow",
24
- CACHE_CONTROL = "cache-control",
25
- CONTENT_DISPOSITION = "content-disposition",
26
- CONTENT_ENCODING = "content-encoding",
27
- CONTENT_LENGTH = "content-length",
28
- CONTENT_RANGE = "content-range",
29
- CONTENT_TYPE = "content-type",
30
- CONNECTION = "connection",
31
- COOKIE = "cookie",
32
- ETag = "etag",
33
- HOST = "host",
34
- IF_MODIFIED_SINCE = "if-modified-since",
35
- IF_NONE_MATCH = "if-none-match",
36
- LAST_MODIFIED = "last-modified",
37
- LOCATION = "location",
38
- RANGE = "range",
39
- RATE_LIMIT_LIMIT = "ratelimit-limit",
40
- RATE_LIMIT_REMAINING = "ratelimit-remaining",
41
- RATE_LIMIT_RESET = "ratelimit-reset",
42
- RETRY_AFTER = "retry-after",
43
- SET_COOKIE = "set-cookie",
44
- TRANSFER_ENCODING = "transfer-encoding",
45
- X_ACCEL_BUFFERING = "x-accel-buffering",
46
- X_FORWARDED_HOST = "x-forwarded-host",
47
- X_FORWARDED_FOR = "x-forwarded-for",
48
- X_FORWARDED_PROTO = "x-forwarded-proto"
49
- }
8
+ declare const MethodName: {
9
+ readonly GET: "GET";
10
+ readonly POST: "POST";
11
+ readonly PUT: "PUT";
12
+ readonly PATCH: "PATCH";
13
+ readonly DELETE: "DELETE";
14
+ readonly OPTIONS: "OPTIONS";
15
+ readonly HEAD: "HEAD";
16
+ };
17
+ type MethodName = typeof MethodName[keyof typeof MethodName];
18
+ declare const HeaderName: {
19
+ readonly ACCEPT: "accept";
20
+ readonly ACCEPT_CHARSET: "accept-charset";
21
+ readonly ACCEPT_ENCODING: "accept-encoding";
22
+ readonly ACCEPT_LANGUAGE: "accept-language";
23
+ readonly ACCEPT_RANGES: "accept-ranges";
24
+ readonly ALLOW: "allow";
25
+ readonly CACHE_CONTROL: "cache-control";
26
+ readonly CONTENT_DISPOSITION: "content-disposition";
27
+ readonly CONTENT_ENCODING: "content-encoding";
28
+ readonly CONTENT_LENGTH: "content-length";
29
+ readonly CONTENT_RANGE: "content-range";
30
+ readonly CONTENT_TYPE: "content-type";
31
+ readonly CONNECTION: "connection";
32
+ readonly COOKIE: "cookie";
33
+ readonly ETag: "etag";
34
+ readonly HOST: "host";
35
+ readonly IF_MODIFIED_SINCE: "if-modified-since";
36
+ readonly IF_NONE_MATCH: "if-none-match";
37
+ readonly LAST_MODIFIED: "last-modified";
38
+ readonly LOCATION: "location";
39
+ readonly RANGE: "range";
40
+ readonly RATE_LIMIT_LIMIT: "ratelimit-limit";
41
+ readonly RATE_LIMIT_REMAINING: "ratelimit-remaining";
42
+ readonly RATE_LIMIT_RESET: "ratelimit-reset";
43
+ readonly RETRY_AFTER: "retry-after";
44
+ readonly SET_COOKIE: "set-cookie";
45
+ readonly TRANSFER_ENCODING: "transfer-encoding";
46
+ readonly X_ACCEL_BUFFERING: "x-accel-buffering";
47
+ readonly X_FORWARDED_HOST: "x-forwarded-host";
48
+ readonly X_FORWARDED_FOR: "x-forwarded-for";
49
+ readonly X_FORWARDED_PROTO: "x-forwarded-proto";
50
+ };
51
+ type HeaderName = typeof HeaderName[keyof typeof HeaderName];
50
52
  //#endregion
51
53
  //#region src/error/module.d.ts
54
+ declare const ErrorSymbol: unique symbol;
52
55
  declare class RoutupError extends HTTPError {
56
+ readonly '@instanceof': symbol;
53
57
  constructor(input?: HTTPErrorInput$1);
54
58
  }
55
59
  //#endregion
@@ -57,9 +61,9 @@ declare class RoutupError extends HTTPError {
57
61
  type RoutupResponse = {
58
62
  status: number;
59
63
  headers: Headers;
60
- statusText?: string;
61
64
  };
62
65
  type RoutupRequest = ServerRequest;
66
+ type NextFn = (error?: Error) => unknown | Promise<unknown>;
63
67
  interface IRoutupEvent {
64
68
  /**
65
69
  * The srvx ServerRequest (extends Web Standard Request).
@@ -68,11 +72,11 @@ interface IRoutupEvent {
68
72
  /**
69
73
  * Route parameters extracted from the URL path pattern.
70
74
  */
71
- params: Record<string, any>;
75
+ readonly params: Record<string, any>;
72
76
  /**
73
77
  * Current request path, adjusted relative to the mount point during router nesting.
74
78
  */
75
- path: string;
79
+ readonly path: string;
76
80
  /**
77
81
  * HTTP method (GET, POST, PUT, etc.).
78
82
  */
@@ -80,16 +84,7 @@ interface IRoutupEvent {
80
84
  /**
81
85
  * Accumulated mount path from nested routers.
82
86
  */
83
- mountPath: string;
84
- /**
85
- * Error that occurred during dispatch, if any.
86
- */
87
- error?: RoutupError;
88
- /**
89
- * Router ID stack for nesting tracking.
90
- * Used internally by router options resolution.
91
- */
92
- routerPath: number[];
87
+ readonly mountPath: string;
93
88
  /**
94
89
  * Web Standard Headers from the request.
95
90
  */
@@ -108,10 +103,6 @@ interface IRoutupEvent {
108
103
  * that go through `toResponse()`.
109
104
  */
110
105
  readonly response: RoutupResponse;
111
- /**
112
- * Whether a response has been produced.
113
- */
114
- dispatched: boolean;
115
106
  /**
116
107
  * Per-request store for caching and plugin state.
117
108
  *
@@ -119,86 +110,168 @@ interface IRoutupEvent {
119
110
  * Data is garbage collected with the event when the request completes.
120
111
  */
121
112
  readonly store: Record<string | symbol, unknown>;
113
+ /**
114
+ * Pre-resolved router options for the current dispatch context.
115
+ *
116
+ * Contains merged options from the router path stack with defaults applied.
117
+ */
118
+ readonly routerOptions: RouterOptions;
119
+ /**
120
+ * Abort signal tied to the request lifecycle.
121
+ *
122
+ * When a `timeout` router option is set, this signal aborts after the
123
+ * specified duration. Handlers performing long I/O (fetch, streams, DB queries)
124
+ * can pass this signal to those operations for cooperative cancellation.
125
+ */
126
+ readonly signal: AbortSignal;
122
127
  /**
123
128
  * Call the next handler in the pipeline (onion model).
124
129
  *
125
130
  * The result is cached — calling `next()` multiple times returns the same response.
126
131
  * Returns the downstream `Response`, or `undefined` if no handler matched.
127
132
  */
128
- next(): Promise<Response | undefined>;
133
+ next(error?: Error): Promise<Response | undefined>;
129
134
  }
130
135
  //#endregion
131
136
  //#region src/event/module.d.ts
137
+ type RoutupEventCreateContext = {
138
+ request: RoutupRequest;
139
+ params: Record<string, any>;
140
+ path: string;
141
+ method: string;
142
+ mountPath: string;
143
+ headers: Headers;
144
+ searchParams: URLSearchParams;
145
+ response: RoutupResponse;
146
+ store: Record<string | symbol, unknown>;
147
+ signal: AbortSignal;
148
+ routerOptions: () => RouterOptions;
149
+ next: (event: IRoutupEvent, error?: Error) => Promise<Response | undefined>;
150
+ };
132
151
  declare class RoutupEvent implements IRoutupEvent {
133
152
  readonly request: RoutupRequest;
153
+ readonly params: Record<string, any>;
154
+ readonly path: string;
155
+ readonly method: string;
156
+ readonly mountPath: string;
157
+ readonly headers: Headers;
158
+ readonly searchParams: URLSearchParams;
159
+ readonly response: RoutupResponse;
160
+ readonly store: Record<string | symbol, unknown>;
161
+ readonly signal: AbortSignal;
162
+ protected _context: RoutupEventCreateContext;
163
+ protected _routerOptions?: RouterOptions;
164
+ constructor(context: RoutupEventCreateContext);
165
+ get routerOptions(): RouterOptions;
166
+ next(error?: Error): Promise<Response | undefined>;
167
+ }
168
+ //#endregion
169
+ //#region src/hook/constants.d.ts
170
+ declare const HookName: {
171
+ readonly REQUEST: "request";
172
+ readonly RESPONSE: "response";
173
+ readonly ERROR: "error";
174
+ readonly CHILD_MATCH: "childMatch";
175
+ readonly CHILD_DISPATCH_BEFORE: "childDispatchBefore";
176
+ readonly CHILD_DISPATCH_AFTER: "childDispatchAfter";
177
+ };
178
+ type HookName = typeof HookName[keyof typeof HookName];
179
+ //#endregion
180
+ //#region src/dispatcher/types.d.ts
181
+ interface IDispatcherEvent {
182
+ /**
183
+ * The srvx ServerRequest (extends Web Standard Request).
184
+ */
185
+ readonly request: RoutupRequest;
186
+ /**
187
+ * Route parameters extracted from the URL path pattern.
188
+ */
134
189
  params: Record<string, any>;
190
+ /**
191
+ * Current request path, adjusted relative to the mount point during router nesting.
192
+ */
135
193
  path: string;
194
+ /**
195
+ * HTTP method (GET, POST, PUT, etc.).
196
+ */
136
197
  readonly method: string;
198
+ /**
199
+ * Accumulated mount path from nested routers.
200
+ */
137
201
  mountPath: string;
202
+ /**
203
+ * Response accumulator — set status/headers before returning a plain value.
204
+ */
205
+ readonly response: RoutupResponse;
206
+ /**
207
+ * Whether a response has been produced.
208
+ */
209
+ dispatched: boolean;
210
+ /**
211
+ * Error that occurred during dispatch, if any.
212
+ */
138
213
  error?: RoutupError;
139
- routerPath: number[];
140
214
  /**
141
- * Collected allowed methods (for OPTIONS).
215
+ * Router stack for nesting tracking.
216
+ * Used internally by router options resolution.
142
217
  */
143
- methodsAllowed: string[];
144
- readonly store: Record<string | symbol, unknown>;
145
- protected _dispatched: boolean;
146
- protected _response?: RoutupResponse;
218
+ routerPath: RouterPathNode[];
147
219
  /**
148
- * Cached parsed URL (avoids double-parsing).
220
+ * Abort signal for cooperative cancellation.
221
+ *
222
+ * When a `timeout` router option is set, this signal aborts after the
223
+ * specified duration. Handlers can pass it to fetch(), streams, or other
224
+ * AbortSignal-aware APIs.
149
225
  */
150
- protected _url: InstanceType<typeof FastURL>;
151
- protected _searchParams?: URLSearchParams;
226
+ signal: AbortSignal;
152
227
  /**
153
- * Continuation function for middleware onion model.
228
+ * Collected allowed methods for the current path (used for OPTIONS / 405 responses).
154
229
  */
155
- _next?: () => Promise<Response | undefined>;
230
+ methodsAllowed: Set<string>;
156
231
  /**
157
- * Whether _next has already been called (guard against double-invocation).
232
+ * Set the continuation function for this event.
233
+ *
234
+ * Replaces the current continuation. The provided function receives
235
+ * an optional error and may return any value — it will be converted
236
+ * to a `Response` via `toResponse()`.
237
+ *
238
+ * Passing `undefined` clears the continuation function.
158
239
  */
159
- _nextCalled: boolean;
240
+ setNext(fn?: NextFn): void;
160
241
  /**
161
- * The cached result of the next handler.
242
+ * Build a public RoutupEvent from the current dispatch state.
243
+ *
244
+ * Creates a lightweight snapshot with shared references (store, response, headers)
245
+ * and pre-resolved router options. This is the event passed to handler functions.
246
+ *
247
+ * @param signal - Optional AbortSignal override. When provided, the built event
248
+ * uses this signal instead of the dispatcher event's own signal.
249
+ * Used by per-handler timeout to provide a handler-scoped signal.
162
250
  */
163
- _nextResult?: Promise<Response | undefined>;
164
- constructor(request: RoutupRequest);
165
- get headers(): Headers;
166
- get searchParams(): URLSearchParams;
167
- get response(): RoutupResponse;
168
- get dispatched(): boolean;
169
- set dispatched(value: boolean);
170
- next(): Promise<Response | undefined>;
251
+ build(signal?: AbortSignal): RoutupEvent;
171
252
  }
172
- //#endregion
173
- //#region src/dispatcher/types.d.ts
174
253
  interface IDispatcher {
175
- dispatch(event: RoutupEvent): Promise<Response | undefined>;
176
- }
177
- //#endregion
178
- //#region src/hook/constants.d.ts
179
- declare enum HookName {
180
- REQUEST = "request",
181
- RESPONSE = "response",
182
- ERROR = "error",
183
- CHILD_MATCH = "childMatch",
184
- CHILD_DISPATCH_BEFORE = "childDispatchBefore",
185
- CHILD_DISPATCH_AFTER = "childDispatchAfter"
254
+ dispatch(event: IDispatcherEvent): Promise<Response | undefined>;
186
255
  }
187
256
  //#endregion
188
257
  //#region src/hook/types.d.ts
189
- type HookErrorListener = (event: IRoutupEvent) => Promise<unknown> | unknown;
190
- type HookDefaultListener = (event: IRoutupEvent) => Promise<unknown> | unknown;
258
+ type HookErrorListener = (event: IDispatcherEvent) => Promise<unknown> | unknown;
259
+ type HookDefaultListener = (event: IDispatcherEvent) => Promise<unknown> | unknown;
191
260
  type HookListener = HookErrorListener | HookDefaultListener;
192
261
  type HookUnsubscribeFn = () => void;
193
262
  //#endregion
194
263
  //#region src/hook/module.d.ts
264
+ type HookEntry = {
265
+ fn: HookListener;
266
+ priority: number;
267
+ };
195
268
  declare class HookManager {
196
- protected items: Record<string, HookListener[]>;
269
+ protected items: Record<string, HookEntry[]>;
197
270
  constructor();
198
- addListener(name: `${HookName}`, fn: HookListener): HookUnsubscribeFn;
199
- removeListener(name: `${HookName}`): void;
200
- removeListener(name: `${HookName}`, fn: HookListener): void;
201
- trigger(name: `${HookName}`, event: IRoutupEvent): Promise<void>;
271
+ addListener(name: HookName, fn: HookListener, priority?: number): HookUnsubscribeFn;
272
+ removeListener(name: HookName): void;
273
+ removeListener(name: HookName, fn: HookListener): void;
274
+ trigger(name: HookName, event: IDispatcherEvent): Promise<void>;
202
275
  private triggerListener;
203
276
  private isErrorListenerHook;
204
277
  }
@@ -226,16 +299,25 @@ declare class PathMatcher {
226
299
  declare function isPath(input: unknown): input is Path;
227
300
  //#endregion
228
301
  //#region src/handler/constants.d.ts
229
- declare enum HandlerType {
230
- CORE = "core",
231
- ERROR = "error"
232
- }
302
+ declare const HandlerType: {
303
+ readonly CORE: "core";
304
+ readonly ERROR: "error";
305
+ };
306
+ type HandlerType = typeof HandlerType[keyof typeof HandlerType];
233
307
  declare const HandlerSymbol: unique symbol;
234
308
  //#endregion
235
309
  //#region src/handler/types-base.d.ts
236
310
  type HandlerBaseOptions = {
237
311
  method?: Uppercase<MethodName> | Lowercase<MethodName>;
238
312
  path?: Path;
313
+ /**
314
+ * Per-handler timeout in milliseconds.
315
+ *
316
+ * Overrides the router's `handlerTimeout` default. Whether this value
317
+ * can extend or only narrow the default is controlled by the router's
318
+ * `handlerTimeoutOverridable` option.
319
+ */
320
+ timeout?: number;
239
321
  onError?: HookErrorListener;
240
322
  onBefore?: HookDefaultListener;
241
323
  onAfter?: HookDefaultListener;
@@ -244,7 +326,7 @@ type HandlerBaseOptions = {
244
326
  //#region src/handler/error/types.d.ts
245
327
  type ErrorHandler = (error: RoutupError, event: IRoutupEvent) => unknown | Promise<unknown>;
246
328
  type ErrorHandlerOptions = HandlerBaseOptions & {
247
- type: `${HandlerType.ERROR}`;
329
+ type: typeof HandlerType.ERROR;
248
330
  fn: ErrorHandler;
249
331
  };
250
332
  //#endregion
@@ -280,18 +362,20 @@ declare class Handler implements IDispatcher {
280
362
  get type(): "error" | "core";
281
363
  get path(): string | undefined;
282
364
  get method(): MethodName | undefined;
283
- dispatch(event: RoutupEvent): Promise<Response | undefined>;
365
+ dispatch(event: IDispatcherEvent): Promise<Response | undefined>;
284
366
  matchPath(path: string): boolean;
285
367
  setPath(path?: Path): void;
286
- matchMethod(method: `${MethodName}`): boolean;
287
- setMethod(input?: `${MethodName}`): void;
368
+ matchMethod(method: MethodName): boolean;
369
+ setMethod(input?: MethodName): void;
370
+ protected executeWithTimeout(fn: () => unknown | Promise<unknown>, routerOptions: RouterOptions, controller?: AbortController): Promise<unknown>;
371
+ protected resolveTimeout(routerOptions: RouterOptions): number | undefined;
288
372
  protected mountHooks(): void;
289
373
  }
290
374
  //#endregion
291
375
  //#region src/handler/core/types.d.ts
292
376
  type CoreHandler = (event: IRoutupEvent) => unknown | Promise<unknown>;
293
377
  type CoreHandlerOptions = HandlerBaseOptions & {
294
- type: `${HandlerType.CORE}`;
378
+ type: typeof HandlerType.CORE;
295
379
  fn: CoreHandler;
296
380
  };
297
381
  //#endregion
@@ -394,28 +478,142 @@ declare function isWebHandler(input: unknown): input is WebHandler;
394
478
  declare function isHandlerOptions(input: unknown): input is HandlerOptions;
395
479
  declare function isHandler(input: unknown): input is Handler;
396
480
  //#endregion
397
- //#region src/router/constants.d.ts
398
- declare enum RouterPipelineStep {
399
- START = 0,
400
- LOOKUP = 1,
401
- CHILD_BEFORE = 2,
402
- CHILD_DISPATCH = 3,
403
- CHILD_AFTER = 4,
404
- FINISH = 5
481
+ //#region src/plugin/error/constants.d.ts
482
+ declare const PluginErrorCode: {
483
+ readonly PLUGIN: "PLUGIN";
484
+ readonly NOT_INSTALLED: "PLUGIN_NOT_INSTALLED";
485
+ readonly INSTALL: "PLUGIN_INSTALL";
486
+ };
487
+ type PluginErrorCode = typeof PluginErrorCode[keyof typeof PluginErrorCode];
488
+ //#endregion
489
+ //#region src/plugin/error/module.d.ts
490
+ declare class PluginError extends RoutupError {
491
+ constructor(input?: HTTPErrorInput);
405
492
  }
406
493
  //#endregion
494
+ //#region src/plugin/error/is.d.ts
495
+ declare function isPluginError(input: unknown): input is PluginError;
496
+ //#endregion
497
+ //#region src/plugin/error/sub/install.d.ts
498
+ declare class PluginInstallError extends PluginError {
499
+ readonly pluginName: string;
500
+ constructor(pluginName: string, cause?: Error);
501
+ }
502
+ //#endregion
503
+ //#region src/plugin/error/sub/not-installed.d.ts
504
+ declare class PluginNotInstalledError extends PluginError {
505
+ readonly pluginName: string;
506
+ readonly helperName: string;
507
+ constructor(pluginName: string, helperName: string);
508
+ }
509
+ //#endregion
510
+ //#region src/plugin/types.d.ts
511
+ type PluginInstallFn = (router: IRouter) => any;
512
+ type Plugin = {
513
+ /**
514
+ * The name of the plugin.
515
+ */
516
+ name: string;
517
+ /**
518
+ * The installation function called on registration.
519
+ */
520
+ install: PluginInstallFn;
521
+ };
522
+ type PluginInstallContext = {
523
+ /**
524
+ * The name property overwrites the name defined by the plugin.
525
+ */
526
+ name?: string;
527
+ /**
528
+ * By specifying a path, the plugin will be installed as a child router.
529
+ */
530
+ path?: Path;
531
+ };
532
+ //#endregion
533
+ //#region src/plugin/is.d.ts
534
+ declare function isPlugin(input: unknown): input is Plugin;
535
+ //#endregion
536
+ //#region src/utils/etag/type.d.ts
537
+ type EtagOptions = {
538
+ /**
539
+ * Create a weak ETag?
540
+ * Output is prefixed with: /W
541
+ */
542
+ weak?: boolean;
543
+ /**
544
+ * Threshold of bytes from which an etag is generated.
545
+ *
546
+ * default: undefined
547
+ */
548
+ threshold?: number;
549
+ };
550
+ type EtagFn = (body: any, encoding?: BufferEncoding, size?: number) => Promise<string | undefined>;
551
+ type EtagInput = boolean | EtagOptions | EtagFn;
552
+ //#endregion
553
+ //#region src/utils/trust-proxy/type.d.ts
554
+ type TrustProxyFn = (address: string, hop: number) => boolean;
555
+ type TrustProxyInput = boolean | number | string | string[] | TrustProxyFn;
556
+ //#endregion
557
+ //#region src/router/constants.d.ts
558
+ declare const RouterPipelineStep: {
559
+ readonly START: 0;
560
+ readonly LOOKUP: 1;
561
+ readonly CHILD_BEFORE: 2;
562
+ readonly CHILD_DISPATCH: 3;
563
+ readonly CHILD_AFTER: 4;
564
+ readonly FINISH: 5;
565
+ };
566
+ type RouterPipelineStep = typeof RouterPipelineStep[keyof typeof RouterPipelineStep];
567
+ //#endregion
407
568
  //#region src/router/types.d.ts
569
+ type RouterOptions = {
570
+ path?: Path;
571
+ name?: string;
572
+ /**
573
+ * Global request timeout in milliseconds.
574
+ *
575
+ * Applies to the entire dispatch pipeline in `fetch()`. When exceeded,
576
+ * the request is aborted and a 408 response is returned. The AbortSignal
577
+ * on the event is also aborted for cooperative cancellation.
578
+ */
579
+ timeout?: number;
580
+ /**
581
+ * Default per-handler timeout in milliseconds.
582
+ *
583
+ * Applies individually to each handler's `fn()` execution. Handlers can
584
+ * override this value via their own `timeout` option — see
585
+ * `handlerTimeoutOverridable` to control whether overrides can extend
586
+ * or only narrow this default.
587
+ */
588
+ handlerTimeout?: number;
589
+ /**
590
+ * Whether handlers can extend the `handlerTimeout` default.
591
+ *
592
+ * When `false` (default), a handler's `timeout` is clamped to
593
+ * `Math.min(handlerTimeout, handler.timeout)`. When `true`, the
594
+ * handler's `timeout` fully replaces the router default.
595
+ */
596
+ handlerTimeoutOverridable?: boolean;
597
+ subdomainOffset: number;
598
+ proxyIpMax: number;
599
+ etag: EtagFn;
600
+ trustProxy: TrustProxyFn;
601
+ };
602
+ type RouterOptionsInput = Omit<Partial<RouterOptions>, 'etag' | 'trustProxy'> & {
603
+ etag?: EtagInput;
604
+ trustProxy?: TrustProxyInput;
605
+ };
606
+ type RouterPathNode = {
607
+ readonly name?: string;
608
+ readonly options: Partial<RouterOptions>;
609
+ };
408
610
  type RouterPipelineContext = {
409
611
  step: RouterPipelineStep;
410
- event: RoutupEvent;
612
+ event: IDispatcherEvent;
411
613
  stackIndex: number;
412
614
  response?: Response;
413
615
  };
414
616
  interface IRouter extends IDispatcher {
415
- /**
416
- * Unique identifier for this router instance.
417
- */
418
- readonly id: number;
419
617
  /**
420
618
  * Optional label for the router instance.
421
619
  */
@@ -467,111 +665,66 @@ interface IRouter extends IDispatcher {
467
665
  /**
468
666
  * Add a hook listener.
469
667
  */
470
- on(name: `${HookName.REQUEST}` | `${HookName.RESPONSE}` | `${HookName.CHILD_DISPATCH_BEFORE}` | `${HookName.CHILD_DISPATCH_AFTER}`, fn: HookDefaultListener): HookUnsubscribeFn;
471
- on(name: `${HookName.CHILD_MATCH}`, fn: HookDefaultListener): HookUnsubscribeFn;
472
- on(name: `${HookName.ERROR}`, fn: HookErrorListener): HookUnsubscribeFn;
668
+ on(name: typeof HookName.REQUEST | typeof HookName.RESPONSE | typeof HookName.CHILD_DISPATCH_BEFORE | typeof HookName.CHILD_DISPATCH_AFTER, fn: HookDefaultListener): HookUnsubscribeFn;
669
+ on(name: typeof HookName.CHILD_MATCH, fn: HookDefaultListener): HookUnsubscribeFn;
670
+ on(name: typeof HookName.ERROR, fn: HookErrorListener): HookUnsubscribeFn;
473
671
  /**
474
672
  * Remove a specific or all hook listeners for the given hook name.
475
673
  */
476
- off(name: `${HookName}`): this;
477
- off(name: `${HookName}`, fn: HookListener): this;
674
+ off(name: HookName): this;
675
+ off(name: HookName, fn: HookListener): this;
478
676
  }
479
677
  //#endregion
480
- //#region src/plugin/types.d.ts
481
- type PluginInstallFn = (router: IRouter) => any;
482
- type Plugin = {
483
- /**
484
- * The name of the plugin.
485
- */
486
- name: string;
487
- /**
488
- * The installation function called on registration.
489
- */
490
- install: PluginInstallFn;
491
- };
492
- type PluginInstallContext = {
493
- /**
494
- * The name property overwrites the name defined by the plugin.
495
- */
496
- name?: string;
497
- /**
498
- * By specifying a path, the plugin will be installed as a child router.
499
- */
500
- path?: Path;
501
- };
502
- //#endregion
503
- //#region src/plugin/is.d.ts
504
- declare function isPlugin(input: unknown): input is Plugin;
505
- //#endregion
506
- //#region src/utils/etag/type.d.ts
507
- type EtagOptions = {
508
- /**
509
- * Create a weak ETag?
510
- * Output is prefixed with: /W
511
- */
512
- weak?: boolean;
513
- /**
514
- * Threshold of bytes from which an etag is generated.
515
- *
516
- * default: undefined
517
- */
518
- threshold?: number;
519
- };
520
- type EtagFn = (body: any, encoding?: BufferEncoding, size?: number) => Promise<string | undefined>;
521
- type EtagInput = boolean | EtagOptions | EtagFn;
522
- //#endregion
523
- //#region src/utils/trust-proxy/type.d.ts
524
- type TrustProxyFn = (address: string, hop: number) => boolean;
525
- type TrustProxyInput = boolean | number | string | string[] | TrustProxyFn;
526
- //#endregion
527
- //#region src/router-options/type.d.ts
528
- type RouterOptions = {
529
- /**
530
- * The path the router is mounted on.
531
- *
532
- * @type string
533
- * @default '/'
534
- */
535
- path?: Path;
536
- /**
537
- * Name of the router.
538
- */
539
- name?: string;
540
- /**
541
- * default: 2
542
- */
543
- subdomainOffset: number;
678
+ //#region src/dispatcher/module.d.ts
679
+ declare class DispatcherEvent implements IDispatcherEvent {
680
+ readonly request: RoutupRequest;
681
+ params: Record<string, any>;
682
+ path: string;
683
+ readonly method: string;
544
684
  /**
545
- * default: 0
685
+ * Collected allowed methods (for OPTIONS).
546
686
  */
547
- proxyIpMax: number;
687
+ methodsAllowed: Set<string>;
688
+ mountPath: string;
689
+ error?: RoutupError;
690
+ routerPath: RouterPathNode[];
691
+ protected _dispatched: boolean;
692
+ protected _response?: RoutupResponse;
693
+ protected _store?: Record<string | symbol, unknown>;
548
694
  /**
549
- * default: () => true
695
+ * Cached parsed URL (avoids double-parsing).
550
696
  */
551
- etag: EtagFn;
697
+ protected _url: InstanceType<typeof FastURL>;
552
698
  /**
553
- * default: () => false
699
+ * Continuation function for middleware onion model.
554
700
  */
555
- trustProxy: TrustProxyFn;
556
- };
557
- type RouterOptionsInput = Omit<Partial<RouterOptions>, 'etag' | 'trustProxy'> & {
701
+ protected _next?: (event: IRoutupEvent, error?: Error) => Promise<Response | undefined>;
702
+ protected _signal?: AbortSignal;
703
+ protected _signalCleanup?: () => void;
558
704
  /**
559
- * default: true
705
+ * Whether _next has already been called (guard against double-invocation).
560
706
  */
561
- etag?: EtagInput;
707
+ protected _nextCalled: boolean;
562
708
  /**
563
- * default: false
709
+ * The cached result of the next handler.
564
710
  */
565
- trustProxy?: TrustProxyInput;
566
- };
711
+ protected _nextResult?: Promise<Response | undefined>;
712
+ constructor(request: RoutupRequest);
713
+ get response(): RoutupResponse;
714
+ get signal(): AbortSignal;
715
+ set signal(value: AbortSignal);
716
+ get dispatched(): boolean;
717
+ set dispatched(value: boolean);
718
+ protected next(event: IRoutupEvent, error?: Error): Promise<Response | undefined>;
719
+ setNext(fn?: NextFn): void;
720
+ build(signal?: AbortSignal): RoutupEvent;
721
+ protected get store(): Record<string | symbol, unknown>;
722
+ protected resolveOptions(): RouterOptions;
723
+ }
567
724
  //#endregion
568
725
  //#region src/router/module.d.ts
569
726
  declare class Router implements IRouter {
570
727
  readonly '@instanceof': symbol;
571
- /**
572
- * An identifier for the router instance.
573
- */
574
- readonly id: number;
575
728
  /**
576
729
  * A label for the router instance.
577
730
  */
@@ -594,15 +747,19 @@ declare class Router implements IRouter {
594
747
  * @protected
595
748
  */
596
749
  protected hookManager: HookManager;
597
- constructor(options?: RouterOptionsInput);
750
+ /**
751
+ * Normalized options for this router instance.
752
+ */
753
+ protected _options: Partial<RouterOptions>;
754
+ constructor(input?: RouterOptionsInput);
598
755
  matchPath(path: string): boolean;
599
756
  setPath(value?: Path): void;
600
757
  /**
601
- * Public entry point — creates a RoutupEvent from the request,
758
+ * Public entry point — creates a DispatcherEvent from the request,
602
759
  * runs the pipeline, and returns a Response (with 404/500 fallbacks).
603
760
  */
604
761
  fetch(request: RoutupRequest): Promise<Response>;
605
- protected buildFallbackResponse(request: RoutupRequest, event: RoutupEvent, status: number, message: string): Response;
762
+ protected buildFallbackResponse(request: RoutupRequest, event: IDispatcherEvent, status: number, message: string): Response;
606
763
  protected executePipelineStep(context: RouterPipelineContext): Promise<void>;
607
764
  protected executePipelineStepStart(context: RouterPipelineContext): Promise<void>;
608
765
  protected executePipelineStepLookup(context: RouterPipelineContext): Promise<void>;
@@ -610,7 +767,7 @@ declare class Router implements IRouter {
610
767
  protected executePipelineStepChildAfter(context: RouterPipelineContext): Promise<void>;
611
768
  protected executePipelineStepChildDispatch(context: RouterPipelineContext): Promise<void>;
612
769
  protected executePipelineStepFinish(context: RouterPipelineContext): Promise<void>;
613
- dispatch(event: RoutupEvent): Promise<Response | undefined>;
770
+ dispatch(event: IDispatcherEvent): Promise<Response | undefined>;
614
771
  delete(...handlers: (Handler | HandlerOptions)[]): this;
615
772
  delete(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
616
773
  get(...handlers: (Handler | HandlerOptions)[]): this;
@@ -639,16 +796,16 @@ declare class Router implements IRouter {
639
796
  * @param name
640
797
  * @param fn
641
798
  */
642
- on(name: `${HookName.REQUEST}` | `${HookName.RESPONSE}` | `${HookName.CHILD_DISPATCH_BEFORE}` | `${HookName.CHILD_DISPATCH_AFTER}`, fn: HookDefaultListener): HookUnsubscribeFn;
643
- on(name: `${HookName.CHILD_MATCH}`, fn: HookDefaultListener): HookUnsubscribeFn;
644
- on(name: `${HookName.ERROR}`, fn: HookErrorListener): HookUnsubscribeFn;
799
+ on(name: typeof HookName.REQUEST | typeof HookName.RESPONSE | typeof HookName.CHILD_DISPATCH_BEFORE | typeof HookName.CHILD_DISPATCH_AFTER, fn: HookDefaultListener, priority?: number): HookUnsubscribeFn;
800
+ on(name: typeof HookName.CHILD_MATCH, fn: HookDefaultListener, priority?: number): HookUnsubscribeFn;
801
+ on(name: typeof HookName.ERROR, fn: HookErrorListener, priority?: number): HookUnsubscribeFn;
645
802
  /**
646
803
  * Remove single or all hook listeners.
647
804
  *
648
805
  * @param name
649
806
  */
650
- off(name: `${HookName}`): this;
651
- off(name: `${HookName}`, fn: HookListener): this;
807
+ off(name: HookName): this;
808
+ off(name: HookName, fn: HookListener): this;
652
809
  }
653
810
  //#endregion
654
811
  //#region src/error/create.d.ts
@@ -657,7 +814,7 @@ declare class Router implements IRouter {
657
814
  * - an existing RoutupError (returned as-is)
658
815
  * - an HTTPError (wrapped into a RoutupError preserving status)
659
816
  * - an Error (wrapped preserving message and cause)
660
- * - an options object (statusCode, statusMessage, etc.)
817
+ * - an options object (status, message, etc.)
661
818
  * - a message string
662
819
  *
663
820
  * @param input
@@ -706,7 +863,7 @@ type EventStreamOptions = {
706
863
  maxMessageSize?: number;
707
864
  };
708
865
  type EventStreamHandle = {
709
- write(message: string | EventStreamMessage): void;
866
+ write(message: string | EventStreamMessage): boolean;
710
867
  end(): void;
711
868
  response: Response;
712
869
  };
@@ -715,10 +872,6 @@ declare function createEventStream(event: IRoutupEvent, options?: EventStreamOpt
715
872
  //#region src/response/helpers/event-stream/utils.d.ts
716
873
  declare function serializeEventStreamMessage(message: EventStreamMessage): string;
717
874
  //#endregion
718
- //#region src/response/helpers/gone.d.ts
719
- declare function isResponseGone(event: IRoutupEvent): boolean;
720
- declare function setResponseGone(event: IRoutupEvent): void;
721
- //#endregion
722
875
  //#region src/response/helpers/header.d.ts
723
876
  declare function appendResponseHeader(event: IRoutupEvent, name: string, value: string | string[]): void;
724
877
  declare function appendResponseHeaderDirective(event: IRoutupEvent, name: string, value: string | string[]): void;
@@ -773,23 +926,6 @@ declare function setResponseContentTypeByFileName(event: IRoutupEvent, fileName:
773
926
  //#region src/response/to-response.d.ts
774
927
  declare function toResponse(value: unknown, event: IRoutupEvent): Promise<Response | undefined>;
775
928
  //#endregion
776
- //#region src/request/helpers/body.d.ts
777
- /**
778
- * Read and parse the request body.
779
- *
780
- * - `application/x-www-form-urlencoded` → plain object (duplicate keys become arrays)
781
- * - `application/json` or other → attempts JSON parse, returns undefined on failure
782
- *
783
- * The result is cached on the event store — calling `readBody()` multiple
784
- * times returns the same parsed result.
785
- *
786
- * For binary or streaming access, use `event.request.arrayBuffer()`,
787
- * `event.request.blob()`, or `event.request.body` directly.
788
- *
789
- * @experimental
790
- */
791
- declare function readBody<T = unknown>(event: IRoutupEvent): Promise<T | undefined>;
792
- //#endregion
793
929
  //#region src/request/helpers/cache.d.ts
794
930
  declare function isRequestCacheable(event: IRoutupEvent, modifiedTime: string | Date): boolean;
795
931
  //#endregion
@@ -844,5 +980,8 @@ type RequestProtocolOptions = {
844
980
  };
845
981
  declare function getRequestProtocol(event: IRoutupEvent, options?: RequestProtocolOptions): string;
846
982
  //#endregion
847
- export { RouterPipelineContext as $, sendAccepted as A, RoutupRequest as At, createEventStream as B, sendRedirect as C, PathMatcher as Ct, SendFileStats as D, IDispatcher as Dt, SendFileOptions as E, PathMatcherOptions as Et, isResponseGone as F, MethodName as Ft, isError as G, EventStreamMessage as H, setResponseGone as I, isPlugin as J, createError as K, serializeEventStreamMessage as L, setResponseHeaderAttachment as M, HTTPErrorInput$1 as Mt, appendResponseHeader as N, RoutupError as Nt, sendFile as O, RoutupEvent as Ot, appendResponseHeaderDirective as P, HeaderName as Pt, IRouter as Q, EventStreamHandle as R, sendStream as S, isPath as St, SendFileContentOptions as T, PathMatcherExecResult as Tt, ResponseCacheHeadersOptions as U, EventStreamListener as V, setResponseCacheHeaders as W, PluginInstallContext as X, Plugin as Y, PluginInstallFn as Z, getRequestHeader as _, ErrorHandler as _t, getRequestIP as a, WebHandler as at, toResponse as b, HandlerSymbol as bt, matchRequestContentType as c, fromNodeMiddleware as ct, getRequestAcceptableEncoding as d, defineCoreHandler as dt, isHandler as et, getRequestAcceptableEncodings as f, CoreHandler as ft, getRequestAcceptableContentTypes as g, defineErrorHandler as gt, getRequestAcceptableContentType as h, HandlerOptions as ht, RequestIpOptions as i, fromWebHandler as it, setResponseHeaderContentType as j, RoutupResponse as jt, sendCreated as k, IRoutupEvent as kt, getRequestAcceptableLanguage as l, NodeHandler as lt, getRequestAcceptableCharsets as m, Handler as mt, getRequestProtocol as n, isWebHandler as nt, RequestHostNameOptions as o, WebHandlerProvider as ot, getRequestAcceptableCharset as p, CoreHandlerOptions as pt, Router as q, useRequestNegotiator as r, isWebHandlerProvider as rt, getRequestHostName as s, fromNodeHandler as st, RequestProtocolOptions as t, isHandlerOptions as tt, getRequestAcceptableLanguages as u, NodeMiddleware as ut, isRequestCacheable as v, ErrorHandlerOptions as vt, sendFormat as w, Path as wt, setResponseContentTypeByFileName as x, HandlerType as xt, readBody as y, HandlerBaseOptions as yt, EventStreamOptions as z };
848
- //# sourceMappingURL=index-BBvBVzPm.d.mts.map
983
+ //#region src/router/options.d.ts
984
+ declare function normalizeRouterOptions(input: RouterOptionsInput): Partial<RouterOptions>;
985
+ //#endregion
986
+ export { Plugin as $, sendAccepted as A, PathMatcher as At, EventStreamMessage as B, RoutupRequest as Bt, sendRedirect as C, defineErrorHandler as Ct, SendFileStats as D, HandlerSymbol as Dt, SendFileOptions as E, HandlerBaseOptions as Et, serializeEventStreamMessage as F, IDispatcherEvent as Ft, Router as G, HeaderName as Gt, setResponseCacheHeaders as H, ErrorSymbol as Ht, EventStreamHandle as I, RoutupEvent as It, RouterOptions as J, DispatcherEvent as K, MethodName as Kt, EventStreamOptions as L, RoutupEventCreateContext as Lt, setResponseHeaderAttachment as M, PathMatcherExecResult as Mt, appendResponseHeader as N, PathMatcherOptions as Nt, sendFile as O, HandlerType as Ot, appendResponseHeaderDirective as P, IDispatcher as Pt, isPlugin as Q, createEventStream as R, IRoutupEvent as Rt, sendStream as S, HandlerOptions as St, SendFileContentOptions as T, ErrorHandlerOptions as Tt, isError as U, HTTPErrorInput$1 as Ut, ResponseCacheHeadersOptions as V, RoutupResponse as Vt, createError as W, RoutupError as Wt, RouterPathNode as X, RouterOptionsInput as Y, RouterPipelineContext as Z, getRequestAcceptableContentTypes as _, NodeMiddleware as _t, RequestIpOptions as a, PluginError as at, toResponse as b, CoreHandlerOptions as bt, getRequestHostName as c, isHandlerOptions as ct, getRequestAcceptableLanguages as d, fromWebHandler as dt, PluginInstallContext as et, getRequestAcceptableEncoding as f, WebHandler as ft, getRequestAcceptableContentType as g, NodeHandler as gt, getRequestAcceptableCharsets as h, fromNodeMiddleware as ht, useRequestNegotiator as i, isPluginError as it, setResponseHeaderContentType as j, Path as jt, sendCreated as k, isPath as kt, matchRequestContentType as l, isWebHandler as lt, getRequestAcceptableCharset as m, fromNodeHandler as mt, RequestProtocolOptions as n, PluginNotInstalledError as nt, getRequestIP as o, PluginErrorCode as ot, getRequestAcceptableEncodings as p, WebHandlerProvider as pt, IRouter as q, getRequestProtocol as r, PluginInstallError as rt, RequestHostNameOptions as s, isHandler as st, normalizeRouterOptions as t, PluginInstallFn as tt, getRequestAcceptableLanguage as u, isWebHandlerProvider as ut, getRequestHeader as v, defineCoreHandler as vt, sendFormat as w, ErrorHandler as wt, setResponseContentTypeByFileName as x, Handler as xt, isRequestCacheable as y, CoreHandler as yt, EventStreamListener as z, NextFn as zt };
987
+ //# sourceMappingURL=index-doDmTYzg.d.mts.map