@orkestrel/router 0.0.11 → 0.0.13
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/README.md +13 -10
- package/dist/src/browser/index.d.ts +43 -44
- package/dist/src/browser/index.js +60 -52
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +189 -117
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +945 -911
- package/dist/src/core/index.d.ts +945 -911
- package/dist/src/core/index.js +188 -117
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +25 -21
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +21 -21
- package/dist/src/server/index.d.ts +21 -21
- package/dist/src/server/index.js +25 -21
- package/dist/src/server/index.js.map +1 -1
- package/package.json +19 -15
|
@@ -3,7 +3,7 @@ import { EmitterHooks } from '@orkestrel/emitter';
|
|
|
3
3
|
import { EmitterInterface } from '@orkestrel/emitter';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Represents the native-override seam — a predicate deciding whether an entry's `meta`
|
|
7
7
|
* ANSWERS a given `match` call, beyond path matching.
|
|
8
8
|
*
|
|
9
9
|
* @typeParam Meta - The entry payload the predicate reads
|
|
@@ -14,12 +14,12 @@ import { EmitterInterface } from '@orkestrel/emitter';
|
|
|
14
14
|
* === requestMethod`), the `Navigator` omits the predicate entirely (every
|
|
15
15
|
* path match always answers). Passed per-call to {@link RouterInterface.match};
|
|
16
16
|
* when omitted, every entry whose path matches is eligible. Total — it never
|
|
17
|
-
* throws
|
|
17
|
+
* throws, so a consumer keeps it pure.
|
|
18
18
|
*/
|
|
19
19
|
export declare type AnswerHandler<Meta> = (meta: Meta) => boolean;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Canonicalizes a route path for REGISTRY IDENTITY — strips a single trailing
|
|
23
23
|
* slash, except the root `/` (and the empty pattern). The trailing-slash fold
|
|
24
24
|
* {@link compilePath} normalizes a pattern through, so identity agrees with the
|
|
25
25
|
* matcher.
|
|
@@ -45,10 +45,10 @@ export declare type AnswerHandler<Meta> = (meta: Meta) => boolean;
|
|
|
45
45
|
export declare function canonicalizePath(path: string): string;
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
|
-
*
|
|
48
|
+
* Classifies one path segment into its specificity TIER — the SAME syntax
|
|
49
49
|
* {@link compilePath} rewrites: a syntactically valid `:name` head is a PARAM
|
|
50
50
|
* segment, a final `*name` is a WILDCARD segment, everything else (including a
|
|
51
|
-
* literal segment that merely CONTAINS a `:` mid-string,
|
|
51
|
+
* literal segment that merely CONTAINS a `:` mid-string, for example `a:b`) is a
|
|
52
52
|
* LITERAL segment.
|
|
53
53
|
*
|
|
54
54
|
* @remarks
|
|
@@ -56,11 +56,12 @@ export declare function canonicalizePath(path: string): string;
|
|
|
56
56
|
* segment `includes(':')` as a param, so a literal segment like `a:b` was
|
|
57
57
|
* mis-tiered even though {@link compilePath} compiles it literally. Sharing one
|
|
58
58
|
* segment parser between compilation and classification keeps the two in
|
|
59
|
-
* agreement
|
|
59
|
+
* agreement. Pure and total.
|
|
60
60
|
*
|
|
61
61
|
* @param segment - One `/`-split path segment
|
|
62
|
-
* @param isFinal -
|
|
63
|
-
*
|
|
62
|
+
* @param isFinal - If `true`, `segment` is the path's last segment and may
|
|
63
|
+
* classify as a wildcard; if `false`, a wildcard-shaped segment classifies as
|
|
64
|
+
* a literal
|
|
64
65
|
* @returns The segment's specificity tier — {@link import('./constants.js').TIER_LITERAL},
|
|
65
66
|
* {@link import('./constants.js').TIER_PARAM}, or
|
|
66
67
|
* {@link import('./constants.js').TIER_WILDCARD}
|
|
@@ -76,7 +77,7 @@ export declare function canonicalizePath(path: string): string;
|
|
|
76
77
|
export declare function classifySegment(segment: string, isFinal: boolean): number;
|
|
77
78
|
|
|
78
79
|
/**
|
|
79
|
-
*
|
|
80
|
+
* Compares two route paths by SPECIFICITY — the comparator that picks the
|
|
80
81
|
* most-specific matching route (literal-over-param-over-wildcard,
|
|
81
82
|
* registration-order-independent).
|
|
82
83
|
*
|
|
@@ -104,12 +105,12 @@ export declare function classifySegment(segment: string, isFinal: boolean): numb
|
|
|
104
105
|
export declare function compareSpecificity(a: string, b: string): number;
|
|
105
106
|
|
|
106
107
|
/**
|
|
107
|
-
*
|
|
108
|
+
* Represents a compiled route path — the anchored regex plus its ordered param names.
|
|
108
109
|
*
|
|
109
110
|
* @remarks
|
|
110
|
-
* The once-per-path compile output of `compilePath` (
|
|
111
|
+
* The once-per-path compile output of `compilePath` (the path compiler in `helpers.ts`):
|
|
111
112
|
* `regex` is anchored (`^…$`) and matches the WHOLE pathname (with an
|
|
112
|
-
* optional trailing slash
|
|
113
|
+
* optional trailing slash), and `params` lists each captured segment's
|
|
113
114
|
* name (`:name` or the final `*name`) in order, so a `regex.exec` result's
|
|
114
115
|
* capture groups line up with `params` positionally (the walk `matchPath`
|
|
115
116
|
* performs). Plain data — no behavior.
|
|
@@ -120,7 +121,7 @@ export declare interface CompiledPath {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
/**
|
|
123
|
-
*
|
|
124
|
+
* Compiles a route path pattern into an anchored regex and its ordered param
|
|
124
125
|
* names.
|
|
125
126
|
*
|
|
126
127
|
* @remarks
|
|
@@ -128,7 +129,7 @@ export declare interface CompiledPath {
|
|
|
128
129
|
* `([^/]+)` capture group; the FINAL segment may instead be `*name`, which
|
|
129
130
|
* becomes a `(.+)` capture spanning the REST of the path including slashes — a
|
|
130
131
|
* wildcard segment anywhere but last is a registration-time programmer error
|
|
131
|
-
* and throws `
|
|
132
|
+
* and throws a `ContractError` at the construction/registration boundary. Every regex
|
|
132
133
|
* metacharacter in a literal segment is escaped first ({@link escapeRegExp}),
|
|
133
134
|
* so a path like `/files/:name.json` matches the `.` literally apart from the
|
|
134
135
|
* param. The regex is anchored (`^…$`), so it matches the whole pathname, not
|
|
@@ -146,942 +147,975 @@ export declare interface CompiledPath {
|
|
|
146
147
|
* regex flag, so `/Users` matches `/users`. The pattern's own casing is never
|
|
147
148
|
* altered — only the matching behavior.
|
|
148
149
|
*
|
|
149
|
-
* @param path - The route path pattern (
|
|
150
|
-
* @param sensitive -
|
|
150
|
+
* @param path - The route path pattern (for example `/users/:id`, `/files/*rest`)
|
|
151
|
+
* @param sensitive - If `true`, matching is case-sensitive; if `false`, case is
|
|
152
|
+
* folded during matching. Default: `true`
|
|
151
153
|
* @returns The {@link CompiledPath} — its `regex` + ordered `params`
|
|
152
|
-
* @throws {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
154
|
+
* @throws {@link import('@orkestrel/contract').ContractError} Thrown when a
|
|
155
|
+
* `*name` wildcard segment is not the FINAL segment
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* const { regex, params } = compilePath('/users/:id/posts/:slug')
|
|
160
|
+
* params // ['id', 'slug']
|
|
161
|
+
* regex.exec('/users/7/posts/hello') // ['…', '7', 'hello']
|
|
162
|
+
* regex.test('/users/7/posts/hello/') // true — the trailing slash is optional
|
|
163
|
+
*
|
|
164
|
+
* compilePath('/files/*rest').regex.test('/files/a/b.png') // true
|
|
165
|
+
* compilePath('/Users', false).regex.test('/users') // true — case-insensitive
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export declare function compilePath(path: string, sensitive?: boolean): CompiledPath;
|
|
166
169
|
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
export declare function computeDispatchKey(entry: RouteEntry<{
|
|
184
|
-
|
|
185
|
-
}>): string;
|
|
170
|
+
/**
|
|
171
|
+
* Computes the registry key for a method-dimensioned dispatcher route.
|
|
172
|
+
*
|
|
173
|
+
* @remarks
|
|
174
|
+
* Combines the route record's HTTP method with the outer entry's canonical
|
|
175
|
+
* path, so registrations that differ only by one trailing slash replace each
|
|
176
|
+
* other while registrations for different methods remain distinct.
|
|
177
|
+
*
|
|
178
|
+
* @param entry - The dispatcher route entry to identify
|
|
179
|
+
* @returns The canonical `METHOD /path` registry key
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* computeDispatchKey({ path: '/health/', meta: { method: 'GET' } }) // 'GET /health'
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export declare function computeDispatchKey(entry: RouteEntry<{
|
|
187
|
+
readonly method: Method;
|
|
188
|
+
}>): string;
|
|
186
189
|
|
|
187
|
-
/**
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
export declare function computeSpecificity(path: string): readonly number[];
|
|
190
|
+
/**
|
|
191
|
+
* Computes a route path's SPECIFICITY VECTOR — the per-segment type ranking
|
|
192
|
+
* that breaks a tie when several registered routes match the same concrete
|
|
193
|
+
* pathname.
|
|
194
|
+
*
|
|
195
|
+
* @remarks
|
|
196
|
+
* Splits the CANONICALIZED path into segments (on `/`) and maps each to its
|
|
197
|
+
* specificity tier through {@link classifySegment} — the same segment parser
|
|
198
|
+
* {@link compilePath} uses, so a literal segment that merely contains a `:`
|
|
199
|
+
* (for example `a:b`) is correctly tiered as literal rather than param (the old
|
|
200
|
+
* engine's bug, fixed here). The standard route-precedence rule compares two
|
|
201
|
+
* matching routes' vectors LEFT-TO-RIGHT: at the first index where the tiers
|
|
202
|
+
* differ, the HIGHER tier (a literal over a param over a wildcard) is MORE
|
|
203
|
+
* SPECIFIC and wins — so `/users/me` (`[2, 2]`) beats `/users/:id` (`[2, 1]`)
|
|
204
|
+
* beats `/users/*rest` (`[2, 0]`) regardless of registration order. Two routes
|
|
205
|
+
* that match the SAME concrete pathname necessarily have the same segment
|
|
206
|
+
* count in the common case; {@link compareSpecificity} handles the general
|
|
207
|
+
* case for totality.
|
|
208
|
+
*
|
|
209
|
+
* @param path - The route path pattern (for example `/users/:id`)
|
|
210
|
+
* @returns The per-segment specificity tiers, in order
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* computeSpecificity('/users/me') // [2, 2]
|
|
215
|
+
* computeSpecificity('/users/:id') // [2, 1]
|
|
216
|
+
* computeSpecificity('/files/*rest') // [2, 0]
|
|
217
|
+
* computeSpecificity('/a:b') // [2] — literal, not param — the classification fix
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
export declare function computeSpecificity(path: string): readonly number[];
|
|
218
221
|
|
|
219
|
-
/**
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
export declare function createDispatcher<TState = undefined>(options?: DispatcherOptions<TState>): DispatcherInterface<TState>;
|
|
222
|
+
/**
|
|
223
|
+
* Creates a {@link DispatcherInterface} — the fetch-standard, method-
|
|
224
|
+
* dimensioned dispatch entity over one internal `Router<RouteRecord<TState>>`.
|
|
225
|
+
*
|
|
226
|
+
* @remarks
|
|
227
|
+
* Prefer this over `new Dispatcher(...)` at call sites that only need the
|
|
228
|
+
* interface.
|
|
229
|
+
*
|
|
230
|
+
* @typeParam TState - The consumer's opaque per-request state type (default
|
|
231
|
+
* `undefined` for stateless use)
|
|
232
|
+
* @param options - Optional initial `routes`, the `sensitive` case toggle,
|
|
233
|
+
* the `unmatched`/`unmethoded` default-responder overrides, and the
|
|
234
|
+
* Emitter pattern's `on`/`error` wiring
|
|
235
|
+
* @returns A {@link DispatcherInterface}
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```ts
|
|
239
|
+
* import { createDispatcher } from '@src/core'
|
|
240
|
+
*
|
|
241
|
+
* const dispatcher = createDispatcher<{ readonly userId: string }>({
|
|
242
|
+
* routes: [
|
|
243
|
+
* { method: 'GET', path: '/health', handler: () => new Response('ok') },
|
|
244
|
+
* ],
|
|
245
|
+
* })
|
|
246
|
+
* const response = await dispatcher.handle(new Request('http://x/health'), { userId: 'me' })
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
export declare function createDispatcher<TState = undefined>(options?: DispatcherOptions<TState>): DispatcherInterface<TState>;
|
|
247
250
|
|
|
248
|
-
/**
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
export declare function createRouter<Meta>(options?: RouterOptions<Meta>): RouterInterface<Meta>;
|
|
251
|
+
/**
|
|
252
|
+
* Creates a {@link RouterInterface} — the pure path-matching + registry engine
|
|
253
|
+
* shared by the browser `Navigator` and the core `Dispatcher`.
|
|
254
|
+
*
|
|
255
|
+
* @remarks
|
|
256
|
+
* Prefer this over `new Router(...)` at call sites that only need the
|
|
257
|
+
* interface; an entity that OWNS a `Router` internally (like `Dispatcher`)
|
|
258
|
+
* still constructs `new Router(...)` directly.
|
|
259
|
+
*
|
|
260
|
+
* @typeParam Meta - The opaque payload each entry carries and a match returns
|
|
261
|
+
* @param options - Optional initial `entries`, the `sensitive` case toggle
|
|
262
|
+
* (default `true`), and a `key` dedup identity function
|
|
263
|
+
* @returns A {@link RouterInterface}
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* import { createRouter } from '@src/core'
|
|
268
|
+
*
|
|
269
|
+
* const router = createRouter<{ readonly page: string }>()
|
|
270
|
+
* router.add({ path: '/users/:id', meta: { page: 'profile' } })
|
|
271
|
+
* router.match('/users/7') // { path: '/users/:id', params: { id: '7' }, meta: { page: 'profile' } }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
export declare function createRouter<Meta>(options?: RouterOptions<Meta>): RouterInterface<Meta>;
|
|
272
275
|
|
|
273
|
-
/**
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
export declare function decodeParam(value: string): string;
|
|
276
|
+
/**
|
|
277
|
+
* URL-decodes one captured param value, tolerating a malformed percent-escape —
|
|
278
|
+
* the decode {@link matchPath} applies to each captured group.
|
|
279
|
+
*
|
|
280
|
+
* @remarks
|
|
281
|
+
* A bad `%` sequence is not a reason to reject an otherwise-matching route, so
|
|
282
|
+
* a `decodeURIComponent` that would throw falls back to the raw value
|
|
283
|
+
* (mirroring the cookie / token boundary readers). Total — never
|
|
284
|
+
* throws.
|
|
285
|
+
*
|
|
286
|
+
* @param value - The raw captured param value
|
|
287
|
+
* @returns The URL-decoded value, or the raw value when decoding would throw
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```ts
|
|
291
|
+
* decodeParam('a%2Fb') // 'a/b'
|
|
292
|
+
* decodeParam('100%25') // '100%'
|
|
293
|
+
* decodeParam('%') // '%' — malformed escape stays literal
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
export declare function decodeParam(value: string): string;
|
|
294
297
|
|
|
295
|
-
/**
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
export declare
|
|
333
|
-
#private;
|
|
334
|
-
readonly router: RouterInterface<RouteRecord<TState>>;
|
|
335
|
-
constructor(options?: DispatcherOptions<TState>);
|
|
336
|
-
get emitter(): EmitterInterface<DispatcherEventMap>;
|
|
337
|
-
add<Path extends string>(input: RouteInput<Path, TState>): void;
|
|
338
|
-
add(inputs: ReadonlyArray<RouteInput<string, TState>>): void;
|
|
339
|
-
group(prefix: string): DispatchGroupInterface<TState>;
|
|
340
|
-
match(method: Method, pathname: string): DispatchResult<TState>;
|
|
341
|
-
handle(request: Request, state: TState): Promise<Response>;
|
|
342
|
-
destroy(): void;
|
|
343
|
-
}
|
|
298
|
+
/**
|
|
299
|
+
* Provides an identity pass-through for a {@link RouteInput} that pins its `Path` generic
|
|
300
|
+
* to the LITERAL registration-site string, so `context.params` types
|
|
301
|
+
* correctly through {@link PathParams} without an explicit type argument.
|
|
302
|
+
*
|
|
303
|
+
* @remarks
|
|
304
|
+
* A bare object literal handed straight to {@link import('./types.js').DispatcherInterface}'s
|
|
305
|
+
* `add` already infers `Path` as a literal at that call site — but the moment
|
|
306
|
+
* the object is built through an intermediate binding (a local `const route =
|
|
307
|
+
* { method, path, handler }`) TypeScript widens `path` to `string` unless the
|
|
308
|
+
* binding's own type is pinned. Wrapping the literal in `defineRoute(...)`
|
|
309
|
+
* supplies that pin: its `const Path extends string` type parameter infers the
|
|
310
|
+
* NARROW literal from the call, and the function returns its input completely
|
|
311
|
+
* unchanged (same reference, no cloning, no validation) — this is a
|
|
312
|
+
* compile-time typing aid only, not a construction step (contrast
|
|
313
|
+
* {@link import('./factories.js')} `create*` entity factories). A
|
|
314
|
+
* heterogeneous `RouteInput[]` built from several `defineRoute(...)` calls
|
|
315
|
+
* still widens each element's `Path` to `string` after collection into one array —
|
|
316
|
+
* the realistic ceiling this helper raises is PER-CALL typing at the
|
|
317
|
+
* registration site, not a stored, still-literal-typed record.
|
|
318
|
+
*
|
|
319
|
+
* @typeParam Path - The route path pattern literal (drives `context.params`
|
|
320
|
+
* through {@link PathParams})
|
|
321
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
322
|
+
* @param input - The {@link RouteInput} to pass through unchanged
|
|
323
|
+
* @returns `input`, unchanged (same reference)
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* const input = defineRoute({
|
|
328
|
+
* method: 'GET',
|
|
329
|
+
* path: '/users/:id',
|
|
330
|
+
* handler: (_request, context) => new Response(context.params.id), // typed string
|
|
331
|
+
* })
|
|
332
|
+
* dispatcher.add(input)
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
export declare function defineRoute<const Path extends string, TState = undefined>(input: RouteInput<Path, TState>): RouteInput<Path, TState>;
|
|
344
336
|
|
|
345
|
-
/**
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Represents the fetch-standard, method-dimensioned dispatch entity — layers HTTP method
|
|
339
|
+
* dispatch and web-standard `Request`/`Response` handling over one internal
|
|
340
|
+
* `Router<RouteRecord<TState>>`. The core machine the eventual server face
|
|
341
|
+
* and any fetch-native runtime consumes directly.
|
|
342
|
+
*
|
|
343
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
344
|
+
*
|
|
345
|
+
* @remarks
|
|
346
|
+
* - **Dedup by `method + canonicalizePath`.** The underlying `Router` is
|
|
347
|
+
* constructed with a `key` function so registering the same method+path
|
|
348
|
+
* twice REPLACES the prior route in place.
|
|
349
|
+
* - **Registration boundary guard.** `add` validates each input's
|
|
350
|
+
* `handler` (`isFunction`) and `method` (must be in {@link METHODS}) —
|
|
351
|
+
* throws a `ContractError` on a malformed registration; path validation is
|
|
352
|
+
* delegated to the underlying `Router`'s own guard. `match`/`handle` stay
|
|
353
|
+
* guard-free.
|
|
354
|
+
* - **Auto-`HEAD` / auto-`OPTIONS`.** A `HEAD` request with no registered
|
|
355
|
+
* `HEAD` route runs the matching `GET` handler and strips the response
|
|
356
|
+
* body; an `OPTIONS` request with no registered `OPTIONS` route answers
|
|
357
|
+
* `204` with a derived `Allow` header.
|
|
358
|
+
* - **Handler throws propagate.** `handle` never invents an error boundary —
|
|
359
|
+
* a handler throw reaches the caller uncaught.
|
|
360
|
+
* - **Emitter.** Owns a `#emitter` for {@link DispatcherEventMap};
|
|
361
|
+
* `match`/`miss` fire AFTER resolution, before the handler/responder runs.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* const dispatcher = new Dispatcher<{ readonly userId: string }>()
|
|
366
|
+
* dispatcher.add({
|
|
367
|
+
* method: 'GET',
|
|
368
|
+
* path: '/users/:id',
|
|
369
|
+
* handler: (request, context) => Response.json({ id: context.params.id }),
|
|
370
|
+
* })
|
|
371
|
+
* const response = await dispatcher.handle(new Request('http://x/users/7'), { userId: 'me' })
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
export declare class Dispatcher<TState = undefined> implements DispatcherInterface<TState> {
|
|
375
|
+
#private;
|
|
376
|
+
constructor(options?: DispatcherOptions<TState>);
|
|
377
|
+
get router(): RouterInterface<RouteRecord<TState>>;
|
|
378
|
+
get emitter(): EmitterInterface<DispatcherEventMap>;
|
|
379
|
+
add<Path extends string>(input: RouteInput<Path, TState>): void;
|
|
380
|
+
add(inputs: ReadonlyArray<RouteInput<string, TState>>): void;
|
|
381
|
+
group(prefix: string): DispatchGroupInterface<TState>;
|
|
382
|
+
match(method: Method, pathname: string): DispatchResult<TState>;
|
|
383
|
+
handle(request: Request, state: TState): Promise<Response>;
|
|
384
|
+
destroy(): void;
|
|
385
|
+
}
|
|
365
386
|
|
|
366
|
-
/**
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
* decision `handle` builds its response from.
|
|
388
|
-
* - `handle(request, state)` — the full dispatch: parses `request.url`,
|
|
389
|
-
* calls `match`, and either invokes the winning handler (auto-stripping
|
|
390
|
-
* the body for a derived `HEAD`, auto-answering a derived `OPTIONS` with
|
|
391
|
-
* the `Allow` set), or invokes the `unmatched`/`unmethoded` responder.
|
|
392
|
-
* Emits `match`/`miss` accordingly. A handler throw propagates uncaught.
|
|
393
|
-
* - `destroy()` — tears down the `#emitter` (AGENTS §13); the underlying
|
|
394
|
-
* router is left registered (not cleared) so introspection remains valid
|
|
395
|
-
* after destroy.
|
|
396
|
-
*/
|
|
397
|
-
export declare interface DispatcherInterface<TState = undefined> {
|
|
398
|
-
readonly router: RouterInterface<RouteRecord<TState>>;
|
|
399
|
-
readonly emitter: EmitterInterface<DispatcherEventMap>;
|
|
400
|
-
add<Path extends string>(input: RouteInput<Path, TState>): void;
|
|
401
|
-
add(inputs: ReadonlyArray<RouteInput<string, TState>>): void;
|
|
402
|
-
group(prefix: string): DispatchGroupInterface<TState>;
|
|
403
|
-
match(method: Method, pathname: string): DispatchResult<TState>;
|
|
404
|
-
handle(request: Request, state: TState): Promise<Response>;
|
|
405
|
-
destroy(): void;
|
|
406
|
-
}
|
|
387
|
+
/**
|
|
388
|
+
* Represents the `Dispatcher`'s event map — the dispatch-outcome
|
|
389
|
+
* signals a consumer can observe alongside the return value of `handle`.
|
|
390
|
+
*
|
|
391
|
+
* @remarks
|
|
392
|
+
* - `match` — emitted on every dispatch the dispatcher answers, the derived
|
|
393
|
+
* `HEAD` and `OPTIONS` cases included: the request `method` and the winning
|
|
394
|
+
* `pattern` (for a derived `OPTIONS` answer, the most-specific pattern the
|
|
395
|
+
* pathname resolved to).
|
|
396
|
+
* - `miss` — emitted on every non-matching dispatch: the RAW request
|
|
397
|
+
* `method` (a plain `string`, not narrowed to {@link Method} — an unknown
|
|
398
|
+
* verb like `PURGE` is observable here exactly as sent, never coerced),
|
|
399
|
+
* the raw `pathname`, and which tier missed (`'unmatched'` — nothing
|
|
400
|
+
* matched the path at all; `'unmethoded'` — the path matched but not the
|
|
401
|
+
* method, including an unknown verb against a path with other registered
|
|
402
|
+
* methods).
|
|
403
|
+
*/
|
|
404
|
+
export declare type DispatcherEventMap = {
|
|
405
|
+
readonly match: readonly [method: Method, pattern: string];
|
|
406
|
+
readonly miss: readonly [method: string, pathname: string, status: 'unmatched' | 'unmethoded'];
|
|
407
|
+
};
|
|
407
408
|
|
|
408
|
-
/**
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Represents the fetch-standard, method-dimensioned dispatch entity contract (the
|
|
411
|
+
* behavioral-interface role for the one-class-per-file `Dispatcher`). Layers
|
|
412
|
+
* HTTP method dispatch and web-standard `Request`/`Response` handling over a
|
|
413
|
+
* single internal `Router<RouteRecord<TState>>`.
|
|
414
|
+
*
|
|
415
|
+
* @typeParam TState - The consumer's opaque per-request state type, threaded
|
|
416
|
+
* into every {@link RouteContext} (default `undefined` for stateless use)
|
|
417
|
+
*
|
|
418
|
+
* @remarks
|
|
419
|
+
* - `router` — the underlying registry, exposed READONLY for introspection
|
|
420
|
+
* (the same object `add`/`group`/`match` operate on).
|
|
421
|
+
* - `emitter` — the observable surface for {@link DispatcherEventMap}.
|
|
422
|
+
* - `add(input)` / `add(inputs)` — register ONE / MANY {@link RouteInput}s
|
|
423
|
+
* (batch registration); throws a `ContractError` on a malformed registration (a
|
|
424
|
+
* non-`/`-prefixed path, a non-function handler, or a method outside
|
|
425
|
+
* {@link import('./constants.js').METHODS}) — the construction/registration
|
|
426
|
+
* boundary guard; `match`/`handle` hot paths carry zero guards.
|
|
427
|
+
* - `group(prefix)` — a {@link DispatchGroupInterface} scoped under `prefix`.
|
|
428
|
+
* - `match(method, pathname)` — the raw {@link DispatchResult} for a method +
|
|
429
|
+
* pathname pair, with no `Request`/`Response` involvement — the pure
|
|
430
|
+
* decision `handle` builds its response from.
|
|
431
|
+
* - `handle(request, state)` — the full dispatch: parses `request.url`,
|
|
432
|
+
* calls `match`, and either invokes the winning handler (auto-stripping
|
|
433
|
+
* the body for a derived `HEAD`, auto-answering a derived `OPTIONS` with
|
|
434
|
+
* the `Allow` set), or invokes the `unmatched`/`unmethoded` responder.
|
|
435
|
+
* Emits `match`/`miss` accordingly. A handler throw propagates uncaught.
|
|
436
|
+
* - `destroy()` — tears down the `#emitter`; the underlying
|
|
437
|
+
* router is left registered (not cleared) so introspection remains valid
|
|
438
|
+
* after destroy.
|
|
439
|
+
*/
|
|
440
|
+
export declare interface DispatcherInterface<TState = undefined> {
|
|
441
|
+
readonly router: RouterInterface<RouteRecord<TState>>;
|
|
442
|
+
readonly emitter: EmitterInterface<DispatcherEventMap>;
|
|
443
|
+
add<Path extends string>(input: RouteInput<Path, TState>): void;
|
|
444
|
+
add(inputs: ReadonlyArray<RouteInput<string, TState>>): void;
|
|
445
|
+
group(prefix: string): DispatchGroupInterface<TState>;
|
|
446
|
+
match(method: Method, pathname: string): DispatchResult<TState>;
|
|
447
|
+
handle(request: Request, state: TState): Promise<Response>;
|
|
448
|
+
destroy(): void;
|
|
449
|
+
}
|
|
435
450
|
|
|
436
|
-
/**
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
add(inputs: ReadonlyArray<RouteInput<string, TState>>): void;
|
|
465
|
-
group(prefix: string): DispatchGroupInterface<TState>;
|
|
466
|
-
}
|
|
451
|
+
/**
|
|
452
|
+
* Represents the options for `createDispatcher` — initial routes, case sensitivity, the
|
|
453
|
+
* default-responder overrides, and the Emitter pattern's wiring.
|
|
454
|
+
*
|
|
455
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
456
|
+
*
|
|
457
|
+
* @remarks
|
|
458
|
+
* - `routes` — the initial route inputs to register, equivalent to a bare
|
|
459
|
+
* `createDispatcher()` followed by `add(routes)`. Omitted ⇒ no routes.
|
|
460
|
+
* - `sensitive` — forwarded to the underlying `Router` (default `true`).
|
|
461
|
+
* - `unmatched` — the responder invoked when nothing matches the pathname at
|
|
462
|
+
* all (default: a `404` `Response`).
|
|
463
|
+
* - `unmethoded` — the responder invoked when the pathname matches but not
|
|
464
|
+
* the method, given the derived `Allow` set (default: a `405` `Response`
|
|
465
|
+
* with an `Allow` header).
|
|
466
|
+
* - `on` — initial `DispatcherEventMap` listeners (the Emitter pattern's
|
|
467
|
+
* similar-surface pin).
|
|
468
|
+
* - `error` — the emitter's own listener-error handler, forwarded alongside
|
|
469
|
+
* `on`.
|
|
470
|
+
*/
|
|
471
|
+
export declare interface DispatcherOptions<TState> {
|
|
472
|
+
readonly routes?: ReadonlyArray<RouteInput<string, TState>>;
|
|
473
|
+
readonly sensitive?: boolean;
|
|
474
|
+
readonly unmatched?: (request: Request) => Response | Promise<Response>;
|
|
475
|
+
readonly unmethoded?: (request: Request, allow: readonly Method[]) => Response | Promise<Response>;
|
|
476
|
+
readonly on?: EmitterHooks<DispatcherEventMap>;
|
|
477
|
+
readonly error?: EmitterErrorHandler;
|
|
478
|
+
}
|
|
467
479
|
|
|
468
|
-
/**
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
480
|
+
/**
|
|
481
|
+
* Represents a prefix-scoped registration handle over a
|
|
482
|
+
* {@link import('./Dispatcher.js').Dispatcher} — the method-dimensioned
|
|
483
|
+
* counterpart of `Group` (`Group.ts`).
|
|
484
|
+
*
|
|
485
|
+
* @typeParam TState - The consumer's opaque per-request state type, matching
|
|
486
|
+
* the owning dispatcher
|
|
487
|
+
*
|
|
488
|
+
* @remarks
|
|
489
|
+
* Every `add` composes `input.path` through {@link joinPaths} against
|
|
490
|
+
* `this.prefix` and forwards to the OWNING dispatcher's `add` (its own
|
|
491
|
+
* registration boundary guard still applies). Pure string composition — no
|
|
492
|
+
* independent state or storage.
|
|
493
|
+
*
|
|
494
|
+
* @example
|
|
495
|
+
* ```ts
|
|
496
|
+
* import { Dispatcher } from '@src/core'
|
|
497
|
+
*
|
|
498
|
+
* const dispatcher = new Dispatcher()
|
|
499
|
+
* const api = dispatcher.group('/api')
|
|
500
|
+
* api.add({ method: 'GET', path: '/users', handler: () => new Response('ok') })
|
|
501
|
+
* ```
|
|
502
|
+
*/
|
|
503
|
+
export declare class DispatchGroup<TState> implements DispatchGroupInterface<TState> {
|
|
504
|
+
#private;
|
|
505
|
+
readonly prefix: string;
|
|
506
|
+
constructor(parent: DispatcherInterface<TState>, prefix: string);
|
|
507
|
+
add<Path extends string>(input: RouteInput<Path, TState>): void;
|
|
508
|
+
add(inputs: ReadonlyArray<RouteInput<string, TState>>): void;
|
|
509
|
+
group(prefix: string): DispatchGroupInterface<TState>;
|
|
510
|
+
}
|
|
490
511
|
|
|
491
|
-
/**
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
readonly status: 'unmatched';
|
|
514
|
-
};
|
|
512
|
+
/**
|
|
513
|
+
* Represents a prefix-scoped registration handle over a {@link DispatcherInterface} —
|
|
514
|
+
* the method-dimensioned counterpart of {@link GroupInterface}.
|
|
515
|
+
*
|
|
516
|
+
* @typeParam TState - The consumer's opaque per-request state type, matching
|
|
517
|
+
* the owning dispatcher
|
|
518
|
+
*
|
|
519
|
+
* @remarks
|
|
520
|
+
* - `prefix` — the path prefix this group prepends to every route it
|
|
521
|
+
* registers (and to every nested group's own prefix).
|
|
522
|
+
* - `add(input)` / `add(inputs)` — register ONE / MANY {@link RouteInput}s on
|
|
523
|
+
* the OWNING dispatcher, each input's `path` composed as
|
|
524
|
+
* `prefix + input.path` (batch registration, mirroring
|
|
525
|
+
* {@link DispatcherInterface.add}).
|
|
526
|
+
* - `group(prefix)` — a nested group whose prefix is `this.prefix + prefix`.
|
|
527
|
+
*/
|
|
528
|
+
export declare interface DispatchGroupInterface<TState> {
|
|
529
|
+
readonly prefix: string;
|
|
530
|
+
add<Path extends string>(input: RouteInput<Path, TState>): void;
|
|
531
|
+
add(inputs: ReadonlyArray<RouteInput<string, TState>>): void;
|
|
532
|
+
group(prefix: string): DispatchGroupInterface<TState>;
|
|
533
|
+
}
|
|
515
534
|
|
|
516
|
-
/**
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
535
|
+
/**
|
|
536
|
+
* Represents the outcome of {@link DispatcherInterface.match} — a discriminated union
|
|
537
|
+
* over the dispatch tiers: a full hit, a path-matches-but-method-
|
|
538
|
+
* doesn't (405 territory), or nothing matched at all (404 territory).
|
|
539
|
+
*
|
|
540
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
541
|
+
*
|
|
542
|
+
* @remarks
|
|
543
|
+
* - `'matched'` — carries the winning {@link RouterMatch} (its `meta` is a
|
|
544
|
+
* {@link RouteRecord}).
|
|
545
|
+
* - `'unmethoded'` — the pathname matched at least one entry but none for
|
|
546
|
+
* the requested method; `allow` is the derived `Allow` method set (from
|
|
547
|
+
* `router.entries(pathname)`).
|
|
548
|
+
* - `'unmatched'` — no registered pattern matches the pathname at all.
|
|
549
|
+
*/
|
|
550
|
+
export declare type DispatchResult<TState> = {
|
|
551
|
+
readonly status: 'matched';
|
|
552
|
+
readonly match: RouterMatch<RouteRecord<TState>>;
|
|
553
|
+
} | {
|
|
554
|
+
readonly status: 'unmethoded';
|
|
555
|
+
readonly allow: readonly Method[];
|
|
556
|
+
} | {
|
|
557
|
+
readonly status: 'unmatched';
|
|
558
|
+
};
|
|
537
559
|
|
|
538
|
-
/**
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
export declare class Group<Meta> implements GroupInterface<Meta> {
|
|
560
|
-
#private;
|
|
561
|
-
readonly prefix: string;
|
|
562
|
-
constructor(parent: RouterInterface<Meta>, prefix: string);
|
|
563
|
-
add(entry: RouteEntry<Meta>): void;
|
|
564
|
-
add(entries: ReadonlyArray<RouteEntry<Meta>>): void;
|
|
565
|
-
group(prefix: string): GroupInterface<Meta>;
|
|
566
|
-
}
|
|
560
|
+
/**
|
|
561
|
+
* Escapes every regex metacharacter in a literal string so it can be embedded
|
|
562
|
+
* inside a larger `RegExp` source without being interpreted as syntax.
|
|
563
|
+
*
|
|
564
|
+
* @remarks
|
|
565
|
+
* {@link compilePath} escapes the literal segments of a route pattern with this
|
|
566
|
+
* before splicing in `:name` / `*name` capture groups, so a path like
|
|
567
|
+
* `/files/:name.json` matches the `.` literally rather than as "any character".
|
|
568
|
+
* Pure and total — never throws.
|
|
569
|
+
*
|
|
570
|
+
* @param value - The literal string to escape
|
|
571
|
+
* @returns `value` with every regex metacharacter backslash-escaped
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* ```ts
|
|
575
|
+
* escapeRegExp('a.b+c') // 'a\\.b\\+c'
|
|
576
|
+
* new RegExp(`^${escapeRegExp('a.b')}$`).test('a.b') // true
|
|
577
|
+
* new RegExp(`^${escapeRegExp('a.b')}$`).test('axb') // false
|
|
578
|
+
* ```
|
|
579
|
+
*/
|
|
580
|
+
export declare function escapeRegExp(value: string): string;
|
|
567
581
|
|
|
568
|
-
/**
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
582
|
+
/**
|
|
583
|
+
* Represents a prefix-scoped registration handle over a {@link import('./Router.js').Router} —
|
|
584
|
+
* pure string composition, no independent state or storage.
|
|
585
|
+
*
|
|
586
|
+
* @typeParam Meta - The entry payload type, matching the owning router
|
|
587
|
+
*
|
|
588
|
+
* @remarks
|
|
589
|
+
* Every `add` composes `entry.path` as `joinPaths(prefix, entry.path)` and
|
|
590
|
+
* forwards to the OWNING router, so grouped routes land in the SAME registry.
|
|
591
|
+
* `group(prefix)` nests, composing prefixes through {@link joinPaths}.
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* ```ts
|
|
595
|
+
* import { Router } from '@src/core'
|
|
596
|
+
*
|
|
597
|
+
* const router = new Router<{ readonly page: string }>()
|
|
598
|
+
* const api = router.group('/api')
|
|
599
|
+
* api.add({ path: '/users', meta: { page: 'list' } })
|
|
600
|
+
* router.match('/api/users')?.path // '/api/users'
|
|
601
|
+
* ```
|
|
602
|
+
*/
|
|
603
|
+
export declare class Group<Meta> implements GroupInterface<Meta> {
|
|
604
|
+
#private;
|
|
605
|
+
readonly prefix: string;
|
|
606
|
+
constructor(parent: RouterInterface<Meta>, prefix: string);
|
|
607
|
+
add(entry: RouteEntry<Meta>): void;
|
|
608
|
+
add(entries: ReadonlyArray<RouteEntry<Meta>>): void;
|
|
609
|
+
group(prefix: string): GroupInterface<Meta>;
|
|
610
|
+
}
|
|
589
611
|
|
|
590
|
-
/**
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
612
|
+
/**
|
|
613
|
+
* Represents a prefix-scoped registration handle over a {@link RouterInterface} — pure
|
|
614
|
+
* string composition, no independent state or storage.
|
|
615
|
+
*
|
|
616
|
+
* @typeParam Meta - The entry payload type, matching the owning router
|
|
617
|
+
*
|
|
618
|
+
* @remarks
|
|
619
|
+
* - `prefix` — the path prefix this group prepends to every entry it
|
|
620
|
+
* registers (and to every nested group's own prefix).
|
|
621
|
+
* - `add(entry)` / `add(entries)` — register ONE / MANY entries on the
|
|
622
|
+
* OWNING router, each entry's `path` composed as `prefix + entry.path`
|
|
623
|
+
* (batch registration, mirroring {@link RouterInterface.add}).
|
|
624
|
+
* - `group(prefix)` — a nested group whose prefix is `this.prefix + prefix`;
|
|
625
|
+
* nesting composes prefixes left to right with no depth limit.
|
|
626
|
+
*/
|
|
627
|
+
export declare interface GroupInterface<Meta> {
|
|
628
|
+
readonly prefix: string;
|
|
629
|
+
add(entry: RouteEntry<Meta>): void;
|
|
630
|
+
add(entries: ReadonlyArray<RouteEntry<Meta>>): void;
|
|
631
|
+
group(prefix: string): GroupInterface<Meta>;
|
|
632
|
+
}
|
|
595
633
|
|
|
596
|
-
|
|
634
|
+
/**
|
|
635
|
+
* Names the identifier CONTINUATION characters after the first — mirrors the
|
|
636
|
+
* runtime classifier's `[A-Za-z0-9_]*` tail class.
|
|
637
|
+
*/
|
|
638
|
+
export declare type IdentifierChar = IdentifierStartChar | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
597
639
|
|
|
598
|
-
|
|
599
|
-
* The identifier START characters an identifier-grammar param name may
|
|
600
|
-
* begin with — mirrors the runtime classifier's `[A-Za-z_]` head class
|
|
601
|
-
* (`classifySegment` / `compilePath`, `helpers.ts`).
|
|
602
|
-
*/
|
|
603
|
-
export declare 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' | '_';
|
|
640
|
+
export declare type IdentifierHead<S extends string> = S extends `${infer Head}${infer Tail}` ? Head extends IdentifierStartChar ? TakeIdentifierTail<Tail, Head> : '' : '';
|
|
604
641
|
|
|
605
|
-
/**
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
* compose a prefix with each registered entry's path this way — pure string
|
|
612
|
-
* composition (§4.2.2), no independent state. Both a duplicated slash
|
|
613
|
-
* (`'/api/'` + `'/users'`) and a missing one (`'/api'` + `'users'`) normalize
|
|
614
|
-
* to a single joining slash. An empty `prefix` returns `path` unchanged (after
|
|
615
|
-
* ensuring a leading slash); an empty `path` returns `prefix` unchanged.
|
|
616
|
-
* Pure and total.
|
|
617
|
-
*
|
|
618
|
-
* @param prefix - The group prefix (e.g. `/api`)
|
|
619
|
-
* @param path - The route path being joined under the prefix (e.g. `/users`)
|
|
620
|
-
* @returns The joined `/`-prefixed path
|
|
621
|
-
*
|
|
622
|
-
* @example
|
|
623
|
-
* ```ts
|
|
624
|
-
* joinPaths('/api', '/users') // '/api/users'
|
|
625
|
-
* joinPaths('/api/', '/users') // '/api/users'
|
|
626
|
-
* joinPaths('/api', 'users') // '/api/users'
|
|
627
|
-
* joinPaths('', '/users') // '/users'
|
|
628
|
-
* joinPaths('/api', '') // '/api'
|
|
629
|
-
* ```
|
|
630
|
-
*/
|
|
631
|
-
export declare function joinPaths(prefix: string, path: string): string;
|
|
642
|
+
/**
|
|
643
|
+
* Names the identifier START characters an identifier-grammar param name may
|
|
644
|
+
* begin with — mirrors the runtime classifier's `[A-Za-z_]` head class
|
|
645
|
+
* (`classifySegment` / `compilePath`, `helpers.ts`).
|
|
646
|
+
*/
|
|
647
|
+
export declare 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' | '_';
|
|
632
648
|
|
|
633
|
-
/**
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
649
|
+
/**
|
|
650
|
+
* Joins a group prefix and a route path into one `/`-prefixed path, normalizing
|
|
651
|
+
* duplicate or missing joining slashes.
|
|
652
|
+
*
|
|
653
|
+
* @remarks
|
|
654
|
+
* {@link import('./types.js').GroupInterface} / {@link import('./types.js').DispatchGroupInterface}
|
|
655
|
+
* compose a prefix with each registered entry's path this way — pure string
|
|
656
|
+
* composition, no independent state. Both a duplicated slash
|
|
657
|
+
* (`'/api/'` + `'/users'`) and a missing one (`'/api'` + `'users'`) normalize
|
|
658
|
+
* to a single joining slash. An empty `prefix` returns `path` unchanged (after
|
|
659
|
+
* ensuring a leading slash); an empty `path` returns `prefix` unchanged.
|
|
660
|
+
* Pure and total.
|
|
661
|
+
*
|
|
662
|
+
* @param prefix - The group prefix (for example `/api`)
|
|
663
|
+
* @param path - The route path being joined under the prefix (for example `/users`)
|
|
664
|
+
* @returns The joined `/`-prefixed path
|
|
665
|
+
*
|
|
666
|
+
* @example
|
|
667
|
+
* ```ts
|
|
668
|
+
* joinPaths('/api', '/users') // '/api/users'
|
|
669
|
+
* joinPaths('/api/', '/users') // '/api/users'
|
|
670
|
+
* joinPaths('/api', 'users') // '/api/users'
|
|
671
|
+
* joinPaths('', '/users') // '/users'
|
|
672
|
+
* joinPaths('/api', '') // '/api'
|
|
673
|
+
* ```
|
|
674
|
+
*/
|
|
675
|
+
export declare function joinPaths(prefix: string, path: string): string;
|
|
657
676
|
|
|
658
|
-
/**
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
677
|
+
/**
|
|
678
|
+
* Extracts the URL-decoded params a compiled path captures from a concrete
|
|
679
|
+
* pathname, or `undefined` when the pathname does not match.
|
|
680
|
+
*
|
|
681
|
+
* @remarks
|
|
682
|
+
* Runs the {@link CompiledPath} `regex` against `pathname` (a single `exec`); a
|
|
683
|
+
* miss returns `undefined`. On a hit it walks `params` POSITIONALLY — the
|
|
684
|
+
* `n`-th param name pairs with the `n`-th capture group — and URL-decodes each
|
|
685
|
+
* value with {@link decodeParam}. Returns a frozen `name → value` record (empty
|
|
686
|
+
* for a parameterless path). Total — never throws.
|
|
687
|
+
*
|
|
688
|
+
* @param compiled - The {@link CompiledPath} from {@link compilePath}
|
|
689
|
+
* @param pathname - The concrete request pathname to match (for example `/users/7`)
|
|
690
|
+
* @returns The decoded params on a hit, or `undefined` on a miss
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* ```ts
|
|
694
|
+
* const compiled = compilePath('/users/:id')
|
|
695
|
+
* matchPath(compiled, '/users/7') // { id: '7' }
|
|
696
|
+
* matchPath(compiled, '/users/a%2Fb') // { id: 'a/b' } — decoded
|
|
697
|
+
* matchPath(compiled, '/posts/7') // undefined
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
export declare function matchPath(compiled: CompiledPath, pathname: string): Readonly<Record<string, string>> | undefined;
|
|
668
701
|
|
|
669
|
-
/**
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
* METHODS.has('TRACE') // false
|
|
684
|
-
* ```
|
|
685
|
-
*/
|
|
686
|
-
export declare const METHODS: ReadonlySet<string>;
|
|
702
|
+
/**
|
|
703
|
+
* Names the HTTP methods a {@link DispatcherInterface} dimensions dispatch
|
|
704
|
+
* over — derived from {@link import('./constants.js').METHOD_LIST}, whose
|
|
705
|
+
* membership counterpart is {@link import('./constants.js').METHODS}.
|
|
706
|
+
*
|
|
707
|
+
* @remarks
|
|
708
|
+
* Resolves to `'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' |
|
|
709
|
+
* 'OPTIONS'`. Deriving the union from the constant keeps one home for the
|
|
710
|
+
* method set: a verb added there widens this type, the membership set, and the
|
|
711
|
+
* `parseMethod` narrowing together. `HEAD` is a valid explicit registration
|
|
712
|
+
* even though a `GET` route already auto-answers `HEAD` — an explicit
|
|
713
|
+
* `HEAD` handler always takes precedence over the derived one.
|
|
714
|
+
*/
|
|
715
|
+
export declare type Method = (typeof METHOD_LIST)[number];
|
|
687
716
|
|
|
688
|
-
/**
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
* ```
|
|
709
|
-
*/
|
|
710
|
-
export declare function parseMethod(value: string): Method | undefined;
|
|
717
|
+
/**
|
|
718
|
+
* Lists the HTTP methods a {@link import('./types.js').DispatcherInterface}
|
|
719
|
+
* registers routes under, in canonical order — the single source the
|
|
720
|
+
* {@link import('./types.js').Method} type, {@link METHODS}, and
|
|
721
|
+
* `parseMethod` are all derived from.
|
|
722
|
+
*
|
|
723
|
+
* @remarks
|
|
724
|
+
* A frozen tuple of the verbs: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`,
|
|
725
|
+
* `HEAD`, `OPTIONS`. Adding a verb here widens the `Method` type, the
|
|
726
|
+
* {@link METHODS} membership set, and the `parseMethod` narrowing together, so
|
|
727
|
+
* the method set cannot drift between them. Prefer {@link METHODS} for a
|
|
728
|
+
* membership test; use this tuple where order or literal typing matters.
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* ```ts
|
|
732
|
+
* METHOD_LIST[0] // 'GET'
|
|
733
|
+
* METHOD_LIST.includes('GET') // true
|
|
734
|
+
* ```
|
|
735
|
+
*/
|
|
736
|
+
export declare const METHOD_LIST: readonly ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"];
|
|
711
737
|
|
|
712
|
-
/**
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
*/
|
|
734
|
-
export declare type PathParams<Path extends string> = {
|
|
735
|
-
readonly [K in keyof PathParamsRaw<Path>]: PathParamsRaw<Path>[K];
|
|
736
|
-
};
|
|
738
|
+
/**
|
|
739
|
+
* Holds the complete set of HTTP methods a
|
|
740
|
+
* {@link import('./types.js').DispatcherInterface} registers routes under —
|
|
741
|
+
* backs the registration guard (`add` rejects any `method` outside this set)
|
|
742
|
+
* and the auto-`OPTIONS` `Allow` derivation.
|
|
743
|
+
*
|
|
744
|
+
* @remarks
|
|
745
|
+
* A `ReadonlySet` built from {@link METHOD_LIST}, so it carries exactly the
|
|
746
|
+
* {@link import('./types.js').Method} literals: `GET`, `POST`, `PUT`,
|
|
747
|
+
* `PATCH`, `DELETE`, `HEAD`, `OPTIONS`. `HEAD` is included even though it is
|
|
748
|
+
* never required at registration (a `GET` route auto-answers `HEAD`) — it is
|
|
749
|
+
* still a valid method to register explicitly. The element type stays `string`
|
|
750
|
+
* so a raw, unnarrowed `request.method` can be tested directly.
|
|
751
|
+
*
|
|
752
|
+
* @example
|
|
753
|
+
* ```ts
|
|
754
|
+
* METHODS.has('GET') // true
|
|
755
|
+
* METHODS.has('TRACE') // false
|
|
756
|
+
* ```
|
|
757
|
+
*/
|
|
758
|
+
export declare const METHODS: ReadonlySet<string>;
|
|
737
759
|
|
|
738
|
-
/**
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
export declare
|
|
760
|
+
/**
|
|
761
|
+
* Narrows a raw `request.method` string into a typed {@link Method} — total,
|
|
762
|
+
* never throws.
|
|
763
|
+
*
|
|
764
|
+
* @remarks
|
|
765
|
+
* Consults {@link import('./constants.js').METHOD_LIST} (the one home for the
|
|
766
|
+
* registrable HTTP methods), so a verb added there narrows here without
|
|
767
|
+
* a second list to update; any other value (an unknown verb, non-uppercase
|
|
768
|
+
* casing) resolves to `undefined` rather than throwing (total guard behavior).
|
|
769
|
+
* Pure leaf shared by the `Dispatcher`'s `handle` (honest about an unknown verb)
|
|
770
|
+
* and anywhere else a raw method string needs narrowing.
|
|
771
|
+
*
|
|
772
|
+
* @param value - The raw `request.method` string to narrow
|
|
773
|
+
* @returns The matching {@link Method}, or `undefined` when `value` is not a
|
|
774
|
+
* registrable method
|
|
775
|
+
*
|
|
776
|
+
* @example
|
|
777
|
+
* ```ts
|
|
778
|
+
* parseMethod('GET') // 'GET'
|
|
779
|
+
* parseMethod('PURGE') // undefined
|
|
780
|
+
* parseMethod('get') // undefined — case-sensitive
|
|
781
|
+
* ```
|
|
782
|
+
*/
|
|
783
|
+
export declare function parseMethod(value: string): Method | undefined;
|
|
762
784
|
|
|
763
|
-
/**
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
* @returns `input`, unchanged (same reference)
|
|
789
|
-
*
|
|
790
|
-
* @example
|
|
791
|
-
* ```ts
|
|
792
|
-
* const input = route({
|
|
793
|
-
* method: 'GET',
|
|
794
|
-
* path: '/users/:id',
|
|
795
|
-
* handler: (_request, context) => new Response(context.params.id), // typed string
|
|
796
|
-
* })
|
|
797
|
-
* dispatcher.add(input)
|
|
798
|
-
* ```
|
|
799
|
-
*/
|
|
800
|
-
export declare function route<const Path extends string, TState = undefined>(input: RouteInput<Path, TState>): RouteInput<Path, TState>;
|
|
785
|
+
/**
|
|
786
|
+
* Extracts `{ name: string }` param records from a path pattern at the type
|
|
787
|
+
* level — the typed half of the path grammar.
|
|
788
|
+
*
|
|
789
|
+
* @typeParam Path - A route path pattern literal (`/users/:id/posts/:slug`,
|
|
790
|
+
* `/files/*rest`, or a parameterless literal path)
|
|
791
|
+
*
|
|
792
|
+
* @remarks
|
|
793
|
+
* A `:name` segment contributes `{ name: string }`; a trailing `*name`
|
|
794
|
+
* wildcard (the grammar's only allowed wildcard position) contributes
|
|
795
|
+
* `{ name: string }` capturing the rest of the path; a parameterless pattern
|
|
796
|
+
* resolves to an empty record. Built over {@link PathParamsRaw} and flattened
|
|
797
|
+
* through an identity-mapped type so editor hovers show the resolved shape
|
|
798
|
+
* (`{ id: string; slug: string }`) rather than an unresolved intersection.
|
|
799
|
+
*
|
|
800
|
+
* @example
|
|
801
|
+
* ```ts
|
|
802
|
+
* type A = PathParams<'/users/:id/posts/:slug'> // { readonly id: string; readonly slug: string }
|
|
803
|
+
* type B = PathParams<'/files/*rest'> // { readonly rest: string }
|
|
804
|
+
* type C = PathParams<'/health'> // Record<string, never>
|
|
805
|
+
* ```
|
|
806
|
+
*/
|
|
807
|
+
export declare type PathParams<Path extends string> = {
|
|
808
|
+
readonly [K in keyof PathParamsRaw<Path>]: PathParamsRaw<Path>[K];
|
|
809
|
+
};
|
|
801
810
|
|
|
802
|
-
/**
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
}
|
|
811
|
+
/**
|
|
812
|
+
* Performs recursive, unflattened param extraction for {@link PathParams} — walks a
|
|
813
|
+
* path pattern segment by segment (split on `/`), extracting each segment's
|
|
814
|
+
* {@link SegmentParam} contribution and intersecting the rest.
|
|
815
|
+
*
|
|
816
|
+
* @typeParam Path - The path pattern literal being decomposed
|
|
817
|
+
*
|
|
818
|
+
* @remarks
|
|
819
|
+
* Splits `Path` at its first `/` into `Segment` + `Rest`, intersects
|
|
820
|
+
* `SegmentParam<Segment>` with the recursive walk of `Rest`, and — after no
|
|
821
|
+
* further `/` remains — resolves the final segment's own `SegmentParam`
|
|
822
|
+
* directly. `SegmentParam` mirrors the runtime `classifySegment` grammar
|
|
823
|
+
* exactly: a `:name` HEAD (identifier-char run, stopping at the first
|
|
824
|
+
* non-identifier char) captures a param; a segment whose `:` is not at the
|
|
825
|
+
* segment START (`a:b`) contributes nothing (the classification fix); a
|
|
826
|
+
* final `*name` wildcard captures the rest-of-path param the same way. A
|
|
827
|
+
* fully parameterless `Path` resolves to `unknown` here (the intersection
|
|
828
|
+
* identity every non-capturing segment contributes) — {@link PathParams}
|
|
829
|
+
* flattens that down to a clean empty record. This type is exported so
|
|
830
|
+
* {@link PathParams} (which flattens it into a clean mapped type for IDE
|
|
831
|
+
* hovers) has a documented, testable recursive step; consumers reach for the
|
|
832
|
+
* flattened {@link PathParams} form, never this raw recursion.
|
|
833
|
+
*/
|
|
834
|
+
export declare type PathParamsRaw<Path extends string> = string extends Path ? Readonly<Record<string, string>> : Path extends `${infer Segment}/${infer Rest}` ? SegmentParam<Segment> & PathParamsRaw<Rest> : SegmentParam<Path>;
|
|
826
835
|
|
|
827
|
-
/**
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
export declare interface
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
836
|
+
/**
|
|
837
|
+
* Represents the ambient context a {@link RouteHandler} receives alongside the raw
|
|
838
|
+
* `Request` — decoded params, the winning pattern, the parsed URL, and the
|
|
839
|
+
* consumer's opaque per-request state.
|
|
840
|
+
*
|
|
841
|
+
* @typeParam Path - The route path pattern the handler was registered under
|
|
842
|
+
* (drives the typed shape of `params` through {@link PathParams})
|
|
843
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
844
|
+
*
|
|
845
|
+
* @remarks
|
|
846
|
+
* - `params` — the decoded param record, typed from `Path` through
|
|
847
|
+
* {@link PathParams} (empty for a parameterless path).
|
|
848
|
+
* - `pattern` — the winning REGISTERED pattern (matches
|
|
849
|
+
* {@link RouterMatch.path}), useful for logging/metrics.
|
|
850
|
+
* - `url` — the request URL, already parsed once by the dispatcher.
|
|
851
|
+
* - `state` — the consumer's per-request payload (logger, session, DI bag),
|
|
852
|
+
* threaded opaquely through `handle()` — the router never inspects it.
|
|
853
|
+
*/
|
|
854
|
+
export declare interface RouteContext<Path extends string = string, TState = undefined> {
|
|
855
|
+
readonly params: PathParams<Path>;
|
|
856
|
+
readonly pattern: string;
|
|
857
|
+
readonly url: URL;
|
|
858
|
+
readonly state: TState;
|
|
859
|
+
}
|
|
850
860
|
|
|
851
|
-
/**
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
861
|
+
/**
|
|
862
|
+
* Represents one registered route in a {@link RouterInterface} — the `path` pattern plus
|
|
863
|
+
* the opaque `meta` payload to return on a match, with an optional `name`.
|
|
864
|
+
*
|
|
865
|
+
* @typeParam Meta - The payload to carry on a match (opaque to the engine —
|
|
866
|
+
* a route handler + method on the `Dispatcher`, a component/loader
|
|
867
|
+
* reference on the `Navigator`)
|
|
868
|
+
*
|
|
869
|
+
* @remarks
|
|
870
|
+
* - `path` — the `/`-prefixed route path pattern (`/users/:id`); compiled
|
|
871
|
+
* once at registration.
|
|
872
|
+
* - `meta` — the opaque payload returned (as {@link RouterMatch.meta}) when
|
|
873
|
+
* this entry is the most-specific match. The engine never inspects it —
|
|
874
|
+
* the consumer's {@link AnswerHandler} predicate (the override seam)
|
|
875
|
+
* decides eligibility from it.
|
|
876
|
+
* - `name` — an optional route identifier, carried through onto a match for
|
|
877
|
+
* consumers that build named links or debug output.
|
|
878
|
+
*/
|
|
879
|
+
export declare interface RouteEntry<Meta> {
|
|
880
|
+
readonly path: string;
|
|
881
|
+
readonly meta: Meta;
|
|
882
|
+
readonly name?: string;
|
|
883
|
+
}
|
|
864
884
|
|
|
865
|
-
/**
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
* {@link RouterMatch}.
|
|
879
|
-
*/
|
|
880
|
-
export declare interface RouteInput<Path extends string = string, TState = undefined> {
|
|
881
|
-
readonly method: Method;
|
|
882
|
-
readonly path: Path;
|
|
883
|
-
readonly handler: RouteHandler<Path, TState>;
|
|
884
|
-
readonly name?: string;
|
|
885
|
-
}
|
|
885
|
+
/**
|
|
886
|
+
* Receives the raw fetch `Request` plus its typed {@link RouteContext} and returns (or resolves)
|
|
887
|
+
* a fetch `Response`.
|
|
888
|
+
*
|
|
889
|
+
* @typeParam Path - The route path pattern the handler is registered under
|
|
890
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
891
|
+
*
|
|
892
|
+
* @remarks
|
|
893
|
+
* A handler throw propagates to the caller of `dispatcher.handle` — the
|
|
894
|
+
* dispatcher never invents an error boundary; mapping throws to responses is
|
|
895
|
+
* the consuming server's policy.
|
|
896
|
+
*/
|
|
897
|
+
export declare type RouteHandler<Path extends string = string, TState = undefined> = (request: Request, context: RouteContext<Path, TState>) => Response | Promise<Response>;
|
|
886
898
|
|
|
887
|
-
/**
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
*
|
|
909
|
-
* @example
|
|
910
|
-
* ```ts
|
|
911
|
-
* const router = new Router<{ readonly page: string }>()
|
|
912
|
-
* router.add({ path: '/users/:id', meta: { page: 'profile' } })
|
|
913
|
-
* router.match('/users/7') // { path: '/users/:id', params: { id: '7' }, meta: { page: 'profile' } }
|
|
914
|
-
* ```
|
|
915
|
-
*/
|
|
916
|
-
export declare class Router<Meta> implements RouterInterface<Meta> {
|
|
917
|
-
#private;
|
|
918
|
-
constructor(options?: RouterOptions<Meta>);
|
|
919
|
-
get count(): number;
|
|
920
|
-
add(entry: RouteEntry<Meta>): void;
|
|
921
|
-
add(entries: ReadonlyArray<RouteEntry<Meta>>): void;
|
|
922
|
-
match(pathname: string, answers?: AnswerHandler<Meta>): RouterMatch<Meta> | undefined;
|
|
923
|
-
entries(): ReadonlyArray<RouteEntry<Meta>>;
|
|
924
|
-
entries(pathname: string): ReadonlyArray<RouteEntry<Meta>>;
|
|
925
|
-
group(prefix: string): GroupInterface<Meta>;
|
|
926
|
-
clear(): void;
|
|
927
|
-
}
|
|
899
|
+
/**
|
|
900
|
+
* Represents one route registration input for {@link DispatcherInterface.add} — the
|
|
901
|
+
* method-dimensioned counterpart of {@link RouteEntry}.
|
|
902
|
+
*
|
|
903
|
+
* @typeParam Path - The route path pattern literal (drives the typed
|
|
904
|
+
* `handler`'s `context.params` through {@link PathParams})
|
|
905
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
906
|
+
*
|
|
907
|
+
* @remarks
|
|
908
|
+
* - `method` — the HTTP method this route answers.
|
|
909
|
+
* - `path` — the `/`-prefixed route path pattern.
|
|
910
|
+
* - `handler` — the {@link RouteHandler} invoked on a match.
|
|
911
|
+
* - `name` — an optional route identifier, carried through onto a
|
|
912
|
+
* {@link RouterMatch}.
|
|
913
|
+
*/
|
|
914
|
+
export declare interface RouteInput<Path extends string = string, TState = undefined> {
|
|
915
|
+
readonly method: Method;
|
|
916
|
+
readonly path: Path;
|
|
917
|
+
readonly handler: RouteHandler<Path, TState>;
|
|
918
|
+
readonly name?: string;
|
|
919
|
+
}
|
|
928
920
|
|
|
929
|
-
/**
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
921
|
+
/**
|
|
922
|
+
* Represents the path-matching + registry engine — registers `{ path, meta, name? }`
|
|
923
|
+
* entries (compiling each path once) and resolves a concrete pathname to the
|
|
924
|
+
* MOST SPECIFIC matching entry. The shared machine both the `Navigator`
|
|
925
|
+
* (browser) and the `Dispatcher` (core, method-dimensioned) compose.
|
|
926
|
+
*
|
|
927
|
+
* @typeParam Meta - The opaque payload each entry carries and a match returns
|
|
928
|
+
*
|
|
929
|
+
* @remarks
|
|
930
|
+
* - **Registration boundary guard.** `add` validates each entry's
|
|
931
|
+
* `path` — `isString` plus a leading `/` — and throws a `ContractError` on a
|
|
932
|
+
* malformed registration; `match` stays guard-free (the hot path).
|
|
933
|
+
* - **Compile-once.** Each path is compiled exactly once at registration into
|
|
934
|
+
* a parallel `#compiled` array, so `match` runs only a cached `exec` per
|
|
935
|
+
* candidate.
|
|
936
|
+
* - **Dedup through `key`.** When `options.key` is set, an entry whose computed
|
|
937
|
+
* key already exists REPLACES the prior one IN PLACE (both the `#entries`
|
|
938
|
+
* and `#compiled` arrays, at the existing index) — last write wins, no
|
|
939
|
+
* engine rebuild. Omitted ⇒ every entry is kept, even duplicate paths.
|
|
940
|
+
* - **Groups.** `group(prefix)` returns a {@link GroupInterface} that composes
|
|
941
|
+
* `prefix` onto every entry it registers, nesting through {@link joinPaths}.
|
|
942
|
+
*
|
|
943
|
+
* @example
|
|
944
|
+
* ```ts
|
|
945
|
+
* const router = new Router<{ readonly page: string }>()
|
|
946
|
+
* router.add({ path: '/users/:id', meta: { page: 'profile' } })
|
|
947
|
+
* router.match('/users/7') // { path: '/users/:id', params: { id: '7' }, meta: { page: 'profile' } }
|
|
948
|
+
* ```
|
|
949
|
+
*/
|
|
950
|
+
export declare class Router<Meta> implements RouterInterface<Meta> {
|
|
951
|
+
#private;
|
|
952
|
+
constructor(options?: RouterOptions<Meta>);
|
|
953
|
+
get count(): number;
|
|
954
|
+
add(entry: RouteEntry<Meta>): void;
|
|
955
|
+
add(entries: ReadonlyArray<RouteEntry<Meta>>): void;
|
|
956
|
+
match(pathname: string, answers?: AnswerHandler<Meta>): RouterMatch<Meta> | undefined;
|
|
957
|
+
entries(): ReadonlyArray<RouteEntry<Meta>>;
|
|
958
|
+
entries(pathname: string): ReadonlyArray<RouteEntry<Meta>>;
|
|
959
|
+
group(prefix: string): GroupInterface<Meta>;
|
|
960
|
+
clear(): void;
|
|
961
|
+
}
|
|
946
962
|
|
|
947
|
-
/**
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
* - `match(pathname, answers?)` — the MOST-SPECIFIC matching entry as a
|
|
965
|
-
* {@link RouterMatch} (its winning `path`, decoded `params`, `meta`, and
|
|
966
|
-
* `name`), or `undefined`. The optional {@link AnswerHandler} predicate
|
|
967
|
-
* filters candidates by `meta` first; omitted ⇒ every path match is
|
|
968
|
-
* eligible.
|
|
969
|
-
* - `entries()` — ALL registered entries in registration order.
|
|
970
|
-
* - `entries(pathname)` — only entries whose path matches `pathname` (the
|
|
971
|
-
* §9 plural accessor's filtered form; backs a consumer's allow/405 set).
|
|
972
|
-
* - `group(prefix)` — a {@link GroupInterface} scoped under `prefix`; entries
|
|
973
|
-
* added through the group are registered on this same router with `prefix`
|
|
974
|
-
* prepended to each path.
|
|
975
|
-
* - `clear()` — drop every entry (§10), leaving the router reusable.
|
|
976
|
-
*/
|
|
977
|
-
export declare interface RouterInterface<Meta> {
|
|
978
|
-
readonly count: number;
|
|
979
|
-
add(entry: RouteEntry<Meta>): void;
|
|
980
|
-
add(entries: ReadonlyArray<RouteEntry<Meta>>): void;
|
|
981
|
-
match(pathname: string, answers?: AnswerHandler<Meta>): RouterMatch<Meta> | undefined;
|
|
982
|
-
entries(): ReadonlyArray<RouteEntry<Meta>>;
|
|
983
|
-
entries(pathname: string): ReadonlyArray<RouteEntry<Meta>>;
|
|
984
|
-
group(prefix: string): GroupInterface<Meta>;
|
|
985
|
-
clear(): void;
|
|
986
|
-
}
|
|
963
|
+
/**
|
|
964
|
+
* Represents the `meta` payload a {@link DispatcherInterface} stores in its underlying
|
|
965
|
+
* `Router` — what {@link RouterInterface.match} returns as
|
|
966
|
+
* {@link RouterMatch.meta} on a dispatch hit.
|
|
967
|
+
*
|
|
968
|
+
* @typeParam TState - The consumer's opaque per-request state type
|
|
969
|
+
*
|
|
970
|
+
* @remarks
|
|
971
|
+
* The handler is typed over `string` (not the original literal `Path`) at
|
|
972
|
+
* storage — path-specific param typing is recovered at the call site through
|
|
973
|
+
* {@link RouteInput}'s generic `Path`, not preserved in the stored record.
|
|
974
|
+
*/
|
|
975
|
+
export declare interface RouteRecord<TState> {
|
|
976
|
+
readonly method: Method;
|
|
977
|
+
readonly handler: RouteHandler<string, TState>;
|
|
978
|
+
readonly name?: string;
|
|
979
|
+
}
|
|
987
980
|
|
|
988
|
-
/**
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
981
|
+
/**
|
|
982
|
+
* Represents the path-matching + registry engine contract (the behavioral-interface
|
|
983
|
+
* role for the one-class-per-file `Router`). Registers `{ path, meta, name? }`
|
|
984
|
+
* entries (compiling each path once) and resolves a concrete pathname to the
|
|
985
|
+
* MOST SPECIFIC matching entry — a literal segment beats a param beats a
|
|
986
|
+
* wildcard at the earliest differing segment, registration-order-independent.
|
|
987
|
+
* The shared engine both the `Navigator` (browser) and the `Dispatcher`
|
|
988
|
+
* (core, method-dimensioned) compose.
|
|
989
|
+
*
|
|
990
|
+
* @typeParam Meta - The opaque payload each entry carries and a match returns
|
|
991
|
+
*
|
|
992
|
+
* @remarks
|
|
993
|
+
* - `count` — the number of registered entries.
|
|
994
|
+
* - `add(entry)` / `add(entries)` — register ONE / MANY entries (batch registration);
|
|
995
|
+
* each path is compiled once here. When constructed with a `key` option,
|
|
996
|
+
* an entry whose key already exists replaces the prior one in place;
|
|
997
|
+
* otherwise every entry is kept, even duplicate paths.
|
|
998
|
+
* - `match(pathname, answers?)` — the MOST-SPECIFIC matching entry as a
|
|
999
|
+
* {@link RouterMatch} (its winning `path`, decoded `params`, `meta`, and
|
|
1000
|
+
* `name`), or `undefined`. The optional {@link AnswerHandler} predicate
|
|
1001
|
+
* filters candidates by `meta` first; omitted ⇒ every path match is
|
|
1002
|
+
* eligible.
|
|
1003
|
+
* - `entries()` — ALL registered entries in registration order.
|
|
1004
|
+
* - `entries(pathname)` — only entries whose path matches `pathname` (the
|
|
1005
|
+
* plural accessor's filtered form; backs a consumer's allow/405 set).
|
|
1006
|
+
* - `group(prefix)` — a {@link GroupInterface} scoped under `prefix`; entries
|
|
1007
|
+
* added through the group are registered on this same router with `prefix`
|
|
1008
|
+
* prepended to each path.
|
|
1009
|
+
* - `clear()` — drop every entry, leaving the router reusable.
|
|
1010
|
+
*/
|
|
1011
|
+
export declare interface RouterInterface<Meta> {
|
|
1012
|
+
readonly count: number;
|
|
1013
|
+
add(entry: RouteEntry<Meta>): void;
|
|
1014
|
+
add(entries: ReadonlyArray<RouteEntry<Meta>>): void;
|
|
1015
|
+
match(pathname: string, answers?: AnswerHandler<Meta>): RouterMatch<Meta> | undefined;
|
|
1016
|
+
entries(): ReadonlyArray<RouteEntry<Meta>>;
|
|
1017
|
+
entries(pathname: string): ReadonlyArray<RouteEntry<Meta>>;
|
|
1018
|
+
group(prefix: string): GroupInterface<Meta>;
|
|
1019
|
+
clear(): void;
|
|
1020
|
+
}
|
|
1008
1021
|
|
|
1009
|
-
/**
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
readonly entries?: ReadonlyArray<RouteEntry<Meta>>;
|
|
1030
|
-
readonly sensitive?: boolean;
|
|
1031
|
-
readonly key?: (entry: RouteEntry<Meta>) => string;
|
|
1032
|
-
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Represents one matched route — the winning entry's PATTERN, decoded params, `meta`
|
|
1024
|
+
* payload, and optional `name`.
|
|
1025
|
+
*
|
|
1026
|
+
* @typeParam Meta - The payload the winning entry carries
|
|
1027
|
+
*
|
|
1028
|
+
* @remarks
|
|
1029
|
+
* What {@link RouterInterface.match} returns on a hit (`undefined` on a
|
|
1030
|
+
* miss). `path` is the winning entry's REGISTERED PATTERN (not the concrete
|
|
1031
|
+
* pathname that was matched) — useful for consumers that need to know which
|
|
1032
|
+
* route fired. `params` is a frozen `name → value` record (empty for a
|
|
1033
|
+
* parameterless path), each value URL-decoded with a malformed `%` escape
|
|
1034
|
+
* tolerated as a literal, never throws. Plain data — no behavior.
|
|
1035
|
+
*/
|
|
1036
|
+
export declare interface RouterMatch<Meta> {
|
|
1037
|
+
readonly path: string;
|
|
1038
|
+
readonly params: Readonly<Record<string, string>>;
|
|
1039
|
+
readonly meta: Meta;
|
|
1040
|
+
readonly name?: string;
|
|
1041
|
+
}
|
|
1033
1042
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1043
|
+
/**
|
|
1044
|
+
* Represents the options for `createRouter` — an optional initial entry set, the case-
|
|
1045
|
+
* sensitivity toggle, and the dedup identity function.
|
|
1046
|
+
*
|
|
1047
|
+
* @typeParam Meta - The entry payload type
|
|
1048
|
+
*
|
|
1049
|
+
* @remarks
|
|
1050
|
+
* - `entries` — the initial `{ path, meta, name? }` entries to register (each
|
|
1051
|
+
* path compiled once), equivalent to a bare `createRouter()` followed by
|
|
1052
|
+
* `add(entries)`. Omitted ⇒ an empty router.
|
|
1053
|
+
* - `sensitive` — case-sensitive path matching (default `true`). Set
|
|
1054
|
+
* `false` to fold case during matching (`/Users` matches `/users`);
|
|
1055
|
+
* registered patterns are never case-folded in storage, only in matching.
|
|
1056
|
+
* - `key` — an optional dedup identity function computed per entry
|
|
1057
|
+
* (`RouteEntry<Meta> → string`). When provided, registering an entry whose
|
|
1058
|
+
* key already exists REPLACES the prior entry in place (last write wins,
|
|
1059
|
+
* no engine rebuild) instead of adding a second candidate. Omitted ⇒ every
|
|
1060
|
+
* registered entry is kept, even duplicate paths.
|
|
1061
|
+
*/
|
|
1062
|
+
export declare interface RouterOptions<Meta> {
|
|
1063
|
+
readonly entries?: ReadonlyArray<RouteEntry<Meta>>;
|
|
1064
|
+
readonly sensitive?: boolean;
|
|
1065
|
+
readonly key?: (entry: RouteEntry<Meta>) => string;
|
|
1066
|
+
}
|
|
1039
1067
|
|
|
1040
|
-
export declare type
|
|
1068
|
+
export declare type SegmentParam<Segment extends string> = Segment extends `:${infer Rest}` ? IdentifierHead<Rest> extends infer Name extends string ? Name extends '' ? unknown : {
|
|
1069
|
+
readonly [K in Name]: string;
|
|
1070
|
+
} : unknown : Segment extends `*${infer Rest}` ? IdentifierHead<Rest> extends infer Name extends string ? Name extends '' ? unknown : {
|
|
1071
|
+
readonly [K in Name]: string;
|
|
1072
|
+
} : unknown : unknown;
|
|
1041
1073
|
|
|
1042
|
-
|
|
1043
|
-
* Specificity tier for a **literal** path segment (`/users`) — the highest
|
|
1044
|
-
* tier, always outranking a param or wildcard segment at the same position.
|
|
1045
|
-
*
|
|
1046
|
-
* @remarks
|
|
1047
|
-
* Consumed by `computeSpecificity` (U1 `helpers.ts`) when ranking candidate
|
|
1048
|
-
* matches left-to-right at the earliest differing segment (§4 precedence).
|
|
1049
|
-
*
|
|
1050
|
-
* @example
|
|
1051
|
-
* ```ts
|
|
1052
|
-
* TIER_LITERAL > TIER_PARAM // true
|
|
1053
|
-
* ```
|
|
1054
|
-
*/
|
|
1055
|
-
export declare const TIER_LITERAL = 2;
|
|
1074
|
+
export declare type TakeIdentifierTail<S extends string, Acc extends string> = S extends `${infer Head}${infer Tail}` ? Head extends IdentifierChar ? TakeIdentifierTail<Tail, `${Acc}${Head}`> : Acc : Acc;
|
|
1056
1075
|
|
|
1057
|
-
/**
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
export declare const
|
|
1076
|
+
/**
|
|
1077
|
+
* Names the specificity tier for a **literal** path segment (`/users`) — the highest
|
|
1078
|
+
* tier, always outranking a param or wildcard segment at the same position.
|
|
1079
|
+
*
|
|
1080
|
+
* @remarks
|
|
1081
|
+
* Consumed by `computeSpecificity` (the path compiler in `helpers.ts`) when ranking candidate
|
|
1082
|
+
* matches left-to-right at the earliest differing segment.
|
|
1083
|
+
*
|
|
1084
|
+
* @example
|
|
1085
|
+
* ```ts
|
|
1086
|
+
* TIER_LITERAL > TIER_PARAM // true
|
|
1087
|
+
* ```
|
|
1088
|
+
*/
|
|
1089
|
+
export declare const TIER_LITERAL = 2;
|
|
1071
1090
|
|
|
1072
|
-
/**
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
export declare const
|
|
1091
|
+
/**
|
|
1092
|
+
* Names the specificity tier for a **param** path segment (`:name`) — ranks below a
|
|
1093
|
+
* literal segment and above a wildcard segment at the same position.
|
|
1094
|
+
*
|
|
1095
|
+
* @remarks
|
|
1096
|
+
* Consumed by `computeSpecificity` (the path compiler in `helpers.ts`) alongside {@link TIER_LITERAL}
|
|
1097
|
+
* and {@link TIER_WILDCARD}.
|
|
1098
|
+
*
|
|
1099
|
+
* @example
|
|
1100
|
+
* ```ts
|
|
1101
|
+
* TIER_PARAM > TIER_WILDCARD // true
|
|
1102
|
+
* ```
|
|
1103
|
+
*/
|
|
1104
|
+
export declare const TIER_PARAM = 1;
|
|
1105
|
+
|
|
1106
|
+
/**
|
|
1107
|
+
* Names the specificity tier for a **wildcard** path segment (`*name`) — the lowest
|
|
1108
|
+
* tier; a wildcard only ever wins against another wildcard shape (an
|
|
1109
|
+
* equal-specificity tie resolved by registration order).
|
|
1110
|
+
*
|
|
1111
|
+
* @remarks
|
|
1112
|
+
* Consumed by `computeSpecificity` (the path compiler in `helpers.ts`).
|
|
1113
|
+
*
|
|
1114
|
+
* @example
|
|
1115
|
+
* ```ts
|
|
1116
|
+
* TIER_WILDCARD // 0
|
|
1117
|
+
* ```
|
|
1118
|
+
*/
|
|
1119
|
+
export declare const TIER_WILDCARD = 0;
|
|
1086
1120
|
|
|
1087
|
-
export { }
|
|
1121
|
+
export { }
|