@owlmeans/router 0.1.18-rc.32 → 0.1.18-rc.33

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 CHANGED
@@ -11,7 +11,7 @@ Internal router service for the OwlMeans entrypoint system.
11
11
  ## Installation
12
12
 
13
13
  ```bash
14
- bun add @owlmeans/router@^0.1.18-rc.32
14
+ bun add @owlmeans/router@^0.1.18-rc.33
15
15
  ```
16
16
 
17
17
  ## Related Packages
@@ -28,7 +28,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
28
28
  your project's skill store (`.agents/skills/`):
29
29
 
30
30
  ```sh
31
- npx @owlmeans/agent-skills@^0.1.18-rc.48
31
+ npx @owlmeans/agent-skills@^0.1.18-rc.49
32
32
  ```
33
33
 
34
34
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/router",
4
- "version": "0.1.18-rc.32",
5
- "generatedAt": "2026-10-05T08:32:19.122Z",
4
+ "version": "0.1.18-rc.33",
5
+ "generatedAt": "2026-10-05T21:03:06.399Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/router
9
9
 
10
10
  **Layer:** Core (L1)
11
- **Install:** `"@owlmeans/router": "^0.1.18-rc.32"` in `dependencies`
11
+ **Install:** `"@owlmeans/router": "^0.1.18-rc.33"` in `dependencies`
12
12
 
13
13
  `@owlmeans/router` is the **plugin host** for OwlMeans UI routing. It does not talk to any
14
14
  concrete router; it defines the contract, holds a registry of routing plugins, and selects the
package/build/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export type * from './types.js';
2
+ export type * from './matcher/types.js';
2
3
  export * from './consts.js';
3
4
  export * from './service.js';
4
5
  export * from './matcher.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,YAAY,CAAA;AAC/B,mBAAmB,oBAAoB,CAAA;AACvC,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA"}
@@ -0,0 +1,31 @@
1
+ import type { PatternSegment, RouteBranch, RouteMatch, RouteObject } from '../types.js';
2
+ /**
3
+ * Pure, DOM-free route matcher. Supports exactly the route-description subset used
4
+ * across the OwlMeans projects: static segments, `:param` dynamic segments, nested
5
+ * (parent/child) routes and index routes. Splat (`*`) and optional (`:x?`) segments
6
+ * are intentionally not implemented yet — a seam is reserved so they can be added
7
+ * without changing the public shape.
8
+ *
9
+ * Segment weights mirror react-router's ranking intuition (static beats dynamic,
10
+ * more specific / deeper beats shallower).
11
+ */
12
+ export interface RouteMatcherHelper {
13
+ /** The non-empty `/`-separated parts of a path. */
14
+ splitPath: (path?: string) => string[];
15
+ /** The pattern segments of a route path: static, `:param` or splat. */
16
+ segmentsOf: (path?: string) => PatternSegment[];
17
+ /**
18
+ * Flatten a route tree into ranked-matchable branches. Every leaf and every node
19
+ * carrying an index child produces a branch.
20
+ */
21
+ flattenRoutes: (routes: RouteObject[], parentChain?: RouteObject[], parentSegments?: PatternSegment[]) => RouteBranch[];
22
+ /** Rank branches so the most specific match is tried first. Stable within equal score. */
23
+ rankRouteBranches: (branches: RouteBranch[]) => RouteBranch[];
24
+ /**
25
+ * Match a pathname against ranked branches. Returns the winning branch as a
26
+ * root→leaf chain of `RouteMatch` (params merged across the whole chain so every
27
+ * depth sees the full leaf param set, matching react-router's `useParams`).
28
+ */
29
+ matchRoutes: (branches: RouteBranch[], pathname: string) => RouteMatch[] | null;
30
+ }
31
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/matcher/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAEvF;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,MAAM,EAAE,CAAA;IACtC,uEAAuE;IACvE,UAAU,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,cAAc,EAAE,CAAA;IAC/C;;;OAGG;IACH,aAAa,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,WAAW,CAAC,EAAE,WAAW,EAAE,EAAE,cAAc,CAAC,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,CAAA;IACvH,0FAA0F;IAC1F,iBAAiB,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,CAAA;IAC7D;;;;OAIG;IACH,WAAW,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,UAAU,EAAE,GAAG,IAAI,CAAA;CAChF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/matcher/types.ts"],"names":[],"mappings":""}
@@ -1,27 +1,4 @@
1
- import type { RouteObject, PatternSegment, RouteBranch, RouteMatch } from './types.js';
2
- /**
3
- * Pure, DOM-free route matcher. Supports exactly the route-description subset used
4
- * across the OwlMeans projects: static segments, `:param` dynamic segments, nested
5
- * (parent/child) routes and index routes. Splat (`*`) and optional (`:x?`) segments
6
- * are intentionally not implemented yet — a seam is reserved so they can be added
7
- * without changing the public shape.
8
- *
9
- * Segment weights mirror react-router's ranking intuition (static beats dynamic,
10
- * more specific / deeper beats shallower).
11
- */
12
- export declare const splitPath: (path?: string) => string[];
13
- export declare const segmentsOf: (path?: string) => PatternSegment[];
14
- /**
15
- * Flatten a route tree into ranked-matchable branches. Every leaf and every node
16
- * carrying an index child produces a branch.
17
- */
18
- export declare const flattenRoutes: (routes: RouteObject[], parentChain?: RouteObject[], parentSegments?: PatternSegment[]) => RouteBranch[];
19
- /** Rank branches so the most specific match is tried first. Stable within equal score. */
20
- export declare const rankRouteBranches: (branches: RouteBranch[]) => RouteBranch[];
21
- /**
22
- * Match a pathname against ranked branches. Returns the winning branch as a
23
- * root→leaf chain of `RouteMatch` (params merged across the whole chain so every
24
- * depth sees the full leaf param set, matching react-router's `useParams`).
25
- */
26
- export declare const matchRoutes: (branches: RouteBranch[], pathname: string) => RouteMatch[] | null;
1
+ import type { RouteMatcherHelper } from './matcher/types.js';
2
+ export declare const createRouteMatcherHelper: () => RouteMatcherHelper;
3
+ export declare const routeMatcherHelper: RouteMatcherHelper;
27
4
  //# sourceMappingURL=matcher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAe,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAGnG;;;;;;;;;GASG;AAIH,eAAO,MAAM,SAAS,UAAW,MAAM,KAAG,MAAM,EACS,CAAA;AAEzD,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,cAAc,EAKrD,CAAA;AAEJ;;;GAGG;AACH,eAAO,MAAM,aAAa,WAChB,WAAW,EAAE,gBACR,WAAW,EAAE,mBACV,cAAc,EAAE,KAC/B,WAAW,EAuBb,CAAA;AAaD,0FAA0F;AAC1F,eAAO,MAAM,iBAAiB,aAAc,WAAW,EAAE,KAAG,WAAW,EAKvC,CAAA;AAsBhC;;;;GAIG;AACH,eAAO,MAAM,WAAW,aAAc,WAAW,EAAE,YAAY,MAAM,KAAG,UAAU,EAAE,GAAG,IAgBtF,CAAA"}
1
+ {"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAE5D,eAAO,MAAM,wBAAwB,QAAO,kBAiG3C,CAAA;AAED,eAAO,MAAM,kBAAkB,oBAA6B,CAAA"}
package/build/matcher.js CHANGED
@@ -1,105 +1,89 @@
1
1
  import { EMPTY_WEIGHT, INDEX_WEIGHT, PARAM, PARAM_WEIGHT, SEP, SPLAT, STATIC_WEIGHT } from './consts.local.js';
2
- /**
3
- * Pure, DOM-free route matcher. Supports exactly the route-description subset used
4
- * across the OwlMeans projects: static segments, `:param` dynamic segments, nested
5
- * (parent/child) routes and index routes. Splat (`*`) and optional (`:x?`) segments
6
- * are intentionally not implemented yet — a seam is reserved so they can be added
7
- * without changing the public shape.
8
- *
9
- * Segment weights mirror react-router's ranking intuition (static beats dynamic,
10
- * more specific / deeper beats shallower).
11
- */
12
- export const splitPath = (path) => (path ?? '').split(SEP).filter(part => part.length > 0);
13
- export const segmentsOf = (path) => splitPath(path).map(part => {
14
- if (part === SPLAT)
15
- return { kind: 'splat', value: SPLAT };
16
- if (part.startsWith(PARAM))
17
- return { kind: 'param', value: part.slice(PARAM.length) };
18
- return { kind: 'static', value: part };
19
- });
20
- /**
21
- * Flatten a route tree into ranked-matchable branches. Every leaf and every node
22
- * carrying an index child produces a branch.
23
- */
24
- export const flattenRoutes = (routes, parentChain = [], parentSegments = []) => {
25
- const branches = [];
26
- for (const route of routes) {
27
- const chain = [...parentChain, route];
28
- if (route.index === true) {
29
- // An index route contributes an empty terminal at the parent's path.
30
- branches.push({ chain, segments: parentSegments, index: true, score: 0 });
31
- continue;
2
+ export const createRouteMatcherHelper = () => {
3
+ const splitPath = (path) => (path ?? '').split(SEP).filter(part => part.length > 0);
4
+ const segmentsOf = (path) => splitPath(path).map(part => {
5
+ if (part === SPLAT)
6
+ return { kind: 'splat', value: SPLAT };
7
+ if (part.startsWith(PARAM))
8
+ return { kind: 'param', value: part.slice(PARAM.length) };
9
+ return { kind: 'static', value: part };
10
+ });
11
+ const flattenRoutes = (routes, parentChain = [], parentSegments = []) => {
12
+ const branches = [];
13
+ for (const route of routes) {
14
+ const chain = [...parentChain, route];
15
+ if (route.index === true) {
16
+ // An index route contributes an empty terminal at the parent's path.
17
+ branches.push({ chain, segments: parentSegments, index: true, score: 0 });
18
+ continue;
19
+ }
20
+ const segments = [...parentSegments, ...segmentsOf(route.path)];
21
+ const children = route.children ?? [];
22
+ if (children.length === 0) {
23
+ branches.push({ chain, segments, index: false, score: 0 });
24
+ }
25
+ else {
26
+ branches.push(...flattenRoutes(children, chain, segments));
27
+ }
32
28
  }
33
- const segments = [...parentSegments, ...segmentsOf(route.path)];
34
- const children = route.children ?? [];
35
- if (children.length === 0) {
36
- branches.push({ chain, segments, index: false, score: 0 });
29
+ return branches;
30
+ };
31
+ const scoreBranch = (branch) => {
32
+ let score = branch.segments.reduce((acc, seg) => {
33
+ if (seg.kind === 'static')
34
+ return acc + STATIC_WEIGHT;
35
+ if (seg.kind === 'param')
36
+ return acc + PARAM_WEIGHT;
37
+ return acc + EMPTY_WEIGHT;
38
+ }, 0);
39
+ if (branch.segments.length === 0)
40
+ score += EMPTY_WEIGHT;
41
+ if (branch.index)
42
+ score += INDEX_WEIGHT;
43
+ return score;
44
+ };
45
+ const rankRouteBranches = (branches) => branches
46
+ .map(branch => ({ ...branch, score: scoreBranch(branch) }))
47
+ .map((branch, index) => ({ branch, index }))
48
+ .sort((a, b) => (b.branch.score - a.branch.score) || (a.index - b.index))
49
+ .map(({ branch }) => branch);
50
+ const matchBranch = (branch, parts) => {
51
+ if (branch.segments.some(seg => seg.kind === 'splat')) {
52
+ throw new Error('router matcher: splat (*) segments are not implemented yet');
37
53
  }
38
- else {
39
- branches.push(...flattenRoutes(children, chain, segments));
54
+ // Exact-length match (no splats/optionals in the supported subset).
55
+ if (branch.segments.length !== parts.length)
56
+ return null;
57
+ const params = {};
58
+ for (let i = 0; i < branch.segments.length; i++) {
59
+ const seg = branch.segments[i];
60
+ const part = parts[i];
61
+ if (seg.kind === 'static') {
62
+ if (seg.value.toLowerCase() !== part.toLowerCase())
63
+ return null;
64
+ }
65
+ else {
66
+ params[seg.value] = decodeURIComponent(part);
67
+ }
40
68
  }
41
- }
42
- return branches;
43
- };
44
- const scoreBranch = (branch) => {
45
- let score = branch.segments.reduce((acc, seg) => {
46
- if (seg.kind === 'static')
47
- return acc + STATIC_WEIGHT;
48
- if (seg.kind === 'param')
49
- return acc + PARAM_WEIGHT;
50
- return acc + EMPTY_WEIGHT;
51
- }, 0);
52
- if (branch.segments.length === 0)
53
- score += EMPTY_WEIGHT;
54
- if (branch.index)
55
- score += INDEX_WEIGHT;
56
- return score;
57
- };
58
- /** Rank branches so the most specific match is tried first. Stable within equal score. */
59
- export const rankRouteBranches = (branches) => branches
60
- .map(branch => ({ ...branch, score: scoreBranch(branch) }))
61
- .map((branch, index) => ({ branch, index }))
62
- .sort((a, b) => (b.branch.score - a.branch.score) || (a.index - b.index))
63
- .map(({ branch }) => branch);
64
- const matchBranch = (branch, parts) => {
65
- if (branch.segments.some(seg => seg.kind === 'splat')) {
66
- throw new Error('router matcher: splat (*) segments are not implemented yet');
67
- }
68
- // Exact-length match (no splats/optionals in the supported subset).
69
- if (branch.segments.length !== parts.length)
70
- return null;
71
- const params = {};
72
- for (let i = 0; i < branch.segments.length; i++) {
73
- const seg = branch.segments[i];
74
- const part = parts[i];
75
- if (seg.kind === 'static') {
76
- if (seg.value.toLowerCase() !== part.toLowerCase())
77
- return null;
69
+ return params;
70
+ };
71
+ const matchRoutes = (branches, pathname) => {
72
+ const parts = splitPath(pathname);
73
+ const ranked = rankRouteBranches(branches);
74
+ for (const branch of ranked) {
75
+ const params = matchBranch(branch, parts);
76
+ if (params == null)
77
+ continue;
78
+ return branch.chain.map(route => ({
79
+ route,
80
+ params,
81
+ pathname: SEP + parts.join(SEP)
82
+ }));
78
83
  }
79
- else {
80
- params[seg.value] = decodeURIComponent(part);
81
- }
82
- }
83
- return params;
84
- };
85
- /**
86
- * Match a pathname against ranked branches. Returns the winning branch as a
87
- * root→leaf chain of `RouteMatch` (params merged across the whole chain so every
88
- * depth sees the full leaf param set, matching react-router's `useParams`).
89
- */
90
- export const matchRoutes = (branches, pathname) => {
91
- const parts = splitPath(pathname);
92
- const ranked = rankRouteBranches(branches);
93
- for (const branch of ranked) {
94
- const params = matchBranch(branch, parts);
95
- if (params == null)
96
- continue;
97
- return branch.chain.map(route => ({
98
- route,
99
- params,
100
- pathname: SEP + parts.join(SEP)
101
- }));
102
- }
103
- return null;
84
+ return null;
85
+ };
86
+ return { splitPath, segmentsOf, flattenRoutes, rankRouteBranches, matchRoutes };
104
87
  };
88
+ export const routeMatcherHelper = createRouteMatcherHelper();
105
89
  //# sourceMappingURL=matcher.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"matcher.js","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/router",
3
- "version": "0.1.18-rc.32",
3
+ "version": "0.1.18-rc.33",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@owlmeans/context": "^0.1.18-rc.33"
24
+ "@owlmeans/context": "^0.1.18-rc.34"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": "*"
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  export type * from './types.js'
3
+ export type * from './matcher/types.js'
3
4
  export * from './consts.js'
4
5
  export * from './service.js'
5
6
  export * from './matcher.js'
@@ -0,0 +1,31 @@
1
+ import type { PatternSegment, RouteBranch, RouteMatch, RouteObject } from '../types.js'
2
+
3
+ /**
4
+ * Pure, DOM-free route matcher. Supports exactly the route-description subset used
5
+ * across the OwlMeans projects: static segments, `:param` dynamic segments, nested
6
+ * (parent/child) routes and index routes. Splat (`*`) and optional (`:x?`) segments
7
+ * are intentionally not implemented yet — a seam is reserved so they can be added
8
+ * without changing the public shape.
9
+ *
10
+ * Segment weights mirror react-router's ranking intuition (static beats dynamic,
11
+ * more specific / deeper beats shallower).
12
+ */
13
+ export interface RouteMatcherHelper {
14
+ /** The non-empty `/`-separated parts of a path. */
15
+ splitPath: (path?: string) => string[]
16
+ /** The pattern segments of a route path: static, `:param` or splat. */
17
+ segmentsOf: (path?: string) => PatternSegment[]
18
+ /**
19
+ * Flatten a route tree into ranked-matchable branches. Every leaf and every node
20
+ * carrying an index child produces a branch.
21
+ */
22
+ flattenRoutes: (routes: RouteObject[], parentChain?: RouteObject[], parentSegments?: PatternSegment[]) => RouteBranch[]
23
+ /** Rank branches so the most specific match is tried first. Stable within equal score. */
24
+ rankRouteBranches: (branches: RouteBranch[]) => RouteBranch[]
25
+ /**
26
+ * Match a pathname against ranked branches. Returns the winning branch as a
27
+ * root→leaf chain of `RouteMatch` (params merged across the whole chain so every
28
+ * depth sees the full leaf param set, matching react-router's `useParams`).
29
+ */
30
+ matchRoutes: (branches: RouteBranch[], pathname: string) => RouteMatch[] | null
31
+ }
package/src/matcher.ts CHANGED
@@ -1,120 +1,104 @@
1
1
  import type { RouteObject, RouteParams, PatternSegment, RouteBranch, RouteMatch } from './types.js'
2
2
  import { EMPTY_WEIGHT, INDEX_WEIGHT, PARAM, PARAM_WEIGHT, SEP, SPLAT, STATIC_WEIGHT } from './consts.local.js'
3
-
4
- /**
5
- * Pure, DOM-free route matcher. Supports exactly the route-description subset used
6
- * across the OwlMeans projects: static segments, `:param` dynamic segments, nested
7
- * (parent/child) routes and index routes. Splat (`*`) and optional (`:x?`) segments
8
- * are intentionally not implemented yet — a seam is reserved so they can be added
9
- * without changing the public shape.
10
- *
11
- * Segment weights mirror react-router's ranking intuition (static beats dynamic,
12
- * more specific / deeper beats shallower).
13
- */
14
-
15
-
16
-
17
- export const splitPath = (path?: string): string[] =>
18
- (path ?? '').split(SEP).filter(part => part.length > 0)
19
-
20
- export const segmentsOf = (path?: string): PatternSegment[] =>
21
- splitPath(path).map(part => {
22
- if (part === SPLAT) return { kind: 'splat', value: SPLAT }
23
- if (part.startsWith(PARAM)) return { kind: 'param', value: part.slice(PARAM.length) }
24
- return { kind: 'static', value: part }
25
- })
26
-
27
- /**
28
- * Flatten a route tree into ranked-matchable branches. Every leaf and every node
29
- * carrying an index child produces a branch.
30
- */
31
- export const flattenRoutes = (
32
- routes: RouteObject[],
33
- parentChain: RouteObject[] = [],
34
- parentSegments: PatternSegment[] = []
35
- ): RouteBranch[] => {
36
- const branches: RouteBranch[] = []
37
-
38
- for (const route of routes) {
39
- const chain = [...parentChain, route]
40
-
41
- if (route.index === true) {
42
- // An index route contributes an empty terminal at the parent's path.
43
- branches.push({ chain, segments: parentSegments, index: true, score: 0 })
44
- continue
3
+ import type { RouteMatcherHelper } from './matcher/types.js'
4
+
5
+ export const createRouteMatcherHelper = (): RouteMatcherHelper => {
6
+ const splitPath = (path?: string): string[] =>
7
+ (path ?? '').split(SEP).filter(part => part.length > 0)
8
+
9
+ const segmentsOf = (path?: string): PatternSegment[] =>
10
+ splitPath(path).map(part => {
11
+ if (part === SPLAT) return { kind: 'splat', value: SPLAT }
12
+ if (part.startsWith(PARAM)) return { kind: 'param', value: part.slice(PARAM.length) }
13
+ return { kind: 'static', value: part }
14
+ })
15
+
16
+ const flattenRoutes = (
17
+ routes: RouteObject[],
18
+ parentChain: RouteObject[] = [],
19
+ parentSegments: PatternSegment[] = []
20
+ ): RouteBranch[] => {
21
+ const branches: RouteBranch[] = []
22
+
23
+ for (const route of routes) {
24
+ const chain = [...parentChain, route]
25
+
26
+ if (route.index === true) {
27
+ // An index route contributes an empty terminal at the parent's path.
28
+ branches.push({ chain, segments: parentSegments, index: true, score: 0 })
29
+ continue
30
+ }
31
+
32
+ const segments = [...parentSegments, ...segmentsOf(route.path)]
33
+ const children = route.children ?? []
34
+
35
+ if (children.length === 0) {
36
+ branches.push({ chain, segments, index: false, score: 0 })
37
+ } else {
38
+ branches.push(...flattenRoutes(children, chain, segments))
39
+ }
45
40
  }
46
41
 
47
- const segments = [...parentSegments, ...segmentsOf(route.path)]
48
- const children = route.children ?? []
42
+ return branches
43
+ }
44
+
45
+ const scoreBranch = (branch: RouteBranch): number => {
46
+ let score = branch.segments.reduce((acc, seg) => {
47
+ if (seg.kind === 'static') return acc + STATIC_WEIGHT
48
+ if (seg.kind === 'param') return acc + PARAM_WEIGHT
49
+ return acc + EMPTY_WEIGHT
50
+ }, 0)
51
+ if (branch.segments.length === 0) score += EMPTY_WEIGHT
52
+ if (branch.index) score += INDEX_WEIGHT
53
+ return score
54
+ }
55
+
56
+ const rankRouteBranches = (branches: RouteBranch[]): RouteBranch[] =>
57
+ branches
58
+ .map(branch => ({ ...branch, score: scoreBranch(branch) }))
59
+ .map((branch, index) => ({ branch, index }))
60
+ .sort((a, b) => (b.branch.score - a.branch.score) || (a.index - b.index))
61
+ .map(({ branch }) => branch)
49
62
 
50
- if (children.length === 0) {
51
- branches.push({ chain, segments, index: false, score: 0 })
52
- } else {
53
- branches.push(...flattenRoutes(children, chain, segments))
63
+ const matchBranch = (branch: RouteBranch, parts: string[]): RouteParams | null => {
64
+ if (branch.segments.some(seg => seg.kind === 'splat')) {
65
+ throw new Error('router matcher: splat (*) segments are not implemented yet')
66
+ }
67
+ // Exact-length match (no splats/optionals in the supported subset).
68
+ if (branch.segments.length !== parts.length) return null
69
+
70
+ const params: RouteParams = {}
71
+ for (let i = 0; i < branch.segments.length; i++) {
72
+ const seg = branch.segments[i]
73
+ const part = parts[i]
74
+ if (seg.kind === 'static') {
75
+ if (seg.value.toLowerCase() !== part.toLowerCase()) return null
76
+ } else {
77
+ params[seg.value] = decodeURIComponent(part)
78
+ }
54
79
  }
80
+ return params
55
81
  }
56
82
 
57
- return branches
58
- }
83
+ const matchRoutes = (branches: RouteBranch[], pathname: string): RouteMatch[] | null => {
84
+ const parts = splitPath(pathname)
85
+ const ranked = rankRouteBranches(branches)
59
86
 
60
- const scoreBranch = (branch: RouteBranch): number => {
61
- let score = branch.segments.reduce((acc, seg) => {
62
- if (seg.kind === 'static') return acc + STATIC_WEIGHT
63
- if (seg.kind === 'param') return acc + PARAM_WEIGHT
64
- return acc + EMPTY_WEIGHT
65
- }, 0)
66
- if (branch.segments.length === 0) score += EMPTY_WEIGHT
67
- if (branch.index) score += INDEX_WEIGHT
68
- return score
69
- }
87
+ for (const branch of ranked) {
88
+ const params = matchBranch(branch, parts)
89
+ if (params == null) continue
70
90
 
71
- /** Rank branches so the most specific match is tried first. Stable within equal score. */
72
- export const rankRouteBranches = (branches: RouteBranch[]): RouteBranch[] =>
73
- branches
74
- .map(branch => ({ ...branch, score: scoreBranch(branch) }))
75
- .map((branch, index) => ({ branch, index }))
76
- .sort((a, b) => (b.branch.score - a.branch.score) || (a.index - b.index))
77
- .map(({ branch }) => branch)
78
-
79
- const matchBranch = (branch: RouteBranch, parts: string[]): RouteParams | null => {
80
- if (branch.segments.some(seg => seg.kind === 'splat')) {
81
- throw new Error('router matcher: splat (*) segments are not implemented yet')
82
- }
83
- // Exact-length match (no splats/optionals in the supported subset).
84
- if (branch.segments.length !== parts.length) return null
85
-
86
- const params: RouteParams = {}
87
- for (let i = 0; i < branch.segments.length; i++) {
88
- const seg = branch.segments[i]
89
- const part = parts[i]
90
- if (seg.kind === 'static') {
91
- if (seg.value.toLowerCase() !== part.toLowerCase()) return null
92
- } else {
93
- params[seg.value] = decodeURIComponent(part)
91
+ return branch.chain.map(route => ({
92
+ route,
93
+ params,
94
+ pathname: SEP + parts.join(SEP)
95
+ }))
94
96
  }
95
- }
96
- return params
97
- }
98
97
 
99
- /**
100
- * Match a pathname against ranked branches. Returns the winning branch as a
101
- * root→leaf chain of `RouteMatch` (params merged across the whole chain so every
102
- * depth sees the full leaf param set, matching react-router's `useParams`).
103
- */
104
- export const matchRoutes = (branches: RouteBranch[], pathname: string): RouteMatch[] | null => {
105
- const parts = splitPath(pathname)
106
- const ranked = rankRouteBranches(branches)
107
-
108
- for (const branch of ranked) {
109
- const params = matchBranch(branch, parts)
110
- if (params == null) continue
111
-
112
- return branch.chain.map(route => ({
113
- route,
114
- params,
115
- pathname: SEP + parts.join(SEP)
116
- }))
98
+ return null
117
99
  }
118
100
 
119
- return null
101
+ return { splitPath, segmentsOf, flattenRoutes, rankRouteBranches, matchRoutes }
120
102
  }
103
+
104
+ export const routeMatcherHelper = createRouteMatcherHelper()