stx-router 0.2.148 → 0.2.150

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -23,7 +23,7 @@ export type {
23
23
  // File-based router
24
24
  export { createRouter, findErrorPage, formatRoutes, Router } from './file-router';
25
25
  // Route pattern matching
26
- export { filePathToPattern, matchRoute, patternToRegex } from './matcher';
26
+ export { bracketPathToRegex, filePathToPattern, matchRoute, patternToRegex } from './matcher';
27
27
  // Named routes
28
28
  export { defineRoute, defineRoutes, resetRoutes, route, setAppUrl } from './named-routes';
29
29
  // Middleware system
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createRouter, findErrorPage, formatRoutes, Router } from "./file-router.js";
2
- export { filePathToPattern, matchRoute, patternToRegex } from "./matcher.js";
2
+ export { bracketPathToRegex, filePathToPattern, matchRoute, patternToRegex } from "./matcher.js";
3
3
  export { defineRoute, defineRoutes, resetRoutes, route, setAppUrl } from "./named-routes.js";
4
4
  export {
5
5
  abortNavigation,
package/dist/matcher.d.ts CHANGED
@@ -1,3 +1,38 @@
1
1
  export declare function filePathToPattern(filePath: string, pagesDir: string): string;
2
+ /**
3
+ * Compile a route pattern into a regex and the parameter names its capture
4
+ * groups correspond to.
5
+ *
6
+ * Single left-to-right pass, which is the whole point. Doing it as three
7
+ * successive `.replace()` sweeps - optional, then catch-all, then required -
8
+ * collects the names in *sweep* order while the capture groups end up in
9
+ * *pattern* order, and the two disagree the moment one pattern mixes kinds.
10
+ *
11
+ * `/:owner/:repository/tree/:ref/:path*` collected as
12
+ * `[path, owner, repository, ref]` against groups `(owner)(repository)(ref)(path)`,
13
+ * so every value was bound to the wrong name: a repository browser given a URL
14
+ * showed the root of a repository named after the branch. Nothing errored -
15
+ * the regex matched and the page rendered - which is why it reads as a routing
16
+ * mystery rather than an off-by-one.
17
+ */
2
18
  export declare function patternToRegex(pattern: string): { regex: RegExp, params: string[] };
3
19
  export declare function matchRoute(pathname: string, routes: { pattern: string, regex: RegExp, params: string[] }[]): { route: typeof routes[0], params: Record<string, string> } | null;
20
+ /**
21
+ * Compile a *file-style* route path - the one with `[param]` brackets - into a
22
+ * regex and its parameter names.
23
+ *
24
+ * File-based routers keep re-deriving this by hand, and the hand-rolled version
25
+ * is always the same two lines:
26
+ *
27
+ * paramNames = [...p.matchAll(/\[([^\]]+)\]/g)].map(m => m[1])
28
+ * regex = p.replace(/\[([^\]]+)\]/g, '([^/]+)')
29
+ *
30
+ * which is correct for `[id]` and wrong for `[...path]` in two ways at once:
31
+ * the name keeps its dots, so a lookup by `path` finds nothing, and the segment
32
+ * compiles to `([^/]+)`, which cannot span a separator, so any multi-segment
33
+ * URL simply fails to match. Both symptoms, one cause, and neither is obvious
34
+ * from the outside - the route just quietly does not exist.
35
+ *
36
+ * Exported so nothing has to write those two lines again.
37
+ */
38
+ export declare function bracketPathToRegex(routePath: string): { regex: RegExp, params: string[] };
package/dist/matcher.js CHANGED
@@ -13,21 +13,33 @@ export function filePathToPattern(filePath, pagesDir) {
13
13
  }
14
14
  export function patternToRegex(pattern) {
15
15
  const params = [];
16
- let regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
17
- regexStr = regexStr.replace(/\/:([^/]+)\?/g, (_, name) => {
18
- params.push(name);
19
- return "(?:/([^/]+))?";
20
- });
21
- regexStr = regexStr.replace(/:([^/]+)\*/g, (_, name) => {
22
- params.push(name);
23
- return "(.+)";
24
- });
25
- regexStr = regexStr.replace(/:([^/]+)/g, (_, name) => {
26
- params.push(name);
27
- return "([^/]+)";
28
- });
29
- regexStr = `^${regexStr}$`;
30
- return { regex: new RegExp(regexStr), params };
16
+ let regexStr = "", i = 0;
17
+ while (i < pattern.length) {
18
+ const rest = pattern.slice(i), optional = /^\/:([^/?*]+)\?/.exec(rest);
19
+ if (optional) {
20
+ params.push(optional[1]);
21
+ regexStr += "(?:/([^/]+))?";
22
+ i += optional[0].length;
23
+ continue;
24
+ }
25
+ const catchAll = /^:([^/?*]+)\*/.exec(rest);
26
+ if (catchAll) {
27
+ params.push(catchAll[1]);
28
+ regexStr += "(.+)";
29
+ i += catchAll[0].length;
30
+ continue;
31
+ }
32
+ const required = /^:([^/?*]+)/.exec(rest);
33
+ if (required) {
34
+ params.push(required[1]);
35
+ regexStr += "([^/]+)";
36
+ i += required[0].length;
37
+ continue;
38
+ }
39
+ regexStr += pattern[i].replace(/[.+^${}()|[\]\\*?]/g, "\\$&");
40
+ i += 1;
41
+ }
42
+ return { regex: new RegExp(`^${regexStr}$`), params };
31
43
  }
32
44
  export function matchRoute(pathname, routes) {
33
45
  const normalizedPath = pathname === "" ? "/" : pathname;
@@ -43,3 +55,7 @@ export function matchRoute(pathname, routes) {
43
55
  }
44
56
  return null;
45
57
  }
58
+ export function bracketPathToRegex(routePath) {
59
+ const pattern = routePath.replace(/\[\[([^\]]+)\]\]/g, ":$1?").replace(/\[([^\]]+)\]/g, ":$1").replace(/:\.\.\.([^/]+)/g, ":$1*");
60
+ return patternToRegex(pattern);
61
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stx-router",
3
3
  "type": "module",
4
- "version": "0.2.148",
4
+ "version": "0.2.150",
5
5
  "description": "File-based router for STX — .stx template discovery, nested layouts, typed route params, middleware, and client-side SPA navigation.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",