@marianmeres/simple-router 3.0.1 → 3.1.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 +358 -0
- package/API.md +570 -0
- package/README.md +38 -9
- package/dist/router.d.ts +31 -26
- package/dist/router.js +17 -17
- package/package.json +11 -6
package/AGENTS.md
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
# @marianmeres/simple-router - AI Agent Reference
|
|
2
|
+
|
|
3
|
+
Machine-readable documentation for AI coding assistants.
|
|
4
|
+
|
|
5
|
+
## Package Metadata
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
name: "@marianmeres/simple-router"
|
|
9
|
+
version: "2.2.3"
|
|
10
|
+
runtime: ["deno", "node"]
|
|
11
|
+
module_system: "ESM"
|
|
12
|
+
entry_point: "src/mod.ts"
|
|
13
|
+
npm_dist: ".npm-dist/"
|
|
14
|
+
license: "MIT"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Core Concepts
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
purpose: "Framework-agnostic string pattern matcher and router"
|
|
21
|
+
use_cases:
|
|
22
|
+
- "SPA client-side routing"
|
|
23
|
+
- "File path matching"
|
|
24
|
+
- "Command pattern matching"
|
|
25
|
+
- "Any string identifier matching"
|
|
26
|
+
key_features:
|
|
27
|
+
- "Dynamic route parameters with optional regex constraints"
|
|
28
|
+
- "Wildcard and catch-all routes"
|
|
29
|
+
- "Spread parameters for multi-segment matching"
|
|
30
|
+
- "Query string parsing (toggleable per route)"
|
|
31
|
+
- "Reactive subscriptions (Svelte store compatible)"
|
|
32
|
+
- "First-match-wins routing"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## File Structure
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
source_files:
|
|
39
|
+
- path: "src/mod.ts"
|
|
40
|
+
purpose: "Main entry point, re-exports public API"
|
|
41
|
+
exports: ["SimpleRoute", "SimpleRouter", "RouteConfig", "RouteParams", "Logger", "RouteCallback", "RouterConfig", "RouterCurrent", "RouterOnOptions", "RouterOptions", "RouterSubscriber", "RouterUnsubscribe"]
|
|
42
|
+
|
|
43
|
+
- path: "src/route.ts"
|
|
44
|
+
purpose: "Low-level pattern parser"
|
|
45
|
+
exports: ["SimpleRoute", "RouteConfig", "RouteParams"]
|
|
46
|
+
class: "SimpleRoute"
|
|
47
|
+
|
|
48
|
+
- path: "src/router.ts"
|
|
49
|
+
purpose: "High-level router with pub/sub"
|
|
50
|
+
exports: ["SimpleRouter", "RouteParams", "Logger", "RouteCallback", "RouterConfig", "RouterCurrent", "RouterOnOptions", "RouterOptions", "RouterSubscriber", "RouterUnsubscribe"]
|
|
51
|
+
class: "SimpleRouter"
|
|
52
|
+
|
|
53
|
+
test_files:
|
|
54
|
+
- path: "tests/route.test.ts"
|
|
55
|
+
test_count: 63
|
|
56
|
+
coverage: "Pattern matching, query params, errors"
|
|
57
|
+
|
|
58
|
+
- path: "tests/router.test.ts"
|
|
59
|
+
test_count: 24
|
|
60
|
+
coverage: "Router lifecycle, subscriptions, logger, edge cases"
|
|
61
|
+
|
|
62
|
+
build_files:
|
|
63
|
+
- path: "scripts/build-npm.ts"
|
|
64
|
+
purpose: "Deno-to-npm build script"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Dependencies
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
runtime:
|
|
71
|
+
- name: "@marianmeres/pubsub"
|
|
72
|
+
version: "^2.4.1"
|
|
73
|
+
registry: "jsr"
|
|
74
|
+
purpose: "Reactive subscriptions"
|
|
75
|
+
|
|
76
|
+
dev_only:
|
|
77
|
+
- "@std/assert"
|
|
78
|
+
- "@std/fs"
|
|
79
|
+
- "@std/path"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## API Summary
|
|
83
|
+
|
|
84
|
+
### SimpleRouter
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
class: "SimpleRouter<T = unknown>"
|
|
88
|
+
purpose: "Main router for pattern matching and state management"
|
|
89
|
+
generic_param:
|
|
90
|
+
name: "T"
|
|
91
|
+
default: "unknown"
|
|
92
|
+
description: "Return type of route callbacks and exec() method"
|
|
93
|
+
|
|
94
|
+
static_properties:
|
|
95
|
+
- name: "debug"
|
|
96
|
+
type: "boolean"
|
|
97
|
+
default: false
|
|
98
|
+
description: "Enable debug logging (uses logger if provided, else console.log)"
|
|
99
|
+
|
|
100
|
+
constructor:
|
|
101
|
+
params:
|
|
102
|
+
- name: "config"
|
|
103
|
+
type: "RouterConfig<T> | RouterOptions<T> | null"
|
|
104
|
+
optional: true
|
|
105
|
+
description: "Route config or options object with routes and logger"
|
|
106
|
+
|
|
107
|
+
methods:
|
|
108
|
+
- name: "on"
|
|
109
|
+
signature: "(routes: string | string[], cb: RouteCallback<T>, options?: RouterOnOptions) => void"
|
|
110
|
+
description: "Register route(s) with callback"
|
|
111
|
+
important: "Routes matched in registration order - first match wins"
|
|
112
|
+
|
|
113
|
+
- name: "exec"
|
|
114
|
+
signature: "(url: string, fallbackFn?: RouteCallback<T>) => T | false"
|
|
115
|
+
description: "Execute pattern matching"
|
|
116
|
+
returns: "Callback result (type T), or false if no match"
|
|
117
|
+
|
|
118
|
+
- name: "subscribe"
|
|
119
|
+
signature: "(subscription: RouterSubscriber) => RouterUnsubscribe"
|
|
120
|
+
description: "Subscribe to state changes (Svelte store compatible)"
|
|
121
|
+
behavior: "Called immediately with current state"
|
|
122
|
+
returns: "Unsubscribe function directly (not wrapped object)"
|
|
123
|
+
|
|
124
|
+
- name: "reset"
|
|
125
|
+
signature: "() => this"
|
|
126
|
+
description: "Clear all routes (except catch-all)"
|
|
127
|
+
|
|
128
|
+
- name: "info"
|
|
129
|
+
signature: "() => Record<string, string>"
|
|
130
|
+
description: "Get route-to-label mapping"
|
|
131
|
+
|
|
132
|
+
properties:
|
|
133
|
+
- name: "current"
|
|
134
|
+
type: "RouterCurrent"
|
|
135
|
+
readonly: true
|
|
136
|
+
description: "Current router state"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### SimpleRoute
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
class: SimpleRoute
|
|
143
|
+
purpose: "Low-level pattern parser"
|
|
144
|
+
|
|
145
|
+
static_properties:
|
|
146
|
+
- name: "SPLITTER"
|
|
147
|
+
type: "string"
|
|
148
|
+
default: "/"
|
|
149
|
+
- name: "WILDCARD"
|
|
150
|
+
type: "string"
|
|
151
|
+
default: "*"
|
|
152
|
+
|
|
153
|
+
constructor:
|
|
154
|
+
params:
|
|
155
|
+
- name: "route"
|
|
156
|
+
type: "string"
|
|
157
|
+
description: "Pattern string"
|
|
158
|
+
throws: "Error if pattern invalid"
|
|
159
|
+
|
|
160
|
+
static_methods:
|
|
161
|
+
- name: "parseQueryString"
|
|
162
|
+
signature: "(str: string) => RouteParams"
|
|
163
|
+
description: "Parse query string to object"
|
|
164
|
+
|
|
165
|
+
methods:
|
|
166
|
+
- name: "parse"
|
|
167
|
+
signature: "(url: string, allowQueryParams?: boolean) => RouteParams | null"
|
|
168
|
+
description: "Match string against pattern"
|
|
169
|
+
returns: "Params object or null"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Pattern Syntax Reference
|
|
173
|
+
|
|
174
|
+
```yaml
|
|
175
|
+
patterns:
|
|
176
|
+
exact:
|
|
177
|
+
syntax: "literal"
|
|
178
|
+
example: "foo"
|
|
179
|
+
matches: "foo"
|
|
180
|
+
|
|
181
|
+
named_param:
|
|
182
|
+
syntax: "[name]"
|
|
183
|
+
example: "[id]"
|
|
184
|
+
captures: "{ id: 'value' }"
|
|
185
|
+
|
|
186
|
+
regex_param:
|
|
187
|
+
syntax: "[name(regex)]"
|
|
188
|
+
example: "[id([0-9]+)]"
|
|
189
|
+
captures: "{ id: '123' }"
|
|
190
|
+
constraint: "Must match regex"
|
|
191
|
+
|
|
192
|
+
optional:
|
|
193
|
+
syntax: "[name]?"
|
|
194
|
+
example: "[id]?"
|
|
195
|
+
captures: "{} or { id: 'value' }"
|
|
196
|
+
|
|
197
|
+
spread:
|
|
198
|
+
syntax: "[...name]"
|
|
199
|
+
example: "[...path]"
|
|
200
|
+
captures: "{ path: 'a/b/c' }"
|
|
201
|
+
constraint: "Only one per route, cannot be optional"
|
|
202
|
+
|
|
203
|
+
wildcard:
|
|
204
|
+
syntax: "*"
|
|
205
|
+
example: "/foo/*"
|
|
206
|
+
constraint: "Must be last segment"
|
|
207
|
+
captures: "{}"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Type Definitions
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
type RouteParams = Record<string, any>;
|
|
214
|
+
|
|
215
|
+
// Generic callback type - T is the return type
|
|
216
|
+
type RouteCallback<T = unknown> = (params: RouteParams | null, route: string) => T;
|
|
217
|
+
|
|
218
|
+
// Generic config type - T is the return type of all callbacks
|
|
219
|
+
type RouterConfig<T = unknown> = Record<string, RouteCallback<T>>;
|
|
220
|
+
|
|
221
|
+
interface Logger {
|
|
222
|
+
debug: (...args: unknown[]) => unknown;
|
|
223
|
+
log: (...args: unknown[]) => unknown;
|
|
224
|
+
warn: (...args: unknown[]) => unknown;
|
|
225
|
+
error: (...args: unknown[]) => unknown;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Generic options type - T is the return type of route callbacks
|
|
229
|
+
interface RouterOptions<T = unknown> {
|
|
230
|
+
routes?: RouterConfig<T> | null;
|
|
231
|
+
logger?: Logger | null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
interface RouterCurrent {
|
|
235
|
+
route: string | null;
|
|
236
|
+
params: RouteParams | null;
|
|
237
|
+
label: string | null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
interface RouterOnOptions {
|
|
241
|
+
label?: string | null;
|
|
242
|
+
allowQueryParams?: boolean;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
type RouterSubscriber = (current: RouterCurrent) => void;
|
|
246
|
+
|
|
247
|
+
type RouterUnsubscribe = () => void;
|
|
248
|
+
|
|
249
|
+
interface RouteConfig {
|
|
250
|
+
segment: string;
|
|
251
|
+
name: string | null;
|
|
252
|
+
test: RegExp;
|
|
253
|
+
isOptional: boolean;
|
|
254
|
+
isSpread: boolean;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Common Usage Patterns
|
|
259
|
+
|
|
260
|
+
### SPA Hash Routing (Typed)
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// Type-safe router - exec() returns Component | false
|
|
264
|
+
const router = new SimpleRouter<Component>({
|
|
265
|
+
"/": () => HomePage,
|
|
266
|
+
"/user/[id]": (params) => UserPage(params?.id),
|
|
267
|
+
"*": () => NotFoundPage
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
window.addEventListener("hashchange", () => {
|
|
271
|
+
const path = location.hash.slice(1) || "/";
|
|
272
|
+
const result = router.exec(path); // Component | false
|
|
273
|
+
if (result !== false) render(result);
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### File Path Matching
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const router = new SimpleRouter({
|
|
281
|
+
"src/[module]/[file].ts": ({ module, file }) => process(module, file),
|
|
282
|
+
"assets/[...path]": ({ path }) => loadAsset(path)
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Command Routing
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
const router = new SimpleRouter({
|
|
290
|
+
"user:create": () => createUser(),
|
|
291
|
+
"user:delete:[id]": ({ id }) => deleteUser(id)
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Important Behaviors
|
|
296
|
+
|
|
297
|
+
```yaml
|
|
298
|
+
behaviors:
|
|
299
|
+
- name: "First Match Wins"
|
|
300
|
+
description: "Routes matched in registration order"
|
|
301
|
+
implication: "Register specific routes before generic ones"
|
|
302
|
+
|
|
303
|
+
- name: "Separator Normalization"
|
|
304
|
+
description: "Multiple separators normalized, trimmed from ends"
|
|
305
|
+
example: "//foo//bar// matches foo/bar"
|
|
306
|
+
|
|
307
|
+
- name: "Query String Priority"
|
|
308
|
+
description: "Path params override same-name query params"
|
|
309
|
+
|
|
310
|
+
- name: "Catch-all Stored Separately"
|
|
311
|
+
description: "'*' route stored in #catchAll, not in #routes array"
|
|
312
|
+
|
|
313
|
+
- name: "Immediate Subscription Call"
|
|
314
|
+
description: "subscribe() calls callback immediately with current state"
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Error Conditions
|
|
318
|
+
|
|
319
|
+
```yaml
|
|
320
|
+
errors:
|
|
321
|
+
- condition: "Multiple spread segments"
|
|
322
|
+
message: "Multiple spread segments are invalid"
|
|
323
|
+
|
|
324
|
+
- condition: "Optional spread"
|
|
325
|
+
message: "Spread segment must not be marked as optional"
|
|
326
|
+
|
|
327
|
+
- condition: "Wildcard not last"
|
|
328
|
+
message: "Wildcard '*' can be used only as a last segment"
|
|
329
|
+
|
|
330
|
+
- condition: "Invalid regex in pattern"
|
|
331
|
+
message: "Invalid regex in route pattern '{segment}': {error}"
|
|
332
|
+
|
|
333
|
+
- condition: "Non-function subscriber"
|
|
334
|
+
throws: "TypeError: Subscription is not a function"
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Development Commands
|
|
338
|
+
|
|
339
|
+
```yaml
|
|
340
|
+
commands:
|
|
341
|
+
test: "deno task test"
|
|
342
|
+
build_npm: "deno task npm:build"
|
|
343
|
+
publish_npm: "deno task npm:publish"
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## Code Modification Guidelines
|
|
347
|
+
|
|
348
|
+
```yaml
|
|
349
|
+
guidelines:
|
|
350
|
+
- "Maintain Svelte store contract compatibility for subscribe()"
|
|
351
|
+
- "subscribe() returns unsubscribe function directly (not wrapped object)"
|
|
352
|
+
- "First-match-wins is core behavior - do not change"
|
|
353
|
+
- "Query param parsing must remain toggleable per-route"
|
|
354
|
+
- "Keep catch-all ('*') handling separate from regular routes"
|
|
355
|
+
- "URL encoding/decoding must be handled for params"
|
|
356
|
+
- "Logger interface must remain compatible with @marianmeres/clog"
|
|
357
|
+
- "Tests cover 87 cases - run before any changes"
|
|
358
|
+
```
|
package/API.md
ADDED
|
@@ -0,0 +1,570 @@
|
|
|
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
|
+
|
|
140
|
+
**Example:**
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// Single route
|
|
144
|
+
router.on("/users", () => UsersPage);
|
|
145
|
+
|
|
146
|
+
// Multiple routes to same handler
|
|
147
|
+
router.on(["/", "/home", "/index.html"], () => HomePage);
|
|
148
|
+
|
|
149
|
+
// With dynamic parameters
|
|
150
|
+
router.on("/user/[id]", (params) => UserPage(params?.id));
|
|
151
|
+
|
|
152
|
+
// With regex constraint
|
|
153
|
+
router.on("/post/[id([0-9]+)]", (params) => PostPage(params?.id));
|
|
154
|
+
|
|
155
|
+
// With label for debugging
|
|
156
|
+
router.on("/admin", () => AdminPage, { label: "admin-dashboard" });
|
|
157
|
+
|
|
158
|
+
// Disable query parameter parsing
|
|
159
|
+
router.on("/raw/[path]", (params) => RawPage(params), { allowQueryParams: false });
|
|
160
|
+
|
|
161
|
+
// Catch-all route (must be registered last)
|
|
162
|
+
router.on("*", () => NotFoundPage);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Important:** Routes are matched in registration order. First match wins!
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
#### `exec()`
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
exec(url: string, fallbackFn?: RouteCallback<T>): T | false
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Executes pattern matching against the provided string.
|
|
176
|
+
|
|
177
|
+
**Parameters:**
|
|
178
|
+
|
|
179
|
+
| Name | Type | Description |
|
|
180
|
+
|------|------|-------------|
|
|
181
|
+
| `url` | `string` | String to match against registered patterns |
|
|
182
|
+
| `fallbackFn` | `RouteCallback<T>` | Optional fallback function if no route matches |
|
|
183
|
+
|
|
184
|
+
**Returns:** `T | false` - The value returned by the matched callback (type `T`), or `false` if no match and no catch-all/fallback.
|
|
185
|
+
|
|
186
|
+
**Match Priority:**
|
|
187
|
+
1. First registered matching route
|
|
188
|
+
2. Fallback function (if provided)
|
|
189
|
+
3. Catch-all `"*"` route (if registered)
|
|
190
|
+
4. Returns `false`
|
|
191
|
+
|
|
192
|
+
**Example:**
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
// Basic usage
|
|
196
|
+
const result = router.exec("/users");
|
|
197
|
+
|
|
198
|
+
// With query parameters
|
|
199
|
+
router.exec("/search?q=hello&page=1");
|
|
200
|
+
|
|
201
|
+
// With fallback function
|
|
202
|
+
router.exec("/unknown", (params, route) => {
|
|
203
|
+
console.log("No match found");
|
|
204
|
+
return NotFoundPage;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Return values from callbacks
|
|
208
|
+
const component = router.exec("/home");
|
|
209
|
+
render(component);
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
#### `subscribe()`
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
subscribe(subscription: RouterSubscriber): RouterUnsubscribe
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Subscribes to router state changes. Follows the [Svelte store contract](https://svelte.dev/docs#Store_contract).
|
|
221
|
+
|
|
222
|
+
**Parameters:**
|
|
223
|
+
|
|
224
|
+
| Name | Type | Description |
|
|
225
|
+
|------|------|-------------|
|
|
226
|
+
| `subscription` | `RouterSubscriber` | Callback function receiving state changes |
|
|
227
|
+
|
|
228
|
+
**Returns:** `RouterUnsubscribe` - Unsubscribe function
|
|
229
|
+
|
|
230
|
+
**Behavior:**
|
|
231
|
+
- Subscriber is called immediately with current state
|
|
232
|
+
- Subsequently called on every `exec()` call
|
|
233
|
+
|
|
234
|
+
**Example:**
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
const unsubscribe = router.subscribe((state) => {
|
|
238
|
+
console.log("Route:", state.route);
|
|
239
|
+
console.log("Params:", state.params);
|
|
240
|
+
console.log("Label:", state.label);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// In Svelte
|
|
244
|
+
$: currentRoute = $router;
|
|
245
|
+
|
|
246
|
+
// Cleanup
|
|
247
|
+
unsubscribe();
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
#### `reset()`
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
reset(): this
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Clears all registered routes except the catch-all (`*`).
|
|
259
|
+
|
|
260
|
+
**Returns:** The router instance for method chaining
|
|
261
|
+
|
|
262
|
+
**Example:**
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
router.reset().on("/new-route", () => NewPage);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
#### `info()`
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
info(): Record<string, string>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Returns a map of registered route patterns to their labels.
|
|
277
|
+
|
|
278
|
+
**Returns:** Object mapping route patterns to labels (empty string if no label)
|
|
279
|
+
|
|
280
|
+
**Example:**
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
router.on("/users", () => {}, { label: "users-list" });
|
|
284
|
+
router.on("/posts", () => {}, { label: "posts-list" });
|
|
285
|
+
|
|
286
|
+
console.log(router.info());
|
|
287
|
+
// { "/users": "users-list", "/posts": "posts-list" }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## SimpleRoute
|
|
293
|
+
|
|
294
|
+
Low-level route pattern parser. Use this directly only when you need pattern matching without the full router functionality.
|
|
295
|
+
|
|
296
|
+
<a id="simpleroute-constructor"></a>
|
|
297
|
+
### Constructor
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
new SimpleRoute(pattern: string)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Creates a new route pattern parser.
|
|
304
|
+
|
|
305
|
+
**Parameters:**
|
|
306
|
+
|
|
307
|
+
| Name | Type | Description |
|
|
308
|
+
|------|------|-------------|
|
|
309
|
+
| `pattern` | `string` | Route pattern to match against |
|
|
310
|
+
|
|
311
|
+
**Throws:** `Error` if pattern is invalid (e.g., multiple spread segments, invalid regex)
|
|
312
|
+
|
|
313
|
+
**Example:**
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
const route = new SimpleRoute("/user/[id([0-9]+)]");
|
|
317
|
+
const route2 = new SimpleRoute("/files/[...path]");
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
<a id="simpleroute-static-properties"></a>
|
|
321
|
+
### Static Properties
|
|
322
|
+
|
|
323
|
+
#### `SPLITTER`
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
static SPLITTER: string = "/"
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Separator used to split paths into segments.
|
|
330
|
+
|
|
331
|
+
#### `WILDCARD`
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
static WILDCARD: string = "*"
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
Wildcard symbol for catch-all routes. Must be used as the last segment.
|
|
338
|
+
|
|
339
|
+
### Static Methods
|
|
340
|
+
|
|
341
|
+
#### `parseQueryString()`
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
static parseQueryString(str: string): RouteParams
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Parses a query string into an object of key-value pairs.
|
|
348
|
+
|
|
349
|
+
**Parameters:**
|
|
350
|
+
|
|
351
|
+
| Name | Type | Description |
|
|
352
|
+
|------|------|-------------|
|
|
353
|
+
| `str` | `string` | Query string (without leading "?") |
|
|
354
|
+
|
|
355
|
+
**Returns:** `RouteParams` - Object with parsed query parameters
|
|
356
|
+
|
|
357
|
+
**Example:**
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
SimpleRoute.parseQueryString("foo=bar&baz=123");
|
|
361
|
+
// { foo: "bar", baz: "123" }
|
|
362
|
+
|
|
363
|
+
SimpleRoute.parseQueryString("name=John%20Doe&city=New%20York");
|
|
364
|
+
// { name: "John Doe", city: "New York" }
|
|
365
|
+
|
|
366
|
+
SimpleRoute.parseQueryString("empty=&flag");
|
|
367
|
+
// { empty: "", flag: "" }
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Instance Methods
|
|
371
|
+
|
|
372
|
+
#### `parse()`
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
parse(url: string, allowQueryParams?: boolean): RouteParams | null
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Parses a string against this route pattern.
|
|
379
|
+
|
|
380
|
+
**Parameters:**
|
|
381
|
+
|
|
382
|
+
| Name | Type | Default | Description |
|
|
383
|
+
|------|------|---------|-------------|
|
|
384
|
+
| `url` | `string` | - | String to test against the pattern |
|
|
385
|
+
| `allowQueryParams` | `boolean` | `true` | Whether to parse query string parameters |
|
|
386
|
+
|
|
387
|
+
**Returns:** `RouteParams` object with extracted parameters, or `null` if no match
|
|
388
|
+
|
|
389
|
+
**Example:**
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
const route = new SimpleRoute("/user/[id]");
|
|
393
|
+
|
|
394
|
+
route.parse("/user/123");
|
|
395
|
+
// { id: "123" }
|
|
396
|
+
|
|
397
|
+
route.parse("/user/123?tab=profile");
|
|
398
|
+
// { id: "123", tab: "profile" }
|
|
399
|
+
|
|
400
|
+
route.parse("/user/123?tab=profile", false);
|
|
401
|
+
// { id: "123?tab=profile" }
|
|
402
|
+
|
|
403
|
+
route.parse("/post/456");
|
|
404
|
+
// null
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## Types
|
|
410
|
+
|
|
411
|
+
### RouteParams
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
type RouteParams = Record<string, any>;
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Object containing extracted route parameters. Keys are parameter names, values are the extracted string values.
|
|
418
|
+
|
|
419
|
+
### RouteCallback\<T\>
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
type RouteCallback<T = unknown> = (params: RouteParams | null, route: string) => T;
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Callback function executed when a route matches.
|
|
426
|
+
|
|
427
|
+
**Type Parameters:**
|
|
428
|
+
- `T` - The return type of the callback (default: `unknown`)
|
|
429
|
+
|
|
430
|
+
**Parameters:**
|
|
431
|
+
- `params` - Extracted route parameters (or `null` if no params)
|
|
432
|
+
- `route` - The matched route pattern string
|
|
433
|
+
|
|
434
|
+
**Returns:** `T` - The value to be returned by `exec()`
|
|
435
|
+
|
|
436
|
+
### RouterConfig\<T\>
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
type RouterConfig<T = unknown> = Record<string, RouteCallback<T>>;
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Configuration object mapping route patterns to callbacks.
|
|
443
|
+
|
|
444
|
+
**Type Parameters:**
|
|
445
|
+
- `T` - The return type of all route callbacks (default: `unknown`)
|
|
446
|
+
|
|
447
|
+
### RouterCurrent
|
|
448
|
+
|
|
449
|
+
```ts
|
|
450
|
+
interface RouterCurrent {
|
|
451
|
+
route: string | null; // Matched pattern, "*" for catch-all, null if no match
|
|
452
|
+
params: RouteParams | null;
|
|
453
|
+
label: string | null;
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Current router state object.
|
|
458
|
+
|
|
459
|
+
### RouterOnOptions
|
|
460
|
+
|
|
461
|
+
```ts
|
|
462
|
+
interface RouterOnOptions {
|
|
463
|
+
label?: string | null; // Optional label for debugging
|
|
464
|
+
allowQueryParams?: boolean; // Whether to parse query params (default: true)
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Options for the `on()` method.
|
|
469
|
+
|
|
470
|
+
### Logger
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
interface Logger {
|
|
474
|
+
debug: (...args: unknown[]) => unknown;
|
|
475
|
+
log: (...args: unknown[]) => unknown;
|
|
476
|
+
warn: (...args: unknown[]) => unknown;
|
|
477
|
+
error: (...args: unknown[]) => unknown;
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
Logger interface compatible with `@marianmeres/clog`. Provides console-compatible logging methods.
|
|
482
|
+
|
|
483
|
+
### RouterOptions\<T\>
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
interface RouterOptions<T = unknown> {
|
|
487
|
+
routes?: RouterConfig<T> | null; // Route configuration
|
|
488
|
+
logger?: Logger | null; // Optional logger instance
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Options object for the constructor (alternative to plain `RouterConfig`).
|
|
493
|
+
|
|
494
|
+
**Type Parameters:**
|
|
495
|
+
- `T` - The return type of route callbacks (default: `unknown`)
|
|
496
|
+
|
|
497
|
+
### RouterSubscriber
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
type RouterSubscriber = (current: RouterCurrent) => void;
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Callback function for route subscriptions.
|
|
504
|
+
|
|
505
|
+
### RouterUnsubscribe
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
type RouterUnsubscribe = () => void;
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
Unsubscribe function returned by `subscribe()`.
|
|
512
|
+
|
|
513
|
+
### RouteConfig (Internal)
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
interface RouteConfig {
|
|
517
|
+
segment: string;
|
|
518
|
+
name: string | null;
|
|
519
|
+
test: RegExp;
|
|
520
|
+
isOptional: boolean;
|
|
521
|
+
isSpread: boolean;
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
Internal configuration for a parsed route segment. Exported for advanced use cases.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Pattern Syntax
|
|
530
|
+
|
|
531
|
+
### Segment Types
|
|
532
|
+
|
|
533
|
+
| Pattern | Description | Example |
|
|
534
|
+
|---------|-------------|---------|
|
|
535
|
+
| `exact` | Literal match | `foo` matches "foo" |
|
|
536
|
+
| `[name]` | Named parameter | `[id]` captures any segment |
|
|
537
|
+
| `[name(regex)]` | Regex constrained | `[id([0-9]+)]` captures digits only |
|
|
538
|
+
| `[name]?` | Optional segment | `[id]?` matches with or without |
|
|
539
|
+
| `[...name]` | Spread parameter | `[...path]` captures multiple segments |
|
|
540
|
+
| `*` | Wildcard catch-all | Matches any remaining path |
|
|
541
|
+
|
|
542
|
+
### Key Behaviors
|
|
543
|
+
|
|
544
|
+
1. **Separator Normalization:** Multiple separators normalized to single; trimmed from ends
|
|
545
|
+
2. **First Match Wins:** Routes matched in registration order
|
|
546
|
+
3. **Query String Parsing:** Enabled by default, can disable per-route
|
|
547
|
+
4. **Path Params Priority:** Path params override query params with same name
|
|
548
|
+
5. **URL Decoding:** Both param names and values are URL-decoded
|
|
549
|
+
|
|
550
|
+
### Pattern Constraints
|
|
551
|
+
|
|
552
|
+
- Spread segments cannot be optional (`[...path]?` throws error)
|
|
553
|
+
- Only one spread segment allowed per route
|
|
554
|
+
- Wildcard `*` must be the last segment
|
|
555
|
+
- Regex in `[name(regex)]` must be valid JavaScript regex
|
|
556
|
+
|
|
557
|
+
### Examples
|
|
558
|
+
|
|
559
|
+
| Pattern | Input | Result |
|
|
560
|
+
|---------|-------|--------|
|
|
561
|
+
| `/foo` | `/bar` | `null` |
|
|
562
|
+
| `/foo` | `/foo` | `{}` |
|
|
563
|
+
| `/[foo]` | `/bar` | `{ foo: "bar" }` |
|
|
564
|
+
| `/[id([0-9]+)]` | `/123` | `{ id: "123" }` |
|
|
565
|
+
| `/[id([0-9]+)]` | `/abc` | `null` |
|
|
566
|
+
| `/foo/[bar]?` | `/foo` | `{}` |
|
|
567
|
+
| `/foo/[bar]?` | `/foo/baz` | `{ bar: "baz" }` |
|
|
568
|
+
| `/[...path]/[file]` | `/a/b/c.js` | `{ path: "a/b", file: "c.js" }` |
|
|
569
|
+
| `/*` | `/any/path` | `{}` |
|
|
570
|
+
| `/[foo]/*` | `/bar/baz` | `{ foo: "bar" }` |
|
package/README.md
CHANGED
|
@@ -255,17 +255,43 @@ SimpleRoute.parseQueryString("foo=bar&baz=123");
|
|
|
255
255
|
|
|
256
256
|
## TypeScript
|
|
257
257
|
|
|
258
|
-
Full TypeScript support with
|
|
258
|
+
Full TypeScript support with generic types for type-safe route callbacks and `exec()` return values.
|
|
259
|
+
|
|
260
|
+
### Generic Router
|
|
261
|
+
|
|
262
|
+
`SimpleRouter<T>` is generic over `T`, the return type of route callbacks:
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
// Typed router - all callbacks must return Component, exec() returns Component | false
|
|
266
|
+
const router = new SimpleRouter<Component>({
|
|
267
|
+
"/": () => HomePage,
|
|
268
|
+
"/about": () => AboutPage,
|
|
269
|
+
"*": () => NotFoundPage,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const result = router.exec("/about"); // Component | false
|
|
273
|
+
if (result !== false) {
|
|
274
|
+
render(result); // result is Component
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Without explicit type - T is inferred from callbacks (or defaults to unknown)
|
|
278
|
+
const router2 = new SimpleRouter({
|
|
279
|
+
"/": () => "home",
|
|
280
|
+
"/about": () => "about",
|
|
281
|
+
});
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Exported Types
|
|
259
285
|
|
|
260
286
|
```ts
|
|
261
287
|
import type {
|
|
262
288
|
Logger,
|
|
263
289
|
RouteParams,
|
|
264
|
-
RouteCallback,
|
|
265
|
-
RouterConfig,
|
|
290
|
+
RouteCallback, // RouteCallback<T = unknown>
|
|
291
|
+
RouterConfig, // RouterConfig<T = unknown>
|
|
266
292
|
RouterCurrent,
|
|
267
293
|
RouterOnOptions,
|
|
268
|
-
RouterOptions,
|
|
294
|
+
RouterOptions, // RouterOptions<T = unknown>
|
|
269
295
|
RouterSubscriber,
|
|
270
296
|
RouterUnsubscribe,
|
|
271
297
|
} from "@marianmeres/simple-router";
|
|
@@ -276,21 +302,24 @@ import type {
|
|
|
276
302
|
### SPA with Hash Routing
|
|
277
303
|
|
|
278
304
|
```ts
|
|
279
|
-
|
|
305
|
+
// Type-safe router with Component return type
|
|
306
|
+
const router = new SimpleRouter<Component>({
|
|
280
307
|
"/": () => HomePage,
|
|
281
308
|
"/about": () => AboutPage,
|
|
282
309
|
"/user/[id]": (params) => UserPage(params?.id),
|
|
283
310
|
"*": () => NotFoundPage,
|
|
284
311
|
});
|
|
285
312
|
|
|
286
|
-
function render(component) {
|
|
287
|
-
document.getElementById("app").innerHTML = component;
|
|
313
|
+
function render(component: Component) {
|
|
314
|
+
document.getElementById("app").innerHTML = component.render();
|
|
288
315
|
}
|
|
289
316
|
|
|
290
317
|
window.addEventListener("hashchange", () => {
|
|
291
318
|
const path = location.hash.slice(1) || "/";
|
|
292
|
-
const component = router.exec(path);
|
|
293
|
-
|
|
319
|
+
const component = router.exec(path); // Component | false
|
|
320
|
+
if (component !== false) {
|
|
321
|
+
render(component);
|
|
322
|
+
}
|
|
294
323
|
});
|
|
295
324
|
|
|
296
325
|
// Trigger initial render
|
package/dist/router.d.ts
CHANGED
|
@@ -12,19 +12,20 @@ export type RouteParams = Record<string, any>;
|
|
|
12
12
|
/**
|
|
13
13
|
* Callback function executed when a route matches.
|
|
14
14
|
*
|
|
15
|
+
* @typeParam T - The return type of the callback
|
|
15
16
|
* @param params - Extracted route parameters (null if no match)
|
|
16
17
|
* @param route - The matched route pattern string
|
|
17
18
|
* @returns Any value - useful for returning components, data, or control flow values
|
|
18
19
|
*
|
|
19
20
|
* @example
|
|
20
21
|
* ```ts
|
|
21
|
-
* const callback: RouteCallback = (params, route) => {
|
|
22
|
+
* const callback: RouteCallback<MyComponent> = (params, route) => {
|
|
22
23
|
* console.log(`Matched ${route} with params:`, params);
|
|
23
24
|
* return MyComponent;
|
|
24
25
|
* };
|
|
25
26
|
* ```
|
|
26
27
|
*/
|
|
27
|
-
export type RouteCallback = (params: RouteParams | null, route: string) =>
|
|
28
|
+
export type RouteCallback<T = unknown> = (params: RouteParams | null, route: string) => T;
|
|
28
29
|
/**
|
|
29
30
|
* Represents the current router state including the matched route,
|
|
30
31
|
* extracted parameters, and optional label.
|
|
@@ -50,16 +51,18 @@ export interface RouterOnOptions {
|
|
|
50
51
|
* Configuration object for initializing the router with routes.
|
|
51
52
|
* Maps route patterns to their callback functions.
|
|
52
53
|
*
|
|
54
|
+
* @typeParam T - The return type of route callbacks
|
|
55
|
+
*
|
|
53
56
|
* @example
|
|
54
57
|
* ```ts
|
|
55
|
-
* const config: RouterConfig = {
|
|
58
|
+
* const config: RouterConfig<Component> = {
|
|
56
59
|
* "/": () => HomePage,
|
|
57
60
|
* "/user/[id]": (params) => UserPage(params?.id),
|
|
58
61
|
* "*": () => NotFoundPage
|
|
59
62
|
* };
|
|
60
63
|
* ```
|
|
61
64
|
*/
|
|
62
|
-
export type RouterConfig = Record<string, RouteCallback
|
|
65
|
+
export type RouterConfig<T = unknown> = Record<string, RouteCallback<T>>;
|
|
63
66
|
/**
|
|
64
67
|
* Logger interface compatible with `@marianmeres/clog`.
|
|
65
68
|
* Provides console-compatible logging methods.
|
|
@@ -76,10 +79,12 @@ export interface Logger {
|
|
|
76
79
|
}
|
|
77
80
|
/**
|
|
78
81
|
* Options for the SimpleRouter factory/constructor.
|
|
82
|
+
*
|
|
83
|
+
* @typeParam T - The return type of route callbacks
|
|
79
84
|
*/
|
|
80
|
-
export interface RouterOptions {
|
|
85
|
+
export interface RouterOptions<T = unknown> {
|
|
81
86
|
/** Route configuration mapping patterns to callbacks */
|
|
82
|
-
routes?: RouterConfig | null;
|
|
87
|
+
routes?: RouterConfig<T> | null;
|
|
83
88
|
/** Optional logger instance for debug output (compatible with @marianmeres/clog) */
|
|
84
89
|
logger?: Logger | null;
|
|
85
90
|
}
|
|
@@ -123,8 +128,10 @@ export type RouterSubscriber = (current: RouterCurrent) => void;
|
|
|
123
128
|
*
|
|
124
129
|
* cmdRouter.exec("user:delete:123");
|
|
125
130
|
* ```
|
|
131
|
+
*
|
|
132
|
+
* @typeParam T - The return type of route callbacks (default: unknown)
|
|
126
133
|
*/
|
|
127
|
-
export declare class SimpleRouter {
|
|
134
|
+
export declare class SimpleRouter<T = unknown> {
|
|
128
135
|
#private;
|
|
129
136
|
/**
|
|
130
137
|
* Enable/disable console debug logging for route matching.
|
|
@@ -140,14 +147,14 @@ export declare class SimpleRouter {
|
|
|
140
147
|
*
|
|
141
148
|
* @example
|
|
142
149
|
* ```ts
|
|
143
|
-
* //
|
|
144
|
-
* const router = new SimpleRouter({
|
|
150
|
+
* // Typed router - exec() returns Component | false
|
|
151
|
+
* const router = new SimpleRouter<Component>({
|
|
145
152
|
* "/": () => HomePage,
|
|
146
153
|
* "/about": () => AboutPage
|
|
147
154
|
* });
|
|
148
155
|
*
|
|
149
156
|
* // With options object
|
|
150
|
-
* const router = new SimpleRouter({
|
|
157
|
+
* const router = new SimpleRouter<Component>({
|
|
151
158
|
* routes: {
|
|
152
159
|
* "/": () => HomePage,
|
|
153
160
|
* "/about": () => AboutPage
|
|
@@ -156,7 +163,7 @@ export declare class SimpleRouter {
|
|
|
156
163
|
* });
|
|
157
164
|
* ```
|
|
158
165
|
*/
|
|
159
|
-
constructor(config?: RouterConfig | RouterOptions | null);
|
|
166
|
+
constructor(config?: RouterConfig<T> | RouterOptions<T> | null);
|
|
160
167
|
/**
|
|
161
168
|
* Returns a map of registered route patterns to their labels.
|
|
162
169
|
* Useful for debugging and introspection.
|
|
@@ -224,38 +231,36 @@ export declare class SimpleRouter {
|
|
|
224
231
|
* router.on("*", () => NotFoundPage);
|
|
225
232
|
* ```
|
|
226
233
|
*/
|
|
227
|
-
on(routes: string | string[], cb: RouteCallback
|
|
234
|
+
on(routes: string | string[], cb: RouteCallback<T>, options?: RouterOnOptions): void;
|
|
228
235
|
/**
|
|
229
236
|
* Executes pattern matching against the provided string.
|
|
230
|
-
* Returns the value returned by the matched route's callback.
|
|
237
|
+
* Returns the value returned by the matched route's callback (type `T`).
|
|
231
238
|
* Routes are tested in registration order - first match wins.
|
|
232
239
|
*
|
|
233
240
|
* @param url - String to match against registered patterns (can be a URL, file path, command, etc.)
|
|
234
241
|
* @param fallbackFn - Optional fallback function if no route matches
|
|
235
|
-
* @returns The value returned by the matched callback, or false if no match
|
|
242
|
+
* @returns `T | false` - The value returned by the matched callback, or `false` if no match
|
|
236
243
|
*
|
|
237
244
|
* @example
|
|
238
245
|
* ```ts
|
|
239
|
-
* //
|
|
240
|
-
* router
|
|
246
|
+
* // Type-safe routing
|
|
247
|
+
* const router = new SimpleRouter<Component>({ "/home": () => HomePage });
|
|
248
|
+
* const result = router.exec("/home"); // Component | false
|
|
241
249
|
*
|
|
242
250
|
* // With query parameters
|
|
243
251
|
* router.exec("/users?sort=name");
|
|
244
252
|
*
|
|
245
|
-
* // File path matching
|
|
246
|
-
* router.exec("src/components/Button.ts");
|
|
247
|
-
*
|
|
248
|
-
* // Command matching
|
|
249
|
-
* router.exec("user:delete:123");
|
|
250
|
-
*
|
|
251
253
|
* // With fallback
|
|
252
|
-
* router.exec("/unknown", () =>
|
|
254
|
+
* router.exec("/unknown", () => NotFoundPage);
|
|
253
255
|
*
|
|
254
|
-
* //
|
|
255
|
-
* const component = router.exec("/
|
|
256
|
+
* // Check for no match
|
|
257
|
+
* const component = router.exec("/path");
|
|
258
|
+
* if (component !== false) {
|
|
259
|
+
* render(component);
|
|
260
|
+
* }
|
|
256
261
|
* ```
|
|
257
262
|
*/
|
|
258
|
-
exec(url: string, fallbackFn?: RouteCallback):
|
|
263
|
+
exec(url: string, fallbackFn?: RouteCallback<T>): T | false;
|
|
259
264
|
/**
|
|
260
265
|
* Subscribes to router state changes.
|
|
261
266
|
* Follows the Svelte store contract - subscriber is called immediately with current state.
|
package/dist/router.js
CHANGED
|
@@ -30,6 +30,8 @@ const PUBSUB_TOPIC = "current";
|
|
|
30
30
|
*
|
|
31
31
|
* cmdRouter.exec("user:delete:123");
|
|
32
32
|
* ```
|
|
33
|
+
*
|
|
34
|
+
* @typeParam T - The return type of route callbacks (default: unknown)
|
|
33
35
|
*/
|
|
34
36
|
export class SimpleRouter {
|
|
35
37
|
/**
|
|
@@ -55,14 +57,14 @@ export class SimpleRouter {
|
|
|
55
57
|
*
|
|
56
58
|
* @example
|
|
57
59
|
* ```ts
|
|
58
|
-
* //
|
|
59
|
-
* const router = new SimpleRouter({
|
|
60
|
+
* // Typed router - exec() returns Component | false
|
|
61
|
+
* const router = new SimpleRouter<Component>({
|
|
60
62
|
* "/": () => HomePage,
|
|
61
63
|
* "/about": () => AboutPage
|
|
62
64
|
* });
|
|
63
65
|
*
|
|
64
66
|
* // With options object
|
|
65
|
-
* const router = new SimpleRouter({
|
|
67
|
+
* const router = new SimpleRouter<Component>({
|
|
66
68
|
* routes: {
|
|
67
69
|
* "/": () => HomePage,
|
|
68
70
|
* "/about": () => AboutPage
|
|
@@ -196,32 +198,30 @@ export class SimpleRouter {
|
|
|
196
198
|
}
|
|
197
199
|
/**
|
|
198
200
|
* Executes pattern matching against the provided string.
|
|
199
|
-
* Returns the value returned by the matched route's callback.
|
|
201
|
+
* Returns the value returned by the matched route's callback (type `T`).
|
|
200
202
|
* Routes are tested in registration order - first match wins.
|
|
201
203
|
*
|
|
202
204
|
* @param url - String to match against registered patterns (can be a URL, file path, command, etc.)
|
|
203
205
|
* @param fallbackFn - Optional fallback function if no route matches
|
|
204
|
-
* @returns The value returned by the matched callback, or false if no match
|
|
206
|
+
* @returns `T | false` - The value returned by the matched callback, or `false` if no match
|
|
205
207
|
*
|
|
206
208
|
* @example
|
|
207
209
|
* ```ts
|
|
208
|
-
* //
|
|
209
|
-
* router
|
|
210
|
+
* // Type-safe routing
|
|
211
|
+
* const router = new SimpleRouter<Component>({ "/home": () => HomePage });
|
|
212
|
+
* const result = router.exec("/home"); // Component | false
|
|
210
213
|
*
|
|
211
214
|
* // With query parameters
|
|
212
215
|
* router.exec("/users?sort=name");
|
|
213
216
|
*
|
|
214
|
-
* // File path matching
|
|
215
|
-
* router.exec("src/components/Button.ts");
|
|
216
|
-
*
|
|
217
|
-
* // Command matching
|
|
218
|
-
* router.exec("user:delete:123");
|
|
219
|
-
*
|
|
220
217
|
* // With fallback
|
|
221
|
-
* router.exec("/unknown", () =>
|
|
218
|
+
* router.exec("/unknown", () => NotFoundPage);
|
|
222
219
|
*
|
|
223
|
-
* //
|
|
224
|
-
* const component = router.exec("/
|
|
220
|
+
* // Check for no match
|
|
221
|
+
* const component = router.exec("/path");
|
|
222
|
+
* if (component !== false) {
|
|
223
|
+
* render(component);
|
|
224
|
+
* }
|
|
225
225
|
* ```
|
|
226
226
|
*/
|
|
227
227
|
exec(url, fallbackFn) {
|
|
@@ -233,7 +233,7 @@ export class SimpleRouter {
|
|
|
233
233
|
if (params !== null) {
|
|
234
234
|
this.#publishCurrent(route.route, params, label);
|
|
235
235
|
this.#dbg(`${dbgPrefix}matches '${route.route}' with`, params);
|
|
236
|
-
return
|
|
236
|
+
return cb(params, route.route);
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
239
|
if (typeof fallbackFn === "function") {
|
package/package.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marianmeres/simple-router",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/mod.js",
|
|
6
6
|
"types": "dist/mod.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/mod.d.ts",
|
|
10
|
+
"import": "./dist/mod.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
7
13
|
"author": "Marian Meres",
|
|
8
14
|
"license": "MIT",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@marianmeres/pubsub": "^2.4.4"
|
|
17
|
+
},
|
|
9
18
|
"repository": {
|
|
10
19
|
"type": "git",
|
|
11
20
|
"url": "git+https://github.com/marianmeres/simple-router.git"
|
|
12
21
|
},
|
|
13
22
|
"bugs": {
|
|
14
23
|
"url": "https://github.com/marianmeres/simple-router/issues"
|
|
15
|
-
},
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"@marianmeres/pubsub": "^2.2.0",
|
|
18
|
-
"@marianmeres/clog": "^2.3.3"
|
|
19
24
|
}
|
|
20
|
-
}
|
|
25
|
+
}
|