@marianmeres/simple-router 3.0.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +399 -0
- package/API.md +597 -0
- package/CLAUDE.md +3 -0
- package/README.md +85 -13
- package/dist/mod.d.ts +1 -1
- package/dist/route.d.ts +18 -2
- package/dist/route.js +65 -17
- package/dist/router.d.ts +43 -26
- package/dist/router.js +56 -25
- package/package.json +19 -6
package/API.md
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
Full API documentation for `@marianmeres/simple-router`.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [SimpleRouter](#simplerouter)
|
|
8
|
+
- [Constructor](#constructor)
|
|
9
|
+
- [Static Properties](#static-properties)
|
|
10
|
+
- [Instance Properties](#instance-properties)
|
|
11
|
+
- [Methods](#methods)
|
|
12
|
+
- [SimpleRoute](#simpleroute)
|
|
13
|
+
- [Constructor](#simpleroute-constructor)
|
|
14
|
+
- [Static Properties](#simpleroute-static-properties)
|
|
15
|
+
- [Static Methods](#static-methods)
|
|
16
|
+
- [Instance Methods](#instance-methods)
|
|
17
|
+
- [Types](#types)
|
|
18
|
+
- [Pattern Syntax](#pattern-syntax)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## SimpleRouter
|
|
23
|
+
|
|
24
|
+
The main router class for registering routes, executing pattern matching, and subscribing to state changes.
|
|
25
|
+
|
|
26
|
+
`SimpleRouter` is generic over `T`, the return type of route callbacks. This allows for type-safe `exec()` return values.
|
|
27
|
+
|
|
28
|
+
### Constructor
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
new SimpleRouter<T = unknown>(config?: RouterConfig<T> | RouterOptions<T> | null)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Creates a new router instance.
|
|
35
|
+
|
|
36
|
+
**Type Parameters:**
|
|
37
|
+
|
|
38
|
+
| Name | Default | Description |
|
|
39
|
+
|------|---------|-------------|
|
|
40
|
+
| `T` | `unknown` | The return type of route callbacks |
|
|
41
|
+
|
|
42
|
+
**Parameters:**
|
|
43
|
+
|
|
44
|
+
| Name | Type | Description |
|
|
45
|
+
|------|------|-------------|
|
|
46
|
+
| `config` | `RouterConfig<T> \| RouterOptions<T> \| null` | Optional route configuration or options object |
|
|
47
|
+
|
|
48
|
+
**Example:**
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// Empty router (T defaults to unknown)
|
|
52
|
+
const router = new SimpleRouter();
|
|
53
|
+
|
|
54
|
+
// Typed router - all callbacks must return Component
|
|
55
|
+
const router = new SimpleRouter<Component>({
|
|
56
|
+
"/": () => HomePage,
|
|
57
|
+
"/about": () => AboutPage,
|
|
58
|
+
"*": () => NotFoundPage
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Simple config without explicit type (T inferred from callbacks)
|
|
62
|
+
const router = new SimpleRouter({
|
|
63
|
+
"/": () => HomePage,
|
|
64
|
+
"/about": () => AboutPage,
|
|
65
|
+
"*": () => NotFoundPage
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// With options object (for logger support)
|
|
69
|
+
const router = new SimpleRouter<Component>({
|
|
70
|
+
routes: {
|
|
71
|
+
"/": () => HomePage,
|
|
72
|
+
"/about": () => AboutPage
|
|
73
|
+
},
|
|
74
|
+
logger: myLogger // optional, compatible with @marianmeres/clog
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Static Properties
|
|
79
|
+
|
|
80
|
+
#### `debug`
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
static debug: boolean = false
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Enable/disable console debug logging for route matching. When enabled, logs detailed matching information using the logger instance (if provided) or falls back to `console.log`.
|
|
87
|
+
|
|
88
|
+
**Example:**
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
SimpleRouter.debug = true;
|
|
92
|
+
router.exec("/test"); // Logs: [SimpleRouter] '/test' -> matches '/' with {}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Instance Properties
|
|
96
|
+
|
|
97
|
+
#### `current`
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
get current(): RouterCurrent
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Returns the current router state containing the last matched route, extracted parameters, and label.
|
|
104
|
+
|
|
105
|
+
**Returns:** `RouterCurrent` - Current state object
|
|
106
|
+
|
|
107
|
+
**Example:**
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
router.exec("/user/123");
|
|
111
|
+
console.log(router.current);
|
|
112
|
+
// { route: "/user/[id]", params: { id: "123" }, label: null }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Methods
|
|
116
|
+
|
|
117
|
+
#### `on()`
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
on(routes: string | string[], cb: RouteCallback<T>, options?: RouterOnOptions): void
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Registers one or more route patterns with a callback function.
|
|
124
|
+
|
|
125
|
+
**Parameters:**
|
|
126
|
+
|
|
127
|
+
| Name | Type | Description |
|
|
128
|
+
|------|------|-------------|
|
|
129
|
+
| `routes` | `string \| string[]` | Single route pattern or array of patterns |
|
|
130
|
+
| `cb` | `RouteCallback<T>` | Callback function executed when route matches (must return `T`) |
|
|
131
|
+
| `options` | `RouterOnOptions` | Optional configuration object |
|
|
132
|
+
|
|
133
|
+
**Options:**
|
|
134
|
+
|
|
135
|
+
| Name | Type | Default | Description |
|
|
136
|
+
|------|------|---------|-------------|
|
|
137
|
+
| `label` | `string \| null` | `null` | Label for debugging/identification |
|
|
138
|
+
| `allowQueryParams` | `boolean` | `true` | Whether to parse query parameters |
|
|
139
|
+
| `strict` | `boolean` | _inherits from router_ | Strict segment matching (see `RouterOptions.strict`) |
|
|
140
|
+
|
|
141
|
+
**Example:**
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
// Single route
|
|
145
|
+
router.on("/users", () => UsersPage);
|
|
146
|
+
|
|
147
|
+
// Multiple routes to same handler
|
|
148
|
+
router.on(["/", "/home", "/index.html"], () => HomePage);
|
|
149
|
+
|
|
150
|
+
// With dynamic parameters
|
|
151
|
+
router.on("/user/[id]", (params) => UserPage(params?.id));
|
|
152
|
+
|
|
153
|
+
// With regex constraint
|
|
154
|
+
router.on("/post/[id([0-9]+)]", (params) => PostPage(params?.id));
|
|
155
|
+
|
|
156
|
+
// With label for debugging
|
|
157
|
+
router.on("/admin", () => AdminPage, { label: "admin-dashboard" });
|
|
158
|
+
|
|
159
|
+
// Disable query parameter parsing
|
|
160
|
+
router.on("/raw/[path]", (params) => RawPage(params), { allowQueryParams: false });
|
|
161
|
+
|
|
162
|
+
// Catch-all route (must be registered last)
|
|
163
|
+
router.on("*", () => NotFoundPage);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Important:** Routes are matched in registration order. First match wins!
|
|
167
|
+
|
|
168
|
+
**Shadowing warning:** When `SimpleRouter.debug` is enabled, registering a route that is strictly more specific than an already-registered route emits a `warn`-level log message (via the provided `Logger` if any, otherwise `console.warn`). The check is skipped when `debug` is off, so there is no runtime cost in production.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
#### `exec()`
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
exec(url: string, fallbackFn?: RouteCallback<T>): T | false
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Executes pattern matching against the provided string.
|
|
179
|
+
|
|
180
|
+
**Parameters:**
|
|
181
|
+
|
|
182
|
+
| Name | Type | Description |
|
|
183
|
+
|------|------|-------------|
|
|
184
|
+
| `url` | `string` | String to match against registered patterns |
|
|
185
|
+
| `fallbackFn` | `RouteCallback<T>` | Optional fallback function if no route matches |
|
|
186
|
+
|
|
187
|
+
**Returns:** `T | false` - The value returned by the matched callback (type `T`), or `false` if no match and no catch-all/fallback.
|
|
188
|
+
|
|
189
|
+
**Match Priority:**
|
|
190
|
+
1. First registered matching route
|
|
191
|
+
2. Fallback function (if provided)
|
|
192
|
+
3. Catch-all `"*"` route (if registered)
|
|
193
|
+
4. Returns `false`
|
|
194
|
+
|
|
195
|
+
**Example:**
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
// Basic usage
|
|
199
|
+
const result = router.exec("/users");
|
|
200
|
+
|
|
201
|
+
// With query parameters
|
|
202
|
+
router.exec("/search?q=hello&page=1");
|
|
203
|
+
|
|
204
|
+
// With fallback function
|
|
205
|
+
router.exec("/unknown", (params, route) => {
|
|
206
|
+
console.log("No match found");
|
|
207
|
+
return NotFoundPage;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Return values from callbacks
|
|
211
|
+
const component = router.exec("/home");
|
|
212
|
+
render(component);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
#### `subscribe()`
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
subscribe(subscription: RouterSubscriber): RouterUnsubscribe
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Subscribes to router state changes. Follows the [Svelte store contract](https://svelte.dev/docs#Store_contract).
|
|
224
|
+
|
|
225
|
+
**Parameters:**
|
|
226
|
+
|
|
227
|
+
| Name | Type | Description |
|
|
228
|
+
|------|------|-------------|
|
|
229
|
+
| `subscription` | `RouterSubscriber` | Callback function receiving state changes |
|
|
230
|
+
|
|
231
|
+
**Returns:** `RouterUnsubscribe` - Unsubscribe function
|
|
232
|
+
|
|
233
|
+
**Behavior:**
|
|
234
|
+
- Subscriber is called immediately with current state
|
|
235
|
+
- Subsequently called on every `exec()` call
|
|
236
|
+
|
|
237
|
+
**Example:**
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const unsubscribe = router.subscribe((state) => {
|
|
241
|
+
console.log("Route:", state.route);
|
|
242
|
+
console.log("Params:", state.params);
|
|
243
|
+
console.log("Label:", state.label);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// In Svelte
|
|
247
|
+
$: currentRoute = $router;
|
|
248
|
+
|
|
249
|
+
// Cleanup
|
|
250
|
+
unsubscribe();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
#### `reset()`
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
reset(): this
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Clears all registered routes except the catch-all (`*`).
|
|
262
|
+
|
|
263
|
+
**Returns:** The router instance for method chaining
|
|
264
|
+
|
|
265
|
+
**Example:**
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
router.reset().on("/new-route", () => NewPage);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
#### `info()`
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
info(): Record<string, string>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Returns a map of registered route patterns to their labels.
|
|
280
|
+
|
|
281
|
+
**Returns:** Object mapping route patterns to labels (empty string if no label)
|
|
282
|
+
|
|
283
|
+
**Example:**
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
router.on("/users", () => {}, { label: "users-list" });
|
|
287
|
+
router.on("/posts", () => {}, { label: "posts-list" });
|
|
288
|
+
|
|
289
|
+
console.log(router.info());
|
|
290
|
+
// { "/users": "users-list", "/posts": "posts-list" }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## SimpleRoute
|
|
296
|
+
|
|
297
|
+
Low-level route pattern parser. Use this directly only when you need pattern matching without the full router functionality.
|
|
298
|
+
|
|
299
|
+
<a id="simpleroute-constructor"></a>
|
|
300
|
+
### Constructor
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
new SimpleRoute(pattern: string, options?: SimpleRouteOptions)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Creates a new route pattern parser.
|
|
307
|
+
|
|
308
|
+
**Parameters:**
|
|
309
|
+
|
|
310
|
+
| Name | Type | Description |
|
|
311
|
+
|------|------|-------------|
|
|
312
|
+
| `pattern` | `string` | Route pattern to match against |
|
|
313
|
+
| `options` | `SimpleRouteOptions` | Optional configuration (currently `{ strict?: boolean }`) |
|
|
314
|
+
|
|
315
|
+
**Throws:** `Error` if pattern is invalid — multiple spread segments, invalid regex, invalid parameter name in a named constraint, or an optional segment followed by a required one.
|
|
316
|
+
|
|
317
|
+
**Example:**
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
const route = new SimpleRoute("/user/[id([0-9]+)]");
|
|
321
|
+
const route2 = new SimpleRoute("/files/[...path]");
|
|
322
|
+
|
|
323
|
+
// Strict mode — '/a//b' will NOT match '/a/b'
|
|
324
|
+
const strict = new SimpleRoute("/a/b", { strict: true });
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
<a id="simpleroute-static-properties"></a>
|
|
328
|
+
### Static Properties
|
|
329
|
+
|
|
330
|
+
#### `SPLITTER`
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
static SPLITTER: string = "/"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Separator used to split paths into segments.
|
|
337
|
+
|
|
338
|
+
#### `WILDCARD`
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
static WILDCARD: string = "*"
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Wildcard symbol for catch-all routes. Must be used as the last segment.
|
|
345
|
+
|
|
346
|
+
### Static Methods
|
|
347
|
+
|
|
348
|
+
#### `parseQueryString()`
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
static parseQueryString(str: string): RouteParams
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Parses a query string into an object of key-value pairs.
|
|
355
|
+
|
|
356
|
+
**Parameters:**
|
|
357
|
+
|
|
358
|
+
| Name | Type | Description |
|
|
359
|
+
|------|------|-------------|
|
|
360
|
+
| `str` | `string` | Query string (without leading "?") |
|
|
361
|
+
|
|
362
|
+
**Returns:** `RouteParams` - Object with parsed query parameters
|
|
363
|
+
|
|
364
|
+
**Example:**
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
SimpleRoute.parseQueryString("foo=bar&baz=123");
|
|
368
|
+
// { foo: "bar", baz: "123" }
|
|
369
|
+
|
|
370
|
+
SimpleRoute.parseQueryString("name=John%20Doe&city=New%20York");
|
|
371
|
+
// { name: "John Doe", city: "New York" }
|
|
372
|
+
|
|
373
|
+
SimpleRoute.parseQueryString("empty=&flag");
|
|
374
|
+
// { empty: "", flag: "" }
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Instance Methods
|
|
378
|
+
|
|
379
|
+
#### `parse()`
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
parse(url: string, allowQueryParams?: boolean): RouteParams | null
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Parses a string against this route pattern.
|
|
386
|
+
|
|
387
|
+
**Parameters:**
|
|
388
|
+
|
|
389
|
+
| Name | Type | Default | Description |
|
|
390
|
+
|------|------|---------|-------------|
|
|
391
|
+
| `url` | `string` | - | String to test against the pattern |
|
|
392
|
+
| `allowQueryParams` | `boolean` | `true` | Whether to parse query string parameters |
|
|
393
|
+
|
|
394
|
+
**Returns:** `RouteParams` object with extracted parameters, or `null` if no match
|
|
395
|
+
|
|
396
|
+
**Example:**
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
const route = new SimpleRoute("/user/[id]");
|
|
400
|
+
|
|
401
|
+
route.parse("/user/123");
|
|
402
|
+
// { id: "123" }
|
|
403
|
+
|
|
404
|
+
route.parse("/user/123?tab=profile");
|
|
405
|
+
// { id: "123", tab: "profile" }
|
|
406
|
+
|
|
407
|
+
route.parse("/user/123?tab=profile", false);
|
|
408
|
+
// { id: "123?tab=profile" }
|
|
409
|
+
|
|
410
|
+
route.parse("/post/456");
|
|
411
|
+
// null
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Types
|
|
417
|
+
|
|
418
|
+
### RouteParams
|
|
419
|
+
|
|
420
|
+
```ts
|
|
421
|
+
type RouteParams = Record<string, any>;
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Object containing extracted route parameters. Keys are parameter names, values are the extracted string values.
|
|
425
|
+
|
|
426
|
+
### RouteCallback\<T\>
|
|
427
|
+
|
|
428
|
+
```ts
|
|
429
|
+
type RouteCallback<T = unknown> = (params: RouteParams | null, route: string) => T;
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Callback function executed when a route matches.
|
|
433
|
+
|
|
434
|
+
**Type Parameters:**
|
|
435
|
+
- `T` - The return type of the callback (default: `unknown`)
|
|
436
|
+
|
|
437
|
+
**Parameters:**
|
|
438
|
+
- `params` - Extracted route parameters (or `null` if no params)
|
|
439
|
+
- `route` - The matched route pattern string
|
|
440
|
+
|
|
441
|
+
**Returns:** `T` - The value to be returned by `exec()`
|
|
442
|
+
|
|
443
|
+
### RouterConfig\<T\>
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
type RouterConfig<T = unknown> = Record<string, RouteCallback<T>>;
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Configuration object mapping route patterns to callbacks.
|
|
450
|
+
|
|
451
|
+
**Type Parameters:**
|
|
452
|
+
- `T` - The return type of all route callbacks (default: `unknown`)
|
|
453
|
+
|
|
454
|
+
### RouterCurrent
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
interface RouterCurrent {
|
|
458
|
+
route: string | null; // Matched pattern, "*" for catch-all, null if no match
|
|
459
|
+
params: RouteParams | null;
|
|
460
|
+
label: string | null;
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Current router state object.
|
|
465
|
+
|
|
466
|
+
### RouterOnOptions
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
interface RouterOnOptions {
|
|
470
|
+
label?: string | null; // Optional label for debugging
|
|
471
|
+
allowQueryParams?: boolean; // Whether to parse query params (default: true)
|
|
472
|
+
strict?: boolean; // Per-route strict mode (overrides router default)
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Options for the `on()` method.
|
|
477
|
+
|
|
478
|
+
### SimpleRouteOptions
|
|
479
|
+
|
|
480
|
+
```ts
|
|
481
|
+
interface SimpleRouteOptions {
|
|
482
|
+
strict?: boolean; // Disable empty-segment collapsing (default: false)
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Options for the `SimpleRoute` constructor.
|
|
487
|
+
|
|
488
|
+
### Logger
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
interface Logger {
|
|
492
|
+
debug: (...args: unknown[]) => unknown;
|
|
493
|
+
log: (...args: unknown[]) => unknown;
|
|
494
|
+
warn: (...args: unknown[]) => unknown;
|
|
495
|
+
error: (...args: unknown[]) => unknown;
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Logger interface compatible with `@marianmeres/clog`. Provides console-compatible logging methods.
|
|
500
|
+
|
|
501
|
+
### RouterOptions\<T\>
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
interface RouterOptions<T = unknown> {
|
|
505
|
+
routes?: RouterConfig<T> | null; // Route configuration
|
|
506
|
+
logger?: Logger | null; // Optional logger instance
|
|
507
|
+
strict?: boolean; // Strict mode; propagates to every route
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Options object for the constructor (alternative to plain `RouterConfig`).
|
|
512
|
+
|
|
513
|
+
**Type Parameters:**
|
|
514
|
+
- `T` - The return type of route callbacks (default: `unknown`)
|
|
515
|
+
|
|
516
|
+
**`strict` mode:** when `true`, empty segments in both patterns and inputs (e.g. `/a//b`) are preserved rather than collapsed. This means `/a//b` will NOT match the canonical pattern `/a/b` — useful for URL canonicalization and preventing accidental shadow-matching in environments where concatenation bugs can produce doubled separators. Per-route `RouterOnOptions.strict` overrides this default.
|
|
517
|
+
|
|
518
|
+
### RouterSubscriber
|
|
519
|
+
|
|
520
|
+
```ts
|
|
521
|
+
type RouterSubscriber = (current: RouterCurrent) => void;
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
Callback function for route subscriptions.
|
|
525
|
+
|
|
526
|
+
### RouterUnsubscribe
|
|
527
|
+
|
|
528
|
+
```ts
|
|
529
|
+
type RouterUnsubscribe = () => void;
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
Unsubscribe function returned by `subscribe()`.
|
|
533
|
+
|
|
534
|
+
### RouteConfig (Internal)
|
|
535
|
+
|
|
536
|
+
```ts
|
|
537
|
+
interface RouteConfig {
|
|
538
|
+
segment: string;
|
|
539
|
+
name: string | null;
|
|
540
|
+
test: RegExp;
|
|
541
|
+
isOptional: boolean;
|
|
542
|
+
isSpread: boolean;
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Internal configuration for a parsed route segment. Exported for advanced use cases.
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
## Pattern Syntax
|
|
551
|
+
|
|
552
|
+
### Segment Types
|
|
553
|
+
|
|
554
|
+
| Pattern | Description | Example |
|
|
555
|
+
|---------|-------------|---------|
|
|
556
|
+
| `exact` | Literal match | `foo` matches "foo" |
|
|
557
|
+
| `[name]` | Named parameter | `[id]` captures any segment |
|
|
558
|
+
| `[name(regex)]` | Regex constrained | `[id([0-9]+)]` captures digits only |
|
|
559
|
+
| `[name]?` | Optional segment | `[id]?` matches with or without |
|
|
560
|
+
| `[...name]` | Spread parameter | `[...path]` captures multiple segments |
|
|
561
|
+
| `*` | Wildcard catch-all | Matches any remaining path |
|
|
562
|
+
|
|
563
|
+
### Key Behaviors
|
|
564
|
+
|
|
565
|
+
1. **Separator Normalization:** Multiple separators are collapsed in non-strict mode (default); trimmed from ends. Enable `strict: true` to preserve them.
|
|
566
|
+
2. **First Match Wins:** Routes matched in registration order. Enable `SimpleRouter.debug` for shadowing warnings at registration time.
|
|
567
|
+
3. **Query String Parsing:** Enabled by default, can disable per-route.
|
|
568
|
+
4. **Path Params Priority:** Path params override query params with the same name.
|
|
569
|
+
5. **URL Decoding:** Parameter _values_ are URL-decoded. Parameter _names_ are taken literally from the pattern (not decoded).
|
|
570
|
+
|
|
571
|
+
### Pattern Constraints
|
|
572
|
+
|
|
573
|
+
- Spread segments cannot be optional (`[...path]?` throws error)
|
|
574
|
+
- Only one spread segment allowed per route
|
|
575
|
+
- Wildcard `*` must be the last segment
|
|
576
|
+
- Regex in `[name(regex)]` must be valid JavaScript regex
|
|
577
|
+
- In a named constraint `[name(regex)]`, `name` must match `/^\w+$/`
|
|
578
|
+
- An optional segment (`[x]?`) may not be followed by a required segment — optional segments are only allowed in trailing position (or directly before a wildcard `*`)
|
|
579
|
+
|
|
580
|
+
### Security (regex constraints)
|
|
581
|
+
|
|
582
|
+
Regex constraints are compiled as ordinary JavaScript regexes. Some patterns (notably nested quantifiers like `(a+)+`) can exhibit catastrophic backtracking on crafted inputs. Route patterns are author-controlled — **never** accept them from untrusted input. Prefer simple character classes (`[0-9]+`, `[a-z]+`) over nested quantifiers.
|
|
583
|
+
|
|
584
|
+
### Examples
|
|
585
|
+
|
|
586
|
+
| Pattern | Input | Result |
|
|
587
|
+
|---------|-------|--------|
|
|
588
|
+
| `/foo` | `/bar` | `null` |
|
|
589
|
+
| `/foo` | `/foo` | `{}` |
|
|
590
|
+
| `/[foo]` | `/bar` | `{ foo: "bar" }` |
|
|
591
|
+
| `/[id([0-9]+)]` | `/123` | `{ id: "123" }` |
|
|
592
|
+
| `/[id([0-9]+)]` | `/abc` | `null` |
|
|
593
|
+
| `/foo/[bar]?` | `/foo` | `{}` |
|
|
594
|
+
| `/foo/[bar]?` | `/foo/baz` | `{ bar: "baz" }` |
|
|
595
|
+
| `/[...path]/[file]` | `/a/b/c.js` | `{ path: "a/b", file: "c.js" }` |
|
|
596
|
+
| `/*` | `/any/path` | `{}` |
|
|
597
|
+
| `/[foo]/*` | `/bar/baz` | `{ foo: "bar" }` |
|
package/CLAUDE.md
ADDED