@orkestrel/router 0.0.1 → 0.0.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.
- package/dist/src/browser/index.d.ts +283 -4
- package/dist/src/browser/index.js +4 -300
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +893 -0
- package/dist/src/core/index.cjs.map +1 -0
- package/dist/src/core/index.d.cts +1067 -0
- package/dist/src/core/index.d.ts +1067 -8
- package/dist/src/core/index.js +33 -175
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +4 -210
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/{helpers.d.ts → index.d.cts} +166 -127
- package/dist/src/server/index.d.ts +166 -2
- package/dist/src/server/index.js +207 -0
- package/dist/src/server/index.js.map +1 -0
- package/package.json +32 -19
- package/dist/src/browser/Navigator.d.ts +0 -62
- package/dist/src/browser/factories.d.ts +0 -33
- package/dist/src/browser/helpers.d.ts +0 -76
- package/dist/src/browser/types.d.ts +0 -101
- package/dist/src/core/DispatchGroup.d.ts +0 -32
- package/dist/src/core/Dispatcher.d.ts +0 -51
- package/dist/src/core/Group.d.ts +0 -30
- package/dist/src/core/Router.d.ts +0 -42
- package/dist/src/core/constants.d.ts +0 -60
- package/dist/src/core/factories.d.ts +0 -53
- package/dist/src/core/helpers.d.ts +0 -274
- package/dist/src/core/types.d.ts +0 -445
- package/dist/src/server/types.d.ts +0 -31
package/dist/src/core/types.d.ts
DELETED
|
@@ -1,445 +0,0 @@
|
|
|
1
|
-
import type { EmitterErrorHandler, EmitterHooks, EmitterInterface } from '@orkestrel/emitter';
|
|
2
|
-
/**
|
|
3
|
-
* The identifier START characters an identifier-grammar param name may
|
|
4
|
-
* begin with — mirrors the runtime classifier's `[A-Za-z_]` head class
|
|
5
|
-
* (`classifySegment` / `compilePath`, `helpers.ts`).
|
|
6
|
-
*/
|
|
7
|
-
export type IdentifierStartChar = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '_';
|
|
8
|
-
/**
|
|
9
|
-
* The identifier CONTINUATION characters after the first — mirrors the
|
|
10
|
-
* runtime classifier's `[A-Za-z0-9_]*` tail class.
|
|
11
|
-
*/
|
|
12
|
-
export type IdentifierChar = IdentifierStartChar | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
13
|
-
export type TakeIdentifierTail<S extends string, Acc extends string> = S extends `${infer Head}${infer Tail}` ? Head extends IdentifierChar ? TakeIdentifierTail<Tail, `${Acc}${Head}`> : Acc : Acc;
|
|
14
|
-
export type IdentifierHead<S extends string> = S extends `${infer Head}${infer Tail}` ? Head extends IdentifierStartChar ? TakeIdentifierTail<Tail, Head> : '' : '';
|
|
15
|
-
export type SegmentParam<Segment extends string> = Segment extends `:${infer Rest}` ? IdentifierHead<Rest> extends infer Name extends string ? Name extends '' ? unknown : {
|
|
16
|
-
readonly [K in Name]: string;
|
|
17
|
-
} : unknown : Segment extends `*${infer Rest}` ? IdentifierHead<Rest> extends infer Name extends string ? Name extends '' ? unknown : {
|
|
18
|
-
readonly [K in Name]: string;
|
|
19
|
-
} : unknown : unknown;
|
|
20
|
-
/**
|
|
21
|
-
* Recursive, unflattened param extraction for {@link PathParams} — walks a
|
|
22
|
-
* path pattern segment by segment (split on `/`), extracting each segment's
|
|
23
|
-
* {@link SegmentParam} contribution and intersecting the rest.
|
|
24
|
-
*
|
|
25
|
-
* @typeParam Path - The path pattern literal being decomposed
|
|
26
|
-
*
|
|
27
|
-
* @remarks
|
|
28
|
-
* Splits `Path` at its first `/` into `Segment` + `Rest`, intersects
|
|
29
|
-
* `SegmentParam<Segment>` with the recursive walk of `Rest`, and — once no
|
|
30
|
-
* further `/` remains — resolves the final segment's own `SegmentParam`
|
|
31
|
-
* directly. `SegmentParam` mirrors the runtime `classifySegment` grammar
|
|
32
|
-
* exactly: a `:name` HEAD (identifier-char run, stopping at the first
|
|
33
|
-
* non-identifier char) captures a param; a segment whose `:` is not at the
|
|
34
|
-
* segment START (`a:b`) contributes nothing (the classification fix, §4); a
|
|
35
|
-
* final `*name` wildcard captures the rest-of-path param the same way. A
|
|
36
|
-
* fully parameterless `Path` resolves to `unknown` here (the intersection
|
|
37
|
-
* identity every non-capturing segment contributes) — {@link PathParams}
|
|
38
|
-
* flattens that down to a clean empty record. This type is exported so
|
|
39
|
-
* {@link PathParams} (which flattens it into a clean mapped type for IDE
|
|
40
|
-
* hovers) has a documented, testable recursive step; consumers reach for the
|
|
41
|
-
* flattened {@link PathParams} form, never this raw recursion.
|
|
42
|
-
*/
|
|
43
|
-
export type PathParamsRaw<Path extends string> = string extends Path ? Readonly<Record<string, string>> : Path extends `${infer Segment}/${infer Rest}` ? SegmentParam<Segment> & PathParamsRaw<Rest> : SegmentParam<Path>;
|
|
44
|
-
/**
|
|
45
|
-
* Extracts `{ name: string }` param records from a path pattern at the type
|
|
46
|
-
* level — the typed half of the path grammar (§4).
|
|
47
|
-
*
|
|
48
|
-
* @typeParam Path - A route path pattern literal (`/users/:id/posts/:slug`,
|
|
49
|
-
* `/files/*rest`, or a parameterless literal path)
|
|
50
|
-
*
|
|
51
|
-
* @remarks
|
|
52
|
-
* A `:name` segment contributes `{ name: string }`; a trailing `*name`
|
|
53
|
-
* wildcard (the grammar's only allowed wildcard position) contributes
|
|
54
|
-
* `{ name: string }` capturing the rest of the path; a parameterless pattern
|
|
55
|
-
* resolves to an empty record. Built over {@link PathParamsRaw} and flattened
|
|
56
|
-
* through an identity-mapped type so editor hovers show the resolved shape
|
|
57
|
-
* (`{ id: string; slug: string }`) rather than an unresolved intersection.
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* ```ts
|
|
61
|
-
* type A = PathParams<'/users/:id/posts/:slug'> // { readonly id: string; readonly slug: string }
|
|
62
|
-
* type B = PathParams<'/files/*rest'> // { readonly rest: string }
|
|
63
|
-
* type C = PathParams<'/health'> // Record<string, never>
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
export type PathParams<Path extends string> = {
|
|
67
|
-
readonly [K in keyof PathParamsRaw<Path>]: PathParamsRaw<Path>[K];
|
|
68
|
-
};
|
|
69
|
-
/**
|
|
70
|
-
* A compiled route path — the anchored regex plus its ordered param names.
|
|
71
|
-
*
|
|
72
|
-
* @remarks
|
|
73
|
-
* The once-per-path compile output of `compilePath` (U1 `helpers.ts`):
|
|
74
|
-
* `regex` is anchored (`^…$`) and matches the WHOLE pathname (with an
|
|
75
|
-
* optional trailing slash, §4), and `params` lists each captured segment's
|
|
76
|
-
* name (`:name` or the final `*name`) in order, so a `regex.exec` result's
|
|
77
|
-
* capture groups line up with `params` positionally (the walk `matchPath`
|
|
78
|
-
* performs). Plain data — no behavior.
|
|
79
|
-
*/
|
|
80
|
-
export interface CompiledPath {
|
|
81
|
-
readonly regex: RegExp;
|
|
82
|
-
readonly params: readonly string[];
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* One registered route in a {@link RouterInterface} — the `path` pattern plus
|
|
86
|
-
* the opaque `meta` payload to return on a match, with an optional `name`.
|
|
87
|
-
*
|
|
88
|
-
* @typeParam Meta - The payload to carry on a match (opaque to the engine —
|
|
89
|
-
* a route handler + method on the `Dispatcher`, a component/loader
|
|
90
|
-
* reference on the `Navigator`)
|
|
91
|
-
*
|
|
92
|
-
* @remarks
|
|
93
|
-
* - `path` — the `/`-prefixed route path pattern (`/users/:id`); compiled
|
|
94
|
-
* once at registration.
|
|
95
|
-
* - `meta` — the opaque payload returned (as {@link RouterMatch.meta}) when
|
|
96
|
-
* this entry is the most-specific match. The engine never inspects it —
|
|
97
|
-
* the consumer's {@link AnswerHandler} predicate (the override seam)
|
|
98
|
-
* decides eligibility from it.
|
|
99
|
-
* - `name` — an optional route identifier, carried through onto a match for
|
|
100
|
-
* consumers that build named links or debug output.
|
|
101
|
-
*/
|
|
102
|
-
export interface RouteEntry<Meta> {
|
|
103
|
-
readonly path: string;
|
|
104
|
-
readonly meta: Meta;
|
|
105
|
-
readonly name?: string;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* One matched route — the winning entry's PATTERN, decoded params, `meta`
|
|
109
|
-
* payload, and optional `name`.
|
|
110
|
-
*
|
|
111
|
-
* @typeParam Meta - The payload the winning entry carries
|
|
112
|
-
*
|
|
113
|
-
* @remarks
|
|
114
|
-
* What {@link RouterInterface.match} returns on a hit (`undefined` on a
|
|
115
|
-
* miss). `path` is the winning entry's REGISTERED PATTERN (not the concrete
|
|
116
|
-
* pathname that was matched) — useful for consumers that need to know which
|
|
117
|
-
* route fired. `params` is a frozen `name → value` record (empty for a
|
|
118
|
-
* parameterless path), each value URL-decoded with a malformed `%` escape
|
|
119
|
-
* tolerated as a literal (§4, never throws). Plain data — no behavior.
|
|
120
|
-
*/
|
|
121
|
-
export interface RouterMatch<Meta> {
|
|
122
|
-
readonly path: string;
|
|
123
|
-
readonly params: Readonly<Record<string, string>>;
|
|
124
|
-
readonly meta: Meta;
|
|
125
|
-
readonly name?: string;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* The native-override seam — a predicate deciding whether an entry's `meta`
|
|
129
|
-
* ANSWERS a given `match` call, beyond path matching.
|
|
130
|
-
*
|
|
131
|
-
* @typeParam Meta - The entry payload the predicate reads
|
|
132
|
-
*
|
|
133
|
-
* @remarks
|
|
134
|
-
* The single seam the philosophy's "one engine, native overrides" principle
|
|
135
|
-
* hangs on: the `Dispatcher` passes a method-check (`(record) => record.method
|
|
136
|
-
* === requestMethod`), the `Navigator` omits the predicate entirely (every
|
|
137
|
-
* path match always answers). Passed per-call to {@link RouterInterface.match};
|
|
138
|
-
* when omitted, every entry whose path matches is eligible. Total — it never
|
|
139
|
-
* throws (a consumer keeps it pure, per AGENTS §14 guard totality).
|
|
140
|
-
*/
|
|
141
|
-
export type AnswerHandler<Meta> = (meta: Meta) => boolean;
|
|
142
|
-
/**
|
|
143
|
-
* Options for `createRouter` — an optional initial entry set, the case-
|
|
144
|
-
* sensitivity toggle, and the dedup identity function.
|
|
145
|
-
*
|
|
146
|
-
* @typeParam Meta - The entry payload type
|
|
147
|
-
*
|
|
148
|
-
* @remarks
|
|
149
|
-
* - `entries` — the initial `{ path, meta, name? }` entries to register (each
|
|
150
|
-
* path compiled once), equivalent to a bare `createRouter()` followed by
|
|
151
|
-
* `add(entries)`. Omitted ⇒ an empty router.
|
|
152
|
-
* - `sensitive` — case-sensitive path matching (default `true`, §4). Set
|
|
153
|
-
* `false` to fold case during matching (`/Users` matches `/users`);
|
|
154
|
-
* registered patterns are never case-folded in storage, only in matching.
|
|
155
|
-
* - `key` — an optional dedup identity function computed per entry
|
|
156
|
-
* (`RouteEntry<Meta> → string`). When provided, registering an entry whose
|
|
157
|
-
* key already exists REPLACES the prior entry in place (last write wins,
|
|
158
|
-
* no engine rebuild) instead of adding a second candidate. Omitted ⇒ every
|
|
159
|
-
* registered entry is kept, even duplicate paths.
|
|
160
|
-
*/
|
|
161
|
-
export interface RouterOptions<Meta> {
|
|
162
|
-
readonly entries?: readonly RouteEntry<Meta>[];
|
|
163
|
-
readonly sensitive?: boolean;
|
|
164
|
-
readonly key?: (entry: RouteEntry<Meta>) => string;
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* The path-matching + registry engine contract (the §4.5 behavioral-interface
|
|
168
|
-
* role for the one-class-per-file `Router`). Registers `{ path, meta, name? }`
|
|
169
|
-
* entries (compiling each path once) and resolves a concrete pathname to the
|
|
170
|
-
* MOST SPECIFIC matching entry — a literal segment beats a param beats a
|
|
171
|
-
* wildcard at the earliest differing segment, registration-order-independent
|
|
172
|
-
* (§4). The shared engine both the `Navigator` (browser) and the `Dispatcher`
|
|
173
|
-
* (core, method-dimensioned) compose.
|
|
174
|
-
*
|
|
175
|
-
* @typeParam Meta - The opaque payload each entry carries and a match returns
|
|
176
|
-
*
|
|
177
|
-
* @remarks
|
|
178
|
-
* - `count` — the number of registered entries.
|
|
179
|
-
* - `add(entry)` / `add(entries)` — register ONE / MANY entries (§9.2 batch);
|
|
180
|
-
* each path is compiled once here. When constructed with a `key` option,
|
|
181
|
-
* an entry whose key already exists replaces the prior one in place;
|
|
182
|
-
* otherwise every entry is kept, even duplicate paths.
|
|
183
|
-
* - `match(pathname, answers?)` — the MOST-SPECIFIC matching entry as a
|
|
184
|
-
* {@link RouterMatch} (its winning `path`, decoded `params`, `meta`, and
|
|
185
|
-
* `name`), or `undefined`. The optional {@link AnswerHandler} predicate
|
|
186
|
-
* filters candidates by `meta` first; omitted ⇒ every path match is
|
|
187
|
-
* eligible.
|
|
188
|
-
* - `entries()` — ALL registered entries in registration order.
|
|
189
|
-
* - `entries(pathname)` — only entries whose path matches `pathname` (the
|
|
190
|
-
* §9 plural accessor's filtered form; backs a consumer's allow/405 set).
|
|
191
|
-
* - `group(prefix)` — a {@link GroupInterface} scoped under `prefix`; entries
|
|
192
|
-
* added through the group are registered on this same router with `prefix`
|
|
193
|
-
* prepended to each path.
|
|
194
|
-
* - `clear()` — drop every entry (§10), leaving the router reusable.
|
|
195
|
-
*/
|
|
196
|
-
export interface RouterInterface<Meta> {
|
|
197
|
-
readonly count: number;
|
|
198
|
-
add(entry: RouteEntry<Meta>): void;
|
|
199
|
-
add(entries: readonly RouteEntry<Meta>[]): void;
|
|
200
|
-
match(pathname: string, answers?: AnswerHandler<Meta>): RouterMatch<Meta> | undefined;
|
|
201
|
-
entries(): readonly RouteEntry<Meta>[];
|
|
202
|
-
entries(pathname: string): readonly RouteEntry<Meta>[];
|
|
203
|
-
group(prefix: string): GroupInterface<Meta>;
|
|
204
|
-
clear(): void;
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* A prefix-scoped registration handle over a {@link RouterInterface} — pure
|
|
208
|
-
* string composition (§4.2.2), no independent state or storage.
|
|
209
|
-
*
|
|
210
|
-
* @typeParam Meta - The entry payload type, matching the owning router
|
|
211
|
-
*
|
|
212
|
-
* @remarks
|
|
213
|
-
* - `prefix` — the path prefix this group prepends to every entry it
|
|
214
|
-
* registers (and to every nested group's own prefix).
|
|
215
|
-
* - `add(entry)` / `add(entries)` — register ONE / MANY entries on the
|
|
216
|
-
* OWNING router, each entry's `path` composed as `prefix + entry.path`
|
|
217
|
-
* (§9.2 batch, mirroring {@link RouterInterface.add}).
|
|
218
|
-
* - `group(prefix)` — a nested group whose prefix is `this.prefix + prefix`;
|
|
219
|
-
* nesting composes prefixes left to right with no depth limit.
|
|
220
|
-
*/
|
|
221
|
-
export interface GroupInterface<Meta> {
|
|
222
|
-
readonly prefix: string;
|
|
223
|
-
add(entry: RouteEntry<Meta>): void;
|
|
224
|
-
add(entries: readonly RouteEntry<Meta>[]): void;
|
|
225
|
-
group(prefix: string): GroupInterface<Meta>;
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* The seven HTTP methods a {@link DispatcherInterface} dimensions dispatch
|
|
229
|
-
* over — the value-level counterpart is {@link import('./constants.js').METHODS}.
|
|
230
|
-
*
|
|
231
|
-
* @remarks
|
|
232
|
-
* `HEAD` is a valid explicit registration even though a `GET` route already
|
|
233
|
-
* auto-answers `HEAD` (§5.1 dispatch semantics) — an explicit `HEAD` handler
|
|
234
|
-
* always takes precedence over the derived one.
|
|
235
|
-
*/
|
|
236
|
-
export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
237
|
-
/**
|
|
238
|
-
* The ambient context a {@link RouteHandler} receives alongside the raw
|
|
239
|
-
* `Request` — decoded params, the winning pattern, the parsed URL, and the
|
|
240
|
-
* consumer's opaque per-request state.
|
|
241
|
-
*
|
|
242
|
-
* @typeParam Path - The route path pattern the handler was registered under
|
|
243
|
-
* (drives the typed shape of `params` via {@link PathParams})
|
|
244
|
-
* @typeParam TState - The consumer's opaque per-request state type
|
|
245
|
-
*
|
|
246
|
-
* @remarks
|
|
247
|
-
* - `params` — the decoded param record, typed from `Path` via
|
|
248
|
-
* {@link PathParams} (empty for a parameterless path).
|
|
249
|
-
* - `pattern` — the winning REGISTERED pattern (matches
|
|
250
|
-
* {@link RouterMatch.path}), useful for logging/metrics.
|
|
251
|
-
* - `url` — the request URL, already parsed once by the dispatcher.
|
|
252
|
-
* - `state` — the consumer's per-request payload (logger, session, DI bag),
|
|
253
|
-
* threaded opaquely through `handle()` — the router never inspects it.
|
|
254
|
-
*/
|
|
255
|
-
export interface RouteContext<Path extends string = string, TState = undefined> {
|
|
256
|
-
readonly params: PathParams<Path>;
|
|
257
|
-
readonly pattern: string;
|
|
258
|
-
readonly url: URL;
|
|
259
|
-
readonly state: TState;
|
|
260
|
-
}
|
|
261
|
-
/**
|
|
262
|
-
* A route handler — receives the raw fetch `Request` plus its typed
|
|
263
|
-
* {@link RouteContext} and returns (or resolves) a fetch `Response`.
|
|
264
|
-
*
|
|
265
|
-
* @typeParam Path - The route path pattern the handler is registered under
|
|
266
|
-
* @typeParam TState - The consumer's opaque per-request state type
|
|
267
|
-
*
|
|
268
|
-
* @remarks
|
|
269
|
-
* A handler throw propagates to the caller of `dispatcher.handle` — the
|
|
270
|
-
* dispatcher never invents an error boundary; mapping throws to responses is
|
|
271
|
-
* the consuming server's policy (§5.1).
|
|
272
|
-
*/
|
|
273
|
-
export type RouteHandler<Path extends string = string, TState = undefined> = (request: Request, context: RouteContext<Path, TState>) => Response | Promise<Response>;
|
|
274
|
-
/**
|
|
275
|
-
* One route registration input for {@link DispatcherInterface.add} — the
|
|
276
|
-
* method-dimensioned counterpart of {@link RouteEntry}.
|
|
277
|
-
*
|
|
278
|
-
* @typeParam Path - The route path pattern literal (drives the typed
|
|
279
|
-
* `handler`'s `context.params` via {@link PathParams})
|
|
280
|
-
* @typeParam TState - The consumer's opaque per-request state type
|
|
281
|
-
*
|
|
282
|
-
* @remarks
|
|
283
|
-
* - `method` — the HTTP method this route answers.
|
|
284
|
-
* - `path` — the `/`-prefixed route path pattern.
|
|
285
|
-
* - `handler` — the {@link RouteHandler} invoked on a match.
|
|
286
|
-
* - `name` — an optional route identifier, carried through onto a
|
|
287
|
-
* {@link RouterMatch}.
|
|
288
|
-
*/
|
|
289
|
-
export interface RouteInput<Path extends string = string, TState = undefined> {
|
|
290
|
-
readonly method: Method;
|
|
291
|
-
readonly path: Path;
|
|
292
|
-
readonly handler: RouteHandler<Path, TState>;
|
|
293
|
-
readonly name?: string;
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* The `meta` payload a {@link DispatcherInterface} stores in its underlying
|
|
297
|
-
* `Router` — what {@link RouterInterface.match} returns as
|
|
298
|
-
* {@link RouterMatch.meta} on a dispatch hit.
|
|
299
|
-
*
|
|
300
|
-
* @typeParam TState - The consumer's opaque per-request state type
|
|
301
|
-
*
|
|
302
|
-
* @remarks
|
|
303
|
-
* The handler is typed over `string` (not the original literal `Path`) at
|
|
304
|
-
* storage — path-specific param typing is recovered at the call site through
|
|
305
|
-
* {@link RouteInput}'s generic `Path`, not preserved in the stored record.
|
|
306
|
-
*/
|
|
307
|
-
export interface RouteRecord<TState> {
|
|
308
|
-
readonly method: Method;
|
|
309
|
-
readonly handler: RouteHandler<string, TState>;
|
|
310
|
-
readonly name?: string;
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* The outcome of {@link DispatcherInterface.match} — a discriminated union
|
|
314
|
-
* over the three dispatch tiers: a full hit, a path-matches-but-method-
|
|
315
|
-
* doesn't (405 territory), or nothing matched at all (404 territory).
|
|
316
|
-
*
|
|
317
|
-
* @typeParam TState - The consumer's opaque per-request state type
|
|
318
|
-
*
|
|
319
|
-
* @remarks
|
|
320
|
-
* - `'matched'` — carries the winning {@link RouterMatch} (its `meta` is a
|
|
321
|
-
* {@link RouteRecord}).
|
|
322
|
-
* - `'unmethoded'` — the pathname matched at least one entry but none for
|
|
323
|
-
* the requested method; `allow` is the derived `Allow` method set (from
|
|
324
|
-
* `router.entries(pathname)`).
|
|
325
|
-
* - `'unmatched'` — no registered pattern matches the pathname at all.
|
|
326
|
-
*/
|
|
327
|
-
export type DispatchResult<TState> = {
|
|
328
|
-
readonly status: 'matched';
|
|
329
|
-
readonly match: RouterMatch<RouteRecord<TState>>;
|
|
330
|
-
} | {
|
|
331
|
-
readonly status: 'unmethoded';
|
|
332
|
-
readonly allow: readonly Method[];
|
|
333
|
-
} | {
|
|
334
|
-
readonly status: 'unmatched';
|
|
335
|
-
};
|
|
336
|
-
/**
|
|
337
|
-
* The `Dispatcher`'s event map (AGENTS §13) — the two dispatch-outcome
|
|
338
|
-
* signals a consumer can observe alongside the return value of `handle`.
|
|
339
|
-
*
|
|
340
|
-
* @remarks
|
|
341
|
-
* - `match` — emitted on every dispatch that resolves to a handler
|
|
342
|
-
* (including the auto-`HEAD`/auto-`OPTIONS` derived cases): the request
|
|
343
|
-
* `method` and the winning `pattern`.
|
|
344
|
-
* - `miss` — emitted on every non-matching dispatch: the RAW request
|
|
345
|
-
* `method` (a plain `string`, not narrowed to {@link Method} — an unknown
|
|
346
|
-
* verb like `PURGE` is observable here exactly as sent, never coerced),
|
|
347
|
-
* the raw `pathname`, and which tier missed (`'unmatched'` — nothing
|
|
348
|
-
* matched the path at all; `'unmethoded'` — the path matched but not the
|
|
349
|
-
* method, including an unknown verb against a path with other registered
|
|
350
|
-
* methods).
|
|
351
|
-
*/
|
|
352
|
-
export type DispatcherEventMap = {
|
|
353
|
-
readonly match: readonly [method: Method, pattern: string];
|
|
354
|
-
readonly miss: readonly [method: string, pathname: string, reason: 'unmatched' | 'unmethoded'];
|
|
355
|
-
};
|
|
356
|
-
/**
|
|
357
|
-
* Options for `createDispatcher` — initial routes, case sensitivity, the two
|
|
358
|
-
* default-responder overrides, and the AGENTS §13 emitter wiring.
|
|
359
|
-
*
|
|
360
|
-
* @typeParam TState - The consumer's opaque per-request state type
|
|
361
|
-
*
|
|
362
|
-
* @remarks
|
|
363
|
-
* - `routes` — the initial route inputs to register, equivalent to a bare
|
|
364
|
-
* `createDispatcher()` followed by `add(routes)`. Omitted ⇒ no routes.
|
|
365
|
-
* - `sensitive` — forwarded to the underlying `Router` (default `true`, §4).
|
|
366
|
-
* - `unmatched` — the responder invoked when nothing matches the pathname at
|
|
367
|
-
* all (default: a `404` `Response`).
|
|
368
|
-
* - `unmethoded` — the responder invoked when the pathname matches but not
|
|
369
|
-
* the method, given the derived `Allow` set (default: a `405` `Response`
|
|
370
|
-
* with an `Allow` header).
|
|
371
|
-
* - `on` — initial `DispatcherEventMap` listeners (AGENTS §8/§13).
|
|
372
|
-
* - `error` — the emitter's own listener-error handler (AGENTS §13),
|
|
373
|
-
* forwarded alongside `on`.
|
|
374
|
-
*/
|
|
375
|
-
export interface DispatcherOptions<TState> {
|
|
376
|
-
readonly routes?: readonly RouteInput<string, TState>[];
|
|
377
|
-
readonly sensitive?: boolean;
|
|
378
|
-
readonly unmatched?: (request: Request) => Response | Promise<Response>;
|
|
379
|
-
readonly unmethoded?: (request: Request, allow: readonly Method[]) => Response | Promise<Response>;
|
|
380
|
-
readonly on?: EmitterHooks<DispatcherEventMap>;
|
|
381
|
-
readonly error?: EmitterErrorHandler;
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* The fetch-standard, method-dimensioned dispatch entity contract (the §4.5
|
|
385
|
-
* behavioral-interface role for the one-class-per-file `Dispatcher`). Layers
|
|
386
|
-
* HTTP method dispatch and web-standard `Request`/`Response` handling over a
|
|
387
|
-
* single internal `Router<RouteRecord<TState>>`.
|
|
388
|
-
*
|
|
389
|
-
* @typeParam TState - The consumer's opaque per-request state type, threaded
|
|
390
|
-
* into every {@link RouteContext} (default `undefined` for stateless use)
|
|
391
|
-
*
|
|
392
|
-
* @remarks
|
|
393
|
-
* - `router` — the underlying registry, exposed READONLY for introspection
|
|
394
|
-
* (the same object `add`/`group`/`match` operate on).
|
|
395
|
-
* - `emitter` — the AGENTS §13 observable surface for {@link DispatcherEventMap}.
|
|
396
|
-
* - `add(input)` / `add(inputs)` — register ONE / MANY {@link RouteInput}s
|
|
397
|
-
* (§9.2 batch); throws `TypeError` on a malformed registration (a
|
|
398
|
-
* non-`/`-prefixed path, a non-function handler, or a method outside
|
|
399
|
-
* {@link import('./constants.js').METHODS}) — the construction/registration
|
|
400
|
-
* boundary guard (§14); `match`/`handle` hot paths carry zero guards.
|
|
401
|
-
* - `group(prefix)` — a {@link DispatchGroupInterface} scoped under `prefix`.
|
|
402
|
-
* - `match(method, pathname)` — the raw {@link DispatchResult} for a method +
|
|
403
|
-
* pathname pair, with no `Request`/`Response` involvement — the pure
|
|
404
|
-
* decision `handle` builds its response from.
|
|
405
|
-
* - `handle(request, state)` — the full dispatch: parses `request.url`,
|
|
406
|
-
* calls `match`, and either invokes the winning handler (auto-stripping
|
|
407
|
-
* the body for a derived `HEAD`, auto-answering a derived `OPTIONS` with
|
|
408
|
-
* the `Allow` set), or invokes the `unmatched`/`unmethoded` responder.
|
|
409
|
-
* Emits `match`/`miss` accordingly. A handler throw propagates uncaught.
|
|
410
|
-
* - `destroy()` — tears down the `#emitter` (AGENTS §13); the underlying
|
|
411
|
-
* router is left registered (not cleared) so introspection remains valid
|
|
412
|
-
* after destroy.
|
|
413
|
-
*/
|
|
414
|
-
export interface DispatcherInterface<TState = undefined> {
|
|
415
|
-
readonly router: RouterInterface<RouteRecord<TState>>;
|
|
416
|
-
readonly emitter: EmitterInterface<DispatcherEventMap>;
|
|
417
|
-
add<Path extends string>(input: RouteInput<Path, TState>): void;
|
|
418
|
-
add(inputs: readonly RouteInput<string, TState>[]): void;
|
|
419
|
-
group(prefix: string): DispatchGroupInterface<TState>;
|
|
420
|
-
match(method: Method, pathname: string): DispatchResult<TState>;
|
|
421
|
-
handle(request: Request, state: TState): Promise<Response>;
|
|
422
|
-
destroy(): void;
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* A prefix-scoped registration handle over a {@link DispatcherInterface} —
|
|
426
|
-
* the method-dimensioned counterpart of {@link GroupInterface}.
|
|
427
|
-
*
|
|
428
|
-
* @typeParam TState - The consumer's opaque per-request state type, matching
|
|
429
|
-
* the owning dispatcher
|
|
430
|
-
*
|
|
431
|
-
* @remarks
|
|
432
|
-
* - `prefix` — the path prefix this group prepends to every route it
|
|
433
|
-
* registers (and to every nested group's own prefix).
|
|
434
|
-
* - `add(input)` / `add(inputs)` — register ONE / MANY {@link RouteInput}s on
|
|
435
|
-
* the OWNING dispatcher, each input's `path` composed as
|
|
436
|
-
* `prefix + input.path` (§9.2 batch, mirroring
|
|
437
|
-
* {@link DispatcherInterface.add}).
|
|
438
|
-
* - `group(prefix)` — a nested group whose prefix is `this.prefix + prefix`.
|
|
439
|
-
*/
|
|
440
|
-
export interface DispatchGroupInterface<TState> {
|
|
441
|
-
readonly prefix: string;
|
|
442
|
-
add<Path extends string>(input: RouteInput<Path, TState>): void;
|
|
443
|
-
add(inputs: readonly RouteInput<string, TState>[]): void;
|
|
444
|
-
group(prefix: string): DispatchGroupInterface<TState>;
|
|
445
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
-
/**
|
|
3
|
-
* Options for `buildRequest` — how to derive the built `Request`'s origin.
|
|
4
|
-
*
|
|
5
|
-
* @remarks
|
|
6
|
-
* - `origin` — an explicit scheme + host to build the request URL against
|
|
7
|
-
* (`https://api.example.com`). Omitted ⇒ derived from the connection: the
|
|
8
|
-
* socket's `encrypted` presence picks `https`/`http`, and the `Host`
|
|
9
|
-
* header supplies the host (absent `Host` ⇒ `localhost`).
|
|
10
|
-
*/
|
|
11
|
-
export interface RequestOptions {
|
|
12
|
-
readonly origin?: string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* A `node:http` request handler — the function `createListener` returns,
|
|
16
|
-
* matching `http.createServer`'s handler signature.
|
|
17
|
-
*
|
|
18
|
-
* @remarks
|
|
19
|
-
* Invoked once per incoming message with the raw `IncomingMessage`/
|
|
20
|
-
* `ServerResponse` pair; never returns a value (writes the response as a
|
|
21
|
-
* side effect).
|
|
22
|
-
*/
|
|
23
|
-
export type ListenerFunction = (request: IncomingMessage, response: ServerResponse) => void;
|
|
24
|
-
/**
|
|
25
|
-
* Derives a consumer's opaque per-request `TState` from the raw
|
|
26
|
-
* `IncomingMessage` — the `state` argument `createListener` threads into
|
|
27
|
-
* `dispatcher.handle`.
|
|
28
|
-
*
|
|
29
|
-
* @typeParam TState - The consumer's opaque per-request state type
|
|
30
|
-
*/
|
|
31
|
-
export type StateFunction<TState> = (message: IncomingMessage) => TState;
|