routup 6.0.0-beta.0 → 6.0.0-beta.2

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.
@@ -97,7 +97,11 @@ interface IAppEvent {
97
97
  */
98
98
  readonly method: MethodNameLike;
99
99
  /**
100
- * Accumulated mount path from nested routers.
100
+ * Prefix the active route was matched on (the substring of the
101
+ * request path the matcher consumed). Set per dispatched handler
102
+ * and restored when the handler returns; useful for static-asset
103
+ * / mount-aware helpers that need to strip this off `path` to
104
+ * recover a mount-relative path.
101
105
  */
102
106
  readonly mountPath: string;
103
107
  /**
@@ -234,7 +238,12 @@ interface IDispatcherEvent {
234
238
  */
235
239
  readonly method: MethodNameLike;
236
240
  /**
237
- * Accumulated mount path from nested routers.
241
+ * Prefix the active route was matched on. Set per dispatched
242
+ * handler to the resolver's `match.path` (the substring of the
243
+ * request path the matcher consumed) and restored to the prior
244
+ * value when the handler returns. Static-asset / mount-aware
245
+ * helpers strip this off `event.path` to recover a mount-relative
246
+ * path.
238
247
  */
239
248
  mountPath: string;
240
249
  /**
@@ -251,16 +260,17 @@ interface IDispatcherEvent {
251
260
  error?: AppError;
252
261
  /**
253
262
  * 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.
263
+ * entry to `App.dispatch`; restored on exit so that re-entering
264
+ * `App.dispatch` for the same event (programmatic use of the
265
+ * `IDispatcher` interface) leaves the caller's view intact.
256
266
  */
257
267
  appOptions: Readonly<AppOptions>;
258
268
  /**
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.
269
+ * `true` while an `App.dispatch` call is on the stack for this
270
+ * event. Used by `App.dispatch` to derive whether the current
271
+ * call is the root (and so should drive root-only behaviour like
272
+ * OPTIONS auto-Allow synthesis). Saved/restored around the
273
+ * dispatch body so re-entrant calls behave correctly.
264
274
  */
265
275
  isDispatching: boolean;
266
276
  /**
@@ -315,16 +325,17 @@ declare class DispatcherEvent implements IDispatcherEvent {
315
325
  error?: AppError;
316
326
  /**
317
327
  * 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.
328
+ * entry to `App.dispatch` and restored on exit so re-entrant
329
+ * dispatch calls leave the caller's view intact. Initialized to
330
+ * `{}` so consumers reading before any dispatch get a valid
331
+ * (empty) shape.
321
332
  */
322
333
  appOptions: Readonly<AppOptions>;
323
334
  /**
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.
335
+ * `true` while an `App.dispatch` call is on the stack for this
336
+ * event. `App.dispatch` reads this on entry to derive `isRoot`
337
+ * and writes it on entry/exit so re-entrant calls behave
338
+ * correctly.
328
339
  */
329
340
  isDispatching: boolean;
330
341
  protected _dispatched: boolean;
@@ -360,54 +371,6 @@ declare class DispatcherEvent implements IDispatcherEvent {
360
371
  protected get store(): Record<string | symbol, unknown>;
361
372
  }
362
373
  //#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
374
  //#region src/handler/constants.d.ts
412
375
  declare const HandlerType: {
413
376
  readonly CORE: "core";
@@ -416,6 +379,22 @@ declare const HandlerType: {
416
379
  type HandlerType = typeof HandlerType[keyof typeof HandlerType];
417
380
  declare const HandlerSymbol: unique symbol;
418
381
  //#endregion
382
+ //#region src/error/create.d.ts
383
+ /**
384
+ * Create an internal error object by
385
+ * - an existing AppError (returned as-is)
386
+ * - an HTTPError (wrapped into a AppError preserving status)
387
+ * - an Error (wrapped preserving message and cause)
388
+ * - an options object (status, message, etc.)
389
+ * - a message string
390
+ *
391
+ * @param input
392
+ */
393
+ declare function createError(input: HTTPErrorInput | unknown): AppError;
394
+ //#endregion
395
+ //#region src/error/is.d.ts
396
+ declare function isError(input: unknown): input is AppError;
397
+ //#endregion
419
398
  //#region src/path/type.d.ts
420
399
  type PathMatcherOptions = PathToRegexpOptions & ParseOptions;
421
400
  type PathMatcherExecResult = {
@@ -443,6 +422,31 @@ declare class PathMatcher implements IPathMatcher {
443
422
  declare function isPath(input: unknown): input is Path;
444
423
  //#endregion
445
424
  //#region src/handler/types-base.d.ts
425
+ /**
426
+ * Side-effect callback fired before the handler's `fn` is invoked.
427
+ * Receives the same `AppEvent` the handler will see. Throwing here is
428
+ * equivalent to the handler throwing — `onError` (if set) fires next
429
+ * and the error propagates to the surrounding error chain. Return
430
+ * value is ignored; if you need to short-circuit, use middleware.
431
+ */
432
+ type HandlerBeforeListener = (event: IAppEvent) => unknown | Promise<unknown>;
433
+ /**
434
+ * Side-effect callback fired after the handler's `fn` returns and
435
+ * `toResponse` builds the final response. Receives the same
436
+ * `AppEvent` `fn` saw plus the produced `Response` (`undefined` when
437
+ * the handler did not produce a response). Throwing here is treated
438
+ * like the handler throwing — `onError` (if set) fires next and the
439
+ * already-built response is dropped in favour of the error path.
440
+ */
441
+ type HandlerAfterListener = (event: IAppEvent, response: Response | undefined) => unknown | Promise<unknown>;
442
+ /**
443
+ * Side-effect callback fired when the handler's `fn`, `onBefore`, or
444
+ * `onAfter` throws. Receives the resolved `AppError` and the
445
+ * handler's event. Throwing here replaces `event.error` with the
446
+ * new error before the pipeline observes it; returning normally
447
+ * lets the original error propagate.
448
+ */
449
+ type HandlerErrorListener = (error: AppError, event: IAppEvent) => unknown | Promise<unknown>;
446
450
  type HandlerBaseOptions = {
447
451
  method?: Uppercase<MethodName> | Lowercase<MethodName>;
448
452
  path?: Path;
@@ -454,9 +458,24 @@ type HandlerBaseOptions = {
454
458
  * `handlerTimeoutOverridable` option.
455
459
  */
456
460
  timeout?: number;
457
- onError?: HookErrorListener;
458
- onBefore?: HookDefaultListener;
459
- onAfter?: HookDefaultListener;
461
+ /**
462
+ * Instrumentation hook fired before `fn` is invoked. Plain
463
+ * optional callback — no event-name dispatch, no priorities;
464
+ * for cross-handler instrumentation, prefer middleware.
465
+ */
466
+ onBefore?: HandlerBeforeListener;
467
+ /**
468
+ * Instrumentation hook fired after the response is built (or
469
+ * the handler resolved without one). Receives `(event, response)`.
470
+ */
471
+ onAfter?: HandlerAfterListener;
472
+ /**
473
+ * Instrumentation hook fired when `fn` (or `onBefore`) throws.
474
+ * Receives `(error, event)`. Re-throwing replaces the active
475
+ * `event.error`; returning normally lets the original error
476
+ * propagate.
477
+ */
478
+ onError?: HandlerErrorListener;
460
479
  };
461
480
  //#endregion
462
481
  //#region src/handler/error/types.d.ts
@@ -490,7 +509,6 @@ type HandlerOptions = CoreHandlerOptions | ErrorHandlerOptions;
490
509
  //#region src/handler/module.d.ts
491
510
  declare class Handler implements IDispatcher {
492
511
  protected config: HandlerOptions;
493
- protected hooks: IHooks;
494
512
  readonly method: MethodName | undefined;
495
513
  constructor(handler: HandlerOptions);
496
514
  get type(): "error" | "core";
@@ -510,7 +528,6 @@ declare class Handler implements IDispatcher {
510
528
  protected resolveHandlerResult(invocation: unknown | Promise<unknown>, handlerEvent: IAppEvent): Promise<unknown>;
511
529
  protected executeWithTimeout(fn: () => unknown | Promise<unknown>, effectiveTimeout: number | undefined, controller?: AbortController): Promise<unknown>;
512
530
  protected resolveTimeout(appOptions: AppOptions): number | undefined;
513
- protected mountHooks(): void;
514
531
  }
515
532
  //#endregion
516
533
  //#region src/handler/core/types.d.ts
@@ -683,7 +700,11 @@ type Plugin = {
683
700
  };
684
701
  type PluginInstallContext = {
685
702
  /**
686
- * By specifying a path, the plugin will be installed as a child router.
703
+ * Mount-path prefix to prepend onto every route the plugin
704
+ * registers. Equivalent to passing the same prefix to
705
+ * `app.use(path, plugin)`. The plugin installs into a scratch
706
+ * `App`; that scratch is then flattened into the host App with
707
+ * this prefix joined onto each route.
687
708
  */
688
709
  path?: Path;
689
710
  };
@@ -712,22 +733,6 @@ type EtagInput = boolean | null | EtagOptions | EtagFn;
712
733
  type TrustProxyFn = (address: string, hop: number) => boolean;
713
734
  type TrustProxyInput = boolean | number | string | string[] | TrustProxyFn;
714
735
  //#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
736
  //#region src/cache/types.d.ts
732
737
  /**
733
738
  * Pluggable cache strategy used by `IRouter` implementations to
@@ -764,14 +769,6 @@ interface ICache<V> {
764
769
  * cached against an earlier route set.
765
770
  */
766
771
  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
772
  }
776
773
  //#endregion
777
774
  //#region src/cache/lru.d.ts
@@ -793,14 +790,12 @@ type LruCacheOptions = {
793
790
  * `BaseRouterOptions.cache` slot.
794
791
  */
795
792
  declare class LruCache<V> implements ICache<V> {
796
- protected options: LruCacheOptions;
797
793
  protected inner: QuickLRU<string, V>;
798
794
  constructor(options?: LruCacheOptions);
799
795
  get(key: string): V | undefined;
800
796
  set(key: string, value: V): void;
801
797
  delete(key: string): void;
802
798
  clear(): void;
803
- clone(): ICache<V>;
804
799
  }
805
800
  //#endregion
806
801
  //#region src/types.d.ts
@@ -858,8 +853,7 @@ type RouteMatch<T extends ObjectLiteral = ObjectLiteral> = {
858
853
  route: Route<T>;
859
854
  /**
860
855
  * 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.
856
+ * `setNext` continuation ("resume from index + 1").
863
857
  */
864
858
  index: number;
865
859
  /**
@@ -944,15 +938,6 @@ interface IRouter<T extends ObjectLiteral = ObjectLiteral> {
944
938
  * implementation.
945
939
  */
946
940
  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
941
  }
957
942
  //#endregion
958
943
  //#region src/app/types.d.ts
@@ -1039,11 +1024,11 @@ type AppOptionsInput = Omit<AppOptions, 'etag' | 'trustProxy'> & {
1039
1024
  *
1040
1025
  * Splits true runtime options (which propagate to mounted children
1041
1026
  * 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.
1027
+ * `path`) and the `router` injectable. Keeping these separate
1028
+ * prevents identity from leaking across the mount boundary — e.g. a
1029
+ * parent's `path: '/api'` would otherwise propagate into a child
1030
+ * whose own `path` is unset and silently double-prefix on
1031
+ * registration.
1047
1032
  */
1048
1033
  type AppContext = {
1049
1034
  /**
@@ -1071,113 +1056,12 @@ type AppContext = {
1071
1056
  * mount-time inheritance.
1072
1057
  */
1073
1058
  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
1059
  /**
1087
1060
  * Pluggable router (route table). Defaults to `LinearRouter` —
1088
1061
  * walks registered entries linearly per request. Swap in an
1089
1062
  * alternative (e.g. `TrieRouter`) on apps with many routes.
1090
1063
  */
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;
1064
+ router?: IRouter<Handler>;
1181
1065
  };
1182
1066
  interface IApp extends IDispatcher {
1183
1067
  /**
@@ -1189,16 +1073,6 @@ interface IApp extends IDispatcher {
1189
1073
  * and returns a Response (with 404/500 fallbacks).
1190
1074
  */
1191
1075
  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
1076
  /**
1203
1077
  * Swap the active `IRouter`. Every previously-registered route
1204
1078
  * is replayed onto the new router so lookups stay correct. Any
@@ -1209,24 +1083,31 @@ interface IApp extends IDispatcher {
1209
1083
  * comparing implementations mid-flight without rebuilding the
1210
1084
  * App.
1211
1085
  */
1212
- setRouter(router: IRouter<RouteEntry>): void;
1086
+ setRouter(router: IRouter<Handler>): void;
1213
1087
  /**
1214
- * Check if a plugin with the given name is installed on this router.
1088
+ * Check if a plugin with the given name is installed on this
1089
+ * App. Plugins installed on a mounted child are merged into the
1090
+ * parent at mount time, so this reflects the flattened view.
1215
1091
  */
1216
1092
  hasPlugin(name: string): boolean;
1217
1093
  /**
1218
- * Get the version of an installed plugin by name on this router,
1219
- * or `undefined` if the plugin is not installed here.
1094
+ * Get the version of an installed plugin by name, or `undefined`
1095
+ * if the plugin is not installed.
1220
1096
  */
1221
1097
  getPluginVersion(name: string): string | undefined;
1222
1098
  /**
1223
- * Register a handler, router, or plugin.
1224
- * When a path is provided, the item is mounted at that path.
1099
+ * Register a handler, App, or plugin.
1100
+ *
1101
+ * When another App is passed, its routes are snapshotted, the
1102
+ * mount path is prefixed onto each, and the entries are
1103
+ * registered on this App's router. The child's plugin registry
1104
+ * is merged into this one. The child is discarded post-mount —
1105
+ * later mutations on it do **not** propagate.
1225
1106
  */
1226
- use(router: IApp): this;
1107
+ use(app: IApp): this;
1227
1108
  use(handler: Handler | HandlerOptions): this;
1228
1109
  use(plugin: Plugin): this;
1229
- use(path: Path, router: IApp): this;
1110
+ use(path: Path, app: IApp): this;
1230
1111
  use(path: Path, handler: Handler | HandlerOptions): this;
1231
1112
  use(path: Path, plugin: Plugin): this;
1232
1113
  /** Register GET handler(s). */
@@ -1250,35 +1131,8 @@ interface IApp extends IDispatcher {
1250
1131
  /** Register OPTIONS handler(s). */
1251
1132
  options(...handlers: (Handler | HandlerOptions)[]): this;
1252
1133
  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
1134
  }
1265
1135
  //#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
1136
  //#region src/response/helpers/cache.d.ts
1283
1137
  type ResponseCacheHeadersOptions = {
1284
1138
  maxAge?: number;
@@ -1493,7 +1347,6 @@ declare class LinearRouter<T extends ObjectLiteral = ObjectLiteral> implements I
1493
1347
  constructor(options?: BaseRouterOptions<T>);
1494
1348
  add(route: Route<T>): void;
1495
1349
  lookup(path: string, _method?: MethodNameLike): readonly RouteMatch<T>[];
1496
- clone(): IRouter<T>;
1497
1350
  }
1498
1351
  //#endregion
1499
1352
  //#region src/router/smart/module.d.ts
@@ -1510,7 +1363,7 @@ type SmartRouterOptions<T extends ObjectLiteral = ObjectLiteral> = BaseRouterOpt
1510
1363
  * buffer; on the first `lookup()` call, picks `LinearRouter` or
1511
1364
  * `TrieRouter` based on the registered route count and replays the
1512
1365
  * pending list onto the chosen inner router. Every subsequent call
1513
- * — `add`, `lookup`, `clone` — forwards to the inner.
1366
+ * — `add`, `lookup` — forwards to the inner.
1514
1367
  *
1515
1368
  * Use this when you don't want to commit to a router family up-front
1516
1369
  * (e.g. a library that registers a variable number of routes
@@ -1534,7 +1387,6 @@ declare class SmartRouter<T extends ObjectLiteral = ObjectLiteral> implements IR
1534
1387
  constructor(options?: SmartRouterOptions<T>);
1535
1388
  add(route: Route<T>): void;
1536
1389
  lookup(path: string, method?: string): readonly RouteMatch<T>[];
1537
- clone(): IRouter<T>;
1538
1390
  /**
1539
1391
  * Pick the inner router based on the registered route count.
1540
1392
  * `LinearRouter` for tiny tables, `TrieRouter` past the
@@ -1716,7 +1568,6 @@ declare class TrieRouter<T extends ObjectLiteral = ObjectLiteral> implements IRo
1716
1568
  constructor(options?: BaseRouterOptions<T>);
1717
1569
  add(route: Route<T>): void;
1718
1570
  lookup(path: string, method?: MethodNameLike): readonly RouteMatch<T>[];
1719
- clone(): IRouter<T>;
1720
1571
  /**
1721
1572
  * T1: returns the pre-computed candidate list when the request's
1722
1573
  * static spine has no param sibling, no prefix routes, and no
@@ -1763,7 +1614,7 @@ declare function buildRoutePathMatcher<T extends ObjectLiteral = ObjectLiteral>(
1763
1614
  //#region src/app/module.d.ts
1764
1615
  declare class App implements IApp {
1765
1616
  /**
1766
- * A label for the router instance.
1617
+ * A label for the App instance.
1767
1618
  */
1768
1619
  readonly name?: string;
1769
1620
  /**
@@ -1781,52 +1632,55 @@ declare class App implements IApp {
1781
1632
  *
1782
1633
  * @protected
1783
1634
  */
1784
- protected router: IRouter<RouteEntry>;
1785
- /**
1786
- * Lifecycle hook registry.
1787
- *
1788
- * @protected
1789
- */
1790
- protected hooks: IHooks;
1635
+ protected router: IRouter<Handler>;
1791
1636
  /**
1792
1637
  * Normalized options for this App instance.
1793
1638
  *
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.
1639
+ * Frozen on construction once published to `event.appOptions`
1640
+ * it is shared across all requests, and a handler must not be
1641
+ * able to mutate router-global state.
1800
1642
  */
1801
1643
  protected _options: Readonly<AppOptions>;
1802
1644
  /**
1803
- * Registry of installed plugins (name → version) on this router.
1645
+ * Registry of installed plugins (name → version) on this App.
1646
+ *
1647
+ * Read by `use(otherApp)` (via the public `plugins` getter) so
1648
+ * plugin registries merge into the parent at flatten time —
1649
+ * `parent.hasPlugin('foo')` then reflects plugins installed on
1650
+ * apps mounted into it.
1804
1651
  *
1805
1652
  * @protected
1806
1653
  */
1807
- protected plugins: Map<string, string | undefined>;
1654
+ protected _plugins: Map<string, string | undefined>;
1808
1655
  /**
1809
1656
  * Every route registered on this App, in registration order.
1810
1657
  *
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
1658
+ * Read by `use(otherApp)` to snapshot routes at flatten time.
1659
+ * Late mutations to `_routes` after a flatten do not propagate.
1818
1660
  */
1819
- protected _routes: Route<RouteEntry>[];
1661
+ protected _routes: Route<Handler>[];
1820
1662
  constructor(input?: AppContext);
1663
+ /**
1664
+ * Public read of the canonical route list. Used by `use(child)`
1665
+ * to snapshot the child's routes at flatten time. Returned
1666
+ * as `readonly` — callers must not mutate.
1667
+ */
1668
+ get routes(): readonly Route<Handler>[];
1669
+ /**
1670
+ * Public read of the installed-plugin registry. Used by
1671
+ * `use(child)` to merge child plugins into the parent at
1672
+ * flatten time. Returned as `ReadonlyMap` — callers must not
1673
+ * mutate; go through `use(plugin)` to install.
1674
+ */
1675
+ get plugins(): ReadonlyMap<string, string | undefined>;
1821
1676
  /**
1822
1677
  * 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.
1678
+ * App so `setRouter` / `use(child)` can read the canonical list
1679
+ * back.
1826
1680
  *
1827
1681
  * @protected
1828
1682
  */
1829
- protected register(route: Route<RouteEntry>): void;
1683
+ protected register(route: Route<Handler>): void;
1830
1684
  /**
1831
1685
  * Swap the active router. Replays every previously-registered
1832
1686
  * route onto the new router so lookups stay correct.
@@ -1836,46 +1690,20 @@ declare class App implements IApp {
1836
1690
  * mid-flight without rebuilding the App. Any cache the previous
1837
1691
  * router carried is dropped along with it.
1838
1692
  */
1839
- setRouter(router: IRouter<RouteEntry>): void;
1693
+ setRouter(router: IRouter<Handler>): void;
1840
1694
  /**
1841
1695
  * Public entry point — creates a DispatcherEvent from the request,
1842
1696
  * runs the pipeline, and returns a Response (with 404/500 fallbacks).
1843
1697
  */
1844
1698
  fetch(request: AppRequest): Promise<Response>;
1845
1699
  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
1700
  dispatch(event: IDispatcherEvent): Promise<Response | undefined>;
1701
+ /**
1702
+ * Walk the matched routes for the current event, dispatching each
1703
+ * handler in order. Re-entered (recursively) from the `setNext`
1704
+ * continuation so `event.next()` resumes from the next match.
1705
+ */
1706
+ protected runMatches(event: IDispatcherEvent, matches: readonly RouteMatch<Handler>[], matchesPath: string, startIndex: number): Promise<Response | undefined>;
1879
1707
  delete(...handlers: (Handler | HandlerOptions)[]): this;
1880
1708
  delete(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1881
1709
  get(...handlers: (Handler | HandlerOptions)[]): this;
@@ -1891,60 +1719,37 @@ declare class App implements IApp {
1891
1719
  options(...handlers: (Handler | HandlerOptions)[]): this;
1892
1720
  options(path: Path, ...handlers: (Handler | HandlerOptions)[]): this;
1893
1721
  protected useForMethod(method: MethodName, ...input: (Path | Handler | HandlerOptions)[]): void;
1894
- use(router: IApp): this;
1722
+ use(app: IApp): this;
1895
1723
  use(handler: Handler | HandlerOptions): this;
1896
1724
  use(plugin: Plugin): this;
1897
- use(path: Path, router: IApp): this;
1725
+ use(path: Path, app: IApp): this;
1898
1726
  use(path: Path, handler: Handler | HandlerOptions): this;
1899
1727
  use(path: Path, plugin: Plugin): this;
1900
1728
  /**
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
1729
+ * Snapshot a child App's routes and plugin registry into this
1730
+ * one. Each route's path is prefixed with `this._path`, the
1731
+ * supplied mount `path`, and the route's own path (in that
1732
+ * order); the resulting entry is registered on this App's
1733
+ * router. The child app is not retained late mutations on it
1734
+ * after this call do not propagate.
1921
1735
  *
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.
1736
+ * @protected
1925
1737
  */
1926
- clone(): IApp;
1738
+ protected flatten(child: App, path: Path | undefined): void;
1927
1739
  /**
1928
- * Add a hook listener.
1929
- *
1930
- * @param name
1931
- * @param fn
1932
- * @param priority
1740
+ * Check if a plugin with the given name is installed on this App.
1933
1741
  */
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;
1742
+ hasPlugin(name: string): boolean;
1937
1743
  /**
1938
- * Remove single or all hook listeners.
1939
- *
1940
- * @param name
1744
+ * Get the version of an installed plugin by name, or `undefined`
1745
+ * if the plugin is not installed.
1941
1746
  */
1942
- off(name: HookName): this;
1943
- off(name: HookName, fn: HookListener): this;
1747
+ getPluginVersion(name: string): string | undefined;
1748
+ protected install(plugin: Plugin, context?: PluginInstallContext): this;
1944
1749
  }
1945
1750
  //#endregion
1946
1751
  //#region src/app/options.d.ts
1947
1752
  declare function normalizeAppOptions(input: AppOptionsInput): AppOptions;
1948
1753
  //#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
1754
+ export { AppOptions as $, HandlerType as $t, sendFormat as A, NodeHandler as At, setResponseHeaderAttachment as B, HandlerAfterListener as Bt, getRequestAcceptableContentTypes as C, isWebHandler as Ct, setResponseContentTypeByFileName as D, WebHandlerProvider as Dt, toResponse as E, WebHandler as Et, SendFileStats as F, Handler as Ft, EventStreamHandle as G, PathMatcher as Gt, appendResponseHeader as H, HandlerBeforeListener as Ht, sendFile as I, HandlerOptions as It, EventStreamListener as J, PathMatcherExecResult as Jt, EventStreamOptions as K, IPathMatcher as Kt, sendCreated as L, defineErrorHandler as Lt, SendFileContentOptions as M, defineCoreHandler as Mt, SendFileDisposition as N, CoreHandler as Nt, sendStream as O, fromNodeHandler as Ot, SendFileOptions as P, CoreHandlerOptions as Pt, AppContext as Q, HandlerSymbol as Qt, sendAccepted as R, ErrorHandler as Rt, getRequestAcceptableContentType as S, isHandlerOptions as St, isRequestCacheable as T, fromWebHandler as Tt, appendResponseHeaderDirective as U, HandlerErrorListener as Ut, setResponseHeaderInline as V, HandlerBaseOptions as Vt, serializeEventStreamMessage as W, isPath as Wt, ResponseCacheHeadersOptions as X, isError as Xt, EventStreamMessage as Y, PathMatcherOptions as Yt, setResponseCacheHeaders as Z, createError as Zt, getRequestAcceptableLanguages as _, isPluginError as _t, SmartRouter as a, AppRequest as an, Route as at, getRequestAcceptableCharset as b, matchHandlerMethod as bt, RequestProtocolOptions as c, NextFn as cn, LruCacheOptions as ct, RequestIpOptions as d, MethodNameLike as dn, Plugin as dt, DispatcherEvent as en, AppOptionsInput as et, getRequestIP as f, AppError as fn, PluginInstallContext as ft, getRequestAcceptableLanguage as g, PluginAlreadyInstalledError as gt, matchRequestContentType as h, PluginInstallError as ht, TrieRouter as i, AppEventCreateContext as in, ObjectLiteral as it, SendFileContent as j, NodeMiddleware as jt, sendRedirect as k, fromNodeMiddleware as kt, getRequestProtocol as l, HeaderName as ln, ICache as lt, getRequestHostName as m, HTTPErrorInput$1 as mn, PluginNotInstalledError as mt, App as n, IDispatcherEvent as nn, BaseRouterOptions as nt, SmartRouterOptions as o, AppResponse as on, RouteMatch as ot, RequestHostNameOptions as p, ErrorSymbol as pn, PluginInstallFn as pt, createEventStream as q, Path as qt, buildRoutePathMatcher as r, AppEvent as rn, IRouter as rt, LinearRouter as s, IAppEvent as sn, LruCache as st, normalizeAppOptions as t, IDispatcher as tn, IApp as tt, useRequestNegotiator as u, MethodName as un, isPlugin as ut, getRequestAcceptableEncoding as v, PluginError as vt, getRequestHeader as w, isWebHandlerProvider as wt, getRequestAcceptableCharsets as x, isHandler as xt, getRequestAcceptableEncodings as y, PluginErrorCode as yt, setResponseHeaderContentType as z, ErrorHandlerOptions as zt };
1755
+ //# sourceMappingURL=index-6qjxyFZh.d.mts.map