@marianmeres/simple-router 3.3.0 → 3.4.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 CHANGED
@@ -109,8 +109,8 @@ methods:
109
109
  important: "Routes matched in registration order - first match wins"
110
110
 
111
111
  - name: "exec"
112
- signature: "(url: string, fallbackFn?: RouteCallback<T>) => T | false"
113
- description: "Execute pattern matching"
112
+ signature: "(url: string, fallbackFn?: RouteCallback<T>, options?: { skipCatchAll?: boolean }) => T | false"
113
+ description: "Execute pattern matching. Pass { skipCatchAll: true } to match only real routes and return false on a miss instead of firing the registered '*' catch-all (an explicit fallbackFn is unaffected)."
114
114
  returns: "Callback result (type T), or false if no match"
115
115
 
116
116
  - name: "subscribe"
@@ -132,6 +132,11 @@ properties:
132
132
  type: "RouterCurrent"
133
133
  readonly: true
134
134
  description: "Current router state"
135
+
136
+ - name: "catchAll"
137
+ type: "RouteCallback<T> | null"
138
+ readonly: true
139
+ description: "The registered catch-all ('*') callback, or null. Does NOT execute it — lets a consumer fire the catch-all as a deliberate final step without re-scanning routes."
135
140
  ```
136
141
 
137
142
  ### SimpleRoute
package/API.md CHANGED
@@ -112,6 +112,25 @@ console.log(router.current);
112
112
  // { route: "/user/[id]", params: { id: "123" }, label: null }
113
113
  ```
114
114
 
115
+ #### `catchAll`
116
+
117
+ ```ts
118
+ get catchAll(): RouteCallback<T> | null
119
+ ```
120
+
121
+ Returns the registered catch-all (`*`) callback, or `null` if none was registered. Does **not** execute it — this lets a consumer fire the catch-all as a deliberate final step (e.g. after an `exec(url, undefined, { skipCatchAll: true })` pass across several routers) without re-scanning the registered routes.
122
+
123
+ **Returns:** `RouteCallback<T> | null` - The catch-all callback, or `null`
124
+
125
+ **Example:**
126
+
127
+ ```ts
128
+ const fallback = router.catchAll;
129
+ if (fallback) {
130
+ const result = fallback(null, "*"); // fire it manually
131
+ }
132
+ ```
133
+
115
134
  ### Methods
116
135
 
117
136
  #### `on()`
@@ -172,7 +191,7 @@ router.on("*", () => NotFoundPage);
172
191
  #### `exec()`
173
192
 
174
193
  ```ts
175
- exec(url: string, fallbackFn?: RouteCallback<T>): T | false
194
+ exec(url: string, fallbackFn?: RouteCallback<T>, options?: { skipCatchAll?: boolean }): T | false
176
195
  ```
177
196
 
178
197
  Executes pattern matching against the provided string.
@@ -183,15 +202,18 @@ Executes pattern matching against the provided string.
183
202
  |------|------|-------------|
184
203
  | `url` | `string` | String to match against registered patterns |
185
204
  | `fallbackFn` | `RouteCallback<T>` | Optional fallback function if no route matches |
205
+ | `options` | `{ skipCatchAll?: boolean }` | Optional. Set `skipCatchAll: true` to match only real routes and return `false` on a miss instead of firing the registered `*` catch-all. An explicit `fallbackFn` is unaffected. |
186
206
 
187
207
  **Returns:** `T | false` - The value returned by the matched callback (type `T`), or `false` if no match and no catch-all/fallback.
188
208
 
189
209
  **Match Priority:**
190
210
  1. First registered matching route
191
211
  2. Fallback function (if provided)
192
- 3. Catch-all `"*"` route (if registered)
212
+ 3. Catch-all `"*"` route (if registered, unless `skipCatchAll: true`)
193
213
  4. Returns `false`
194
214
 
215
+ When `skipCatchAll` turns a would-be catch-all into a miss, `current` and subscribers receive the same `{ route: null, params: null, label: null }` "no match" state as a true miss.
216
+
195
217
  **Example:**
196
218
 
197
219
  ```ts
@@ -210,6 +232,9 @@ router.exec("/unknown", (params, route) => {
210
232
  // Return values from callbacks
211
233
  const component = router.exec("/home");
212
234
  render(component);
235
+
236
+ // Skip the internal catch-all (e.g. when fanning a lookup across multiple routers)
237
+ router.exec("/unknown", undefined, { skipCatchAll: true }); // => false, even if "*" is registered
213
238
  ```
214
239
 
215
240
  ---
package/README.md CHANGED
@@ -187,7 +187,7 @@ router.on("/raw", (params) => RawPage, { allowQueryParams: false });
187
187
 
188
188
  **Important:** Routes are matched in registration order. First match wins! When `SimpleRouter.debug` is enabled, a warning is logged at registration time if a new route would be shadowed by an already-registered one.
189
189
 
190
- ##### `exec(url, fallbackFn?)`
190
+ ##### `exec(url, fallbackFn?, options?)`
191
191
 
192
192
  Execute route matching against a URL.
193
193
 
@@ -199,10 +199,15 @@ router.exec("/unknown", () => console.log("Not found"));
199
199
 
200
200
  // With query params
201
201
  router.exec("/search?q=hello");
202
+
203
+ // Skip the internal "*" catch-all: returns false on a miss even if "*" is registered
204
+ router.exec("/unknown", undefined, { skipCatchAll: true });
202
205
  ```
203
206
 
204
207
  Returns the value returned by the matched callback, or `false` if no match.
205
208
 
209
+ The optional `{ skipCatchAll: true }` flag matches **only real routes** and returns `false` on a miss, instead of falling back to the registered `*` catch-all. An explicitly passed `fallbackFn` is unaffected. This is useful when fanning a single lookup across multiple routers ("try real routes in A, then B, … and fire a catch-all only if none matched") — see the [`catchAll`](#catchall) getter for firing the deferred catch-all yourself.
210
+
206
211
  ##### `subscribe(callback)`
207
212
 
208
213
  Subscribe to router state changes. Follows the [Svelte store contract](https://svelte.dev/docs#Store_contract).
@@ -249,6 +254,15 @@ console.log(router.current);
249
254
  // { route: "/user/[id]", params: { id: "123" }, label: null }
250
255
  ```
251
256
 
257
+ ##### `catchAll`
258
+
259
+ Gets the registered catch-all (`*`) callback, or `null` if none was registered (readonly). Does **not** execute it — this lets you fire the catch-all as a deliberate final step (e.g. after a multi-router `exec(url, undefined, { skipCatchAll: true })` pass) without re-scanning routes.
260
+
261
+ ```ts
262
+ const fallback = router.catchAll;
263
+ if (fallback) fallback(null, "*");
264
+ ```
265
+
252
266
  ##### `static debug`
253
267
 
254
268
  Enable/disable debug logging. When enabled, uses the logger instance (if provided) or falls back to `console.log`.
package/dist/router.d.ts CHANGED
@@ -213,6 +213,13 @@ export declare class SimpleRouter<T = unknown> {
213
213
  * ```
214
214
  */
215
215
  get current(): RouterCurrent;
216
+ /**
217
+ * The registered catch-all (`*`) callback, or `null` if none was registered.
218
+ * Does NOT execute it — lets a consumer fire the catch-all as a deliberate
219
+ * final step (e.g. after `exec(url, undefined, { skipCatchAll: true })`)
220
+ * without re-scanning the registered routes.
221
+ */
222
+ get catchAll(): RouteCallback<T> | null;
216
223
  /**
217
224
  * Registers one or more route patterns with a callback.
218
225
  * Routes are matched in the order they are registered (first match wins).
@@ -251,6 +258,11 @@ export declare class SimpleRouter<T = unknown> {
251
258
  *
252
259
  * @param url - String to match against registered patterns (can be a URL, file path, command, etc.)
253
260
  * @param fallbackFn - Optional fallback function if no route matches
261
+ * @param options - Optional execution options. Set `skipCatchAll: true` to match
262
+ * only real routes and return `false` on a miss instead of falling
263
+ * back to the registered `*` catch-all. An explicitly passed
264
+ * `fallbackFn` is unaffected. Useful when fanning a single lookup
265
+ * across multiple routers and deferring the catch-all decision.
254
266
  * @returns `T | false` - The value returned by the matched callback, or `false` if no match
255
267
  *
256
268
  * @example
@@ -265,6 +277,9 @@ export declare class SimpleRouter<T = unknown> {
265
277
  * // With fallback
266
278
  * router.exec("/unknown", () => NotFoundPage);
267
279
  *
280
+ * // Skip the internal catch-all: a miss returns false even if "*" is registered
281
+ * router.exec("/unknown", undefined, { skipCatchAll: true });
282
+ *
268
283
  * // Check for no match
269
284
  * const component = router.exec("/path");
270
285
  * if (component !== false) {
@@ -272,7 +287,9 @@ export declare class SimpleRouter<T = unknown> {
272
287
  * }
273
288
  * ```
274
289
  */
275
- exec(url: string, fallbackFn?: RouteCallback<T>): T | false;
290
+ exec(url: string, fallbackFn?: RouteCallback<T>, options?: {
291
+ skipCatchAll?: boolean;
292
+ }): T | false;
276
293
  /**
277
294
  * Subscribes to router state changes.
278
295
  * Follows the Svelte store contract - subscriber is called immediately with current state.
package/dist/router.js CHANGED
@@ -150,6 +150,15 @@ export class SimpleRouter {
150
150
  get current() {
151
151
  return this.#current;
152
152
  }
153
+ /**
154
+ * The registered catch-all (`*`) callback, or `null` if none was registered.
155
+ * Does NOT execute it — lets a consumer fire the catch-all as a deliberate
156
+ * final step (e.g. after `exec(url, undefined, { skipCatchAll: true })`)
157
+ * without re-scanning the registered routes.
158
+ */
159
+ get catchAll() {
160
+ return this.#catchAll;
161
+ }
153
162
  /**
154
163
  * Registers one or more route patterns with a callback.
155
164
  * Routes are matched in the order they are registered (first match wins).
@@ -234,6 +243,11 @@ export class SimpleRouter {
234
243
  *
235
244
  * @param url - String to match against registered patterns (can be a URL, file path, command, etc.)
236
245
  * @param fallbackFn - Optional fallback function if no route matches
246
+ * @param options - Optional execution options. Set `skipCatchAll: true` to match
247
+ * only real routes and return `false` on a miss instead of falling
248
+ * back to the registered `*` catch-all. An explicitly passed
249
+ * `fallbackFn` is unaffected. Useful when fanning a single lookup
250
+ * across multiple routers and deferring the catch-all decision.
237
251
  * @returns `T | false` - The value returned by the matched callback, or `false` if no match
238
252
  *
239
253
  * @example
@@ -248,6 +262,9 @@ export class SimpleRouter {
248
262
  * // With fallback
249
263
  * router.exec("/unknown", () => NotFoundPage);
250
264
  *
265
+ * // Skip the internal catch-all: a miss returns false even if "*" is registered
266
+ * router.exec("/unknown", undefined, { skipCatchAll: true });
267
+ *
251
268
  * // Check for no match
252
269
  * const component = router.exec("/path");
253
270
  * if (component !== false) {
@@ -255,7 +272,7 @@ export class SimpleRouter {
255
272
  * }
256
273
  * ```
257
274
  */
258
- exec(url, fallbackFn) {
275
+ exec(url, fallbackFn, options) {
259
276
  const dbgPrefix = `'${url}' -> `;
260
277
  for (const [route, cb, allowQueryParams, label] of this.#routes) {
261
278
  // First match wins
@@ -272,7 +289,7 @@ export class SimpleRouter {
272
289
  this.#dbg(`${dbgPrefix}fallback...`);
273
290
  return fallbackFn(null, "");
274
291
  }
275
- if (typeof this.#catchAll === "function") {
292
+ if (!options?.skipCatchAll && typeof this.#catchAll === "function") {
276
293
  this.#publishCurrent("*", null, null);
277
294
  this.#dbg(`${dbgPrefix}catchall...`);
278
295
  return this.#catchAll(null, "*");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/simple-router",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/mod.js",
6
6
  "types": "dist/mod.d.ts",