@xmachines/play-router 1.0.0-beta.9 → 1.0.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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +246 -341
  3. package/dist/base-route-map.d.ts +38 -17
  4. package/dist/base-route-map.d.ts.map +1 -1
  5. package/dist/base-route-map.js +69 -62
  6. package/dist/base-route-map.js.map +1 -1
  7. package/dist/build-tree.d.ts +7 -0
  8. package/dist/build-tree.d.ts.map +1 -1
  9. package/dist/build-tree.js +21 -6
  10. package/dist/build-tree.js.map +1 -1
  11. package/dist/create-route-map-from-tree.d.ts +33 -0
  12. package/dist/create-route-map-from-tree.d.ts.map +1 -0
  13. package/dist/create-route-map-from-tree.js +43 -0
  14. package/dist/create-route-map-from-tree.js.map +1 -0
  15. package/dist/create-route-map.d.ts +26 -50
  16. package/dist/create-route-map.d.ts.map +1 -1
  17. package/dist/create-route-map.js +24 -114
  18. package/dist/create-route-map.js.map +1 -1
  19. package/dist/errors.d.ts +152 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +186 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/extract-routes.d.ts +5 -31
  24. package/dist/extract-routes.d.ts.map +1 -1
  25. package/dist/extract-routes.js +73 -51
  26. package/dist/extract-routes.js.map +1 -1
  27. package/dist/find-route.d.ts +44 -0
  28. package/dist/find-route.d.ts.map +1 -0
  29. package/dist/find-route.js +103 -0
  30. package/dist/find-route.js.map +1 -0
  31. package/dist/index.d.ts +13 -52
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +13 -137
  34. package/dist/index.js.map +1 -1
  35. package/dist/machine-to-graph.d.ts +17 -0
  36. package/dist/machine-to-graph.d.ts.map +1 -0
  37. package/dist/machine-to-graph.js +115 -0
  38. package/dist/machine-to-graph.js.map +1 -0
  39. package/dist/query.d.ts +49 -1
  40. package/dist/query.d.ts.map +1 -1
  41. package/dist/query.js +98 -3
  42. package/dist/query.js.map +1 -1
  43. package/dist/router-bridge-base.d.ts +187 -48
  44. package/dist/router-bridge-base.d.ts.map +1 -1
  45. package/dist/router-bridge-base.js +312 -120
  46. package/dist/router-bridge-base.js.map +1 -1
  47. package/dist/router-sync.d.ts +95 -0
  48. package/dist/router-sync.d.ts.map +1 -0
  49. package/dist/router-sync.js +161 -0
  50. package/dist/router-sync.js.map +1 -0
  51. package/dist/types.d.ts +163 -22
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/url-pattern-utils.d.ts +117 -0
  54. package/dist/url-pattern-utils.d.ts.map +1 -0
  55. package/dist/url-pattern-utils.js +176 -0
  56. package/dist/url-pattern-utils.js.map +1 -0
  57. package/dist/validate-routes.d.ts +32 -14
  58. package/dist/validate-routes.d.ts.map +1 -1
  59. package/dist/validate-routes.js +28 -21
  60. package/dist/validate-routes.js.map +1 -1
  61. package/package.json +34 -14
  62. package/dist/connect-router.d.ts +0 -56
  63. package/dist/connect-router.d.ts.map +0 -1
  64. package/dist/connect-router.js +0 -112
  65. package/dist/connect-router.js.map +0 -1
  66. package/dist/crawl-machine.d.ts +0 -74
  67. package/dist/crawl-machine.d.ts.map +0 -1
  68. package/dist/crawl-machine.js +0 -95
  69. package/dist/crawl-machine.js.map +0 -1
  70. package/dist/create-browser-history.d.ts +0 -93
  71. package/dist/create-browser-history.d.ts.map +0 -1
  72. package/dist/create-browser-history.js +0 -94
  73. package/dist/create-browser-history.js.map +0 -1
  74. package/dist/create-router.d.ts +0 -73
  75. package/dist/create-router.d.ts.map +0 -1
  76. package/dist/create-router.js +0 -63
  77. package/dist/create-router.js.map +0 -1
  78. package/dist/extract-route.d.ts +0 -25
  79. package/dist/extract-route.d.ts.map +0 -1
  80. package/dist/extract-route.js +0 -63
  81. package/dist/extract-route.js.map +0 -1
@@ -1,25 +1,26 @@
1
1
  /**
2
- * BaseRouteMap — Shared bidirectional route mapping base class
2
+ * RouteMap — Shared bidirectional route mapping base class
3
3
  *
4
4
  * Provides bucket-based pattern matching shared across all framework adapters.
5
5
  * Adapters extend this class rather than duplicating the pattern-match logic.
6
6
  *
7
7
  * Algorithm: O(1) exact match via Map, then bucket-based O(k) pattern match
8
8
  * where k = routes in the first-segment bucket (typically << total routes).
9
+ * Uses URLPattern for parameterized route matching.
9
10
  */
10
11
  /**
11
12
  * A single state ID ↔ path mapping entry.
12
13
  *
13
- * Both fields are `readonly` — mappings are immutable once passed to `BaseRouteMap`.
14
+ * Both fields are `readonly` — mappings are immutable once passed to `RouteMap`.
14
15
  * Adapter packages re-export a structurally compatible `RouteMapping` type under
15
16
  * their own name. This type is published from `@xmachines/play-router` as
16
- * `BaseRouteMapping` to avoid name collisions with those adapter-local types.
17
+ * `RouteMapping` to avoid name collisions with those adapter-local types.
17
18
  *
18
19
  * @example
19
20
  * ```typescript
20
- * const mapping: BaseRouteMapping = { stateId: "home", path: "/" };
21
- * const paramMapping: BaseRouteMapping = { stateId: "profile", path: "/profile/:userId" };
22
- * const optionalMapping: BaseRouteMapping = { stateId: "settings", path: "/settings/:section?" };
21
+ * const mapping: RouteMapping = { stateId: "home", path: "/" };
22
+ * const paramMapping: RouteMapping = { stateId: "profile", path: "/profile/:userId" };
23
+ * const optionalMapping: RouteMapping = { stateId: "settings", path: "/settings/:section?" };
23
24
  * ```
24
25
  */
25
26
  export interface RouteMapping {
@@ -31,24 +32,32 @@ export interface RouteMapping {
31
32
  /**
32
33
  * Shared bidirectional route map base class.
33
34
  *
34
- * All framework adapter `RouteMap` classes extend this — they add no logic of their
35
+ * All framework adapters use this class as their route map — they add no logic of their
35
36
  * own and inherit the full public API from here.
36
37
  *
37
38
  * **Lookup strategy:**
38
39
  * - Static paths (no `:param`) → O(1) `Map` lookup
39
- * - Dynamic paths → O(k) bucket-indexed scan using `RegExp`, where `k` is the number
40
+ * - Dynamic paths → O(k) bucket-indexed scan using `URLPattern`, where `k` is the number
40
41
  * of routes sharing the same first path segment
41
- * - Results are cached after the first match
42
+ * - Results are cached after the first match in an LRU cache (default 500 entries,
43
+ * configurable via the `cacheSize` constructor option)
42
44
  *
43
- * **Pattern syntax** (`:param` / `:param?`):
45
+ * **Pattern syntax** (`:param` / `:param?` / `*`):
44
46
  * - `:param` — required segment, matches exactly one non-`/` segment
45
47
  * - `:param?` — optional segment, matches zero or one non-`/` segment
48
+ * - `*` — wildcard, matches any number of segments (URLPattern semantics)
49
+ *
50
+ * **StateId forms:** stateIds may be registered and looked up in either
51
+ * `"#stateId"` or `"stateId"` form — `RouteMap` canonicalizes internally.
52
+ * `getStateIdByPath` returns the stateId exactly as registered;
53
+ * `getPathByStateId` accepts both forms. Registering the same stateId in both
54
+ * forms refers to one entry (the later registration wins for reverse lookup).
46
55
  *
47
56
  * @example
48
57
  * ```typescript
49
- * import { BaseRouteMap } from "@xmachines/play-router";
58
+ * import { RouteMap } from "@xmachines/play-router";
50
59
  *
51
- * const map = new BaseRouteMap([
60
+ * const map = new RouteMap([
52
61
  * { stateId: "home", path: "/" },
53
62
  * { stateId: "profile", path: "/profile/:userId" },
54
63
  * { stateId: "settings", path: "/settings/:section?" },
@@ -63,7 +72,8 @@ export interface RouteMapping {
63
72
  * map.getPathByStateId("missing"); // null
64
73
  * ```
65
74
  */
66
- export declare class BaseRouteMap {
75
+ export declare class RouteMap {
76
+ /** Keyed on the canonical (bare, `#`-stripped) stateId form. */
67
77
  private stateIdToPath;
68
78
  private pathToStateId;
69
79
  private patternBuckets;
@@ -72,13 +82,21 @@ export declare class BaseRouteMap {
72
82
  * Build a route map from an array of state ID ↔ path mappings.
73
83
  *
74
84
  * Static paths (no `:param`) are indexed in an O(1) `Map`.
75
- * Parameterized paths are compiled to `RegExp` and grouped into first-segment
85
+ * Parameterized paths are compiled to `URLPattern` and grouped into first-segment
76
86
  * buckets for efficient candidate selection.
77
87
  *
78
88
  * @param mappings - Array of `{ stateId, path }` entries. Order determines
79
89
  * priority when multiple patterns could match the same path.
90
+ * @param options - Optional configuration.
91
+ * `options.cacheSize`: Maximum number of resolved parameterized path lookups
92
+ * to cache. Defaults to `500`. Increase for applications with many unique
93
+ * parameterized URL values (e.g. user profile pages with thousands of distinct IDs).
94
+ * After eviction the path falls back to the O(k) bucket pattern scan — correct
95
+ * but slower. Minimum effective value is `1` (QuickLRU constraint).
80
96
  */
81
- constructor(mappings: RouteMapping[]);
97
+ constructor(mappings: RouteMapping[], { cacheSize }?: {
98
+ cacheSize?: number;
99
+ });
82
100
  /**
83
101
  * Resolve a URL path to its mapped state ID.
84
102
  *
@@ -99,17 +117,20 @@ export declare class BaseRouteMap {
99
117
  /**
100
118
  * Look up the path pattern registered for a state ID.
101
119
  *
120
+ * Accepts the stateId in either `"#stateId"` or `"stateId"` form regardless of
121
+ * which form was used at registration — lookups are canonicalized internally,
122
+ * so consumers never need to try both forms.
123
+ *
102
124
  * @param stateId - State machine state ID (e.g., `"profile"`, `"#settings"`)
103
125
  * @returns The registered path pattern, or `null` if the state ID is unknown
104
126
  *
105
127
  * @example
106
128
  * ```typescript
107
129
  * map.getPathByStateId("profile"); // "/profile/:userId"
130
+ * map.getPathByStateId("#profile"); // "/profile/:userId" (same entry)
108
131
  * map.getPathByStateId("missing"); // null
109
132
  * ```
110
133
  */
111
134
  getPathByStateId(stateId: string): string | null;
112
- private getIndexKey;
113
- private getCandidates;
114
135
  }
115
136
  //# sourceMappingURL=base-route-map.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-route-map.d.ts","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,YAAY;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,cAAc,CAA0E;IAChG,OAAO,CAAC,cAAc,CAAkC;IAExD;;;;;;;;;OASG;gBACS,QAAQ,EAAE,YAAY,EAAE;IA4BpC;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAsB7C;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIhD,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,aAAa;CA8BrB"}
1
+ {"version":3,"file":"base-route-map.d.ts","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA0BH;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,YAAY;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,qBAAa,QAAQ;IACpB,gEAAgE;IAChE,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,cAAc,CAGpB;IACF,OAAO,CAAC,cAAc,CAAkC;IAExD;;;;;;;;;;;;;;;OAeG;gBACS,QAAQ,EAAE,YAAY,EAAE,EAAE,EAAE,SAAe,EAAE,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO;IAmCtF;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAuB7C;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;CAGhD"}
@@ -1,34 +1,57 @@
1
1
  /**
2
- * BaseRouteMap — Shared bidirectional route mapping base class
2
+ * RouteMap — Shared bidirectional route mapping base class
3
3
  *
4
4
  * Provides bucket-based pattern matching shared across all framework adapters.
5
5
  * Adapters extend this class rather than duplicating the pattern-match logic.
6
6
  *
7
7
  * Algorithm: O(1) exact match via Map, then bucket-based O(k) pattern match
8
8
  * where k = routes in the first-segment bucket (typically << total routes).
9
+ * Uses URLPattern for parameterized route matching.
9
10
  */
10
11
  import QuickLRU from "quick-lru";
12
+ import { getURLPatternCtor, getCompiledPattern, getIndexKey, getCandidates, isParameterizedPattern, } from "./url-pattern-utils.js";
13
+ import { URLPatternUnavailableError, InvalidRoutePatternError } from "./errors.js";
14
+ /**
15
+ * Canonical stateId key: the bare form with any leading `#` stripped.
16
+ *
17
+ * `RouteMap` accepts stateIds in both `"#stateId"` and `"stateId"` form on insert
18
+ * and on lookup. Internally every stateId is keyed on this canonical bare form so
19
+ * consumers never need to re-implement dual-form handling. The *registered* form
20
+ * is preserved and returned by `getStateIdByPath` so the map is transparent about
21
+ * how routes were declared.
22
+ */
23
+ function canonicalStateIdKey(stateId) {
24
+ return stateId.startsWith("#") ? stateId.slice(1) : stateId;
25
+ }
11
26
  /**
12
27
  * Shared bidirectional route map base class.
13
28
  *
14
- * All framework adapter `RouteMap` classes extend this — they add no logic of their
29
+ * All framework adapters use this class as their route map — they add no logic of their
15
30
  * own and inherit the full public API from here.
16
31
  *
17
32
  * **Lookup strategy:**
18
33
  * - Static paths (no `:param`) → O(1) `Map` lookup
19
- * - Dynamic paths → O(k) bucket-indexed scan using `RegExp`, where `k` is the number
34
+ * - Dynamic paths → O(k) bucket-indexed scan using `URLPattern`, where `k` is the number
20
35
  * of routes sharing the same first path segment
21
- * - Results are cached after the first match
36
+ * - Results are cached after the first match in an LRU cache (default 500 entries,
37
+ * configurable via the `cacheSize` constructor option)
22
38
  *
23
- * **Pattern syntax** (`:param` / `:param?`):
39
+ * **Pattern syntax** (`:param` / `:param?` / `*`):
24
40
  * - `:param` — required segment, matches exactly one non-`/` segment
25
41
  * - `:param?` — optional segment, matches zero or one non-`/` segment
42
+ * - `*` — wildcard, matches any number of segments (URLPattern semantics)
43
+ *
44
+ * **StateId forms:** stateIds may be registered and looked up in either
45
+ * `"#stateId"` or `"stateId"` form — `RouteMap` canonicalizes internally.
46
+ * `getStateIdByPath` returns the stateId exactly as registered;
47
+ * `getPathByStateId` accepts both forms. Registering the same stateId in both
48
+ * forms refers to one entry (the later registration wins for reverse lookup).
26
49
  *
27
50
  * @example
28
51
  * ```typescript
29
- * import { BaseRouteMap } from "@xmachines/play-router";
52
+ * import { RouteMap } from "@xmachines/play-router";
30
53
  *
31
- * const map = new BaseRouteMap([
54
+ * const map = new RouteMap([
32
55
  * { stateId: "home", path: "/" },
33
56
  * { stateId: "profile", path: "/profile/:userId" },
34
57
  * { stateId: "settings", path: "/settings/:section?" },
@@ -43,7 +66,8 @@ import QuickLRU from "quick-lru";
43
66
  * map.getPathByStateId("missing"); // null
44
67
  * ```
45
68
  */
46
- export class BaseRouteMap {
69
+ export class RouteMap {
70
+ /** Keyed on the canonical (bare, `#`-stripped) stateId form. */
47
71
  stateIdToPath;
48
72
  pathToStateId;
49
73
  patternBuckets;
@@ -52,29 +76,45 @@ export class BaseRouteMap {
52
76
  * Build a route map from an array of state ID ↔ path mappings.
53
77
  *
54
78
  * Static paths (no `:param`) are indexed in an O(1) `Map`.
55
- * Parameterized paths are compiled to `RegExp` and grouped into first-segment
79
+ * Parameterized paths are compiled to `URLPattern` and grouped into first-segment
56
80
  * buckets for efficient candidate selection.
57
81
  *
58
82
  * @param mappings - Array of `{ stateId, path }` entries. Order determines
59
83
  * priority when multiple patterns could match the same path.
84
+ * @param options - Optional configuration.
85
+ * `options.cacheSize`: Maximum number of resolved parameterized path lookups
86
+ * to cache. Defaults to `500`. Increase for applications with many unique
87
+ * parameterized URL values (e.g. user profile pages with thousands of distinct IDs).
88
+ * After eviction the path falls back to the O(k) bucket pattern scan — correct
89
+ * but slower. Minimum effective value is `1` (QuickLRU constraint).
60
90
  */
61
- constructor(mappings) {
91
+ constructor(mappings, { cacheSize = 500 } = {}) {
62
92
  this.stateIdToPath = new Map();
63
93
  this.pathToStateId = new Map();
64
94
  this.patternBuckets = new Map();
65
- this.pathMatchCache = new QuickLRU({ maxSize: 500 });
95
+ this.pathMatchCache = new QuickLRU({ maxSize: Math.max(1, cacheSize) });
66
96
  let patternOrder = 0;
67
97
  for (const { stateId, path } of mappings) {
68
- this.stateIdToPath.set(stateId, path);
69
- if (path.includes(":")) {
70
- const pattern = path.replace(/\/:[^/]+\??/g, (segment) => segment.endsWith("?") ? "(?:/([^/]+))?" : "/([^/]+)");
71
- const bucketKey = this.getIndexKey(path);
98
+ // Key on the canonical (bare) form so getPathByStateId accepts both
99
+ // "#stateId" and "stateId". The registered form is kept for returns.
100
+ this.stateIdToPath.set(canonicalStateIdKey(stateId), path);
101
+ if (isParameterizedPattern(path)) {
102
+ const URLPatternCtorFn = getURLPatternCtor();
103
+ if (!URLPatternCtorFn) {
104
+ throw new URLPatternUnavailableError();
105
+ }
106
+ const bucketKey = getIndexKey(path);
72
107
  const bucket = this.patternBuckets.get(bucketKey) ?? [];
73
- bucket.push({
74
- pattern: new RegExp(`^${pattern}$`),
75
- stateId,
76
- order: patternOrder++,
77
- });
108
+ try {
109
+ bucket.push({
110
+ pattern: getCompiledPattern(path, URLPatternCtorFn),
111
+ stateId,
112
+ order: patternOrder++,
113
+ });
114
+ }
115
+ catch (err) {
116
+ throw new InvalidRoutePatternError(path, { cause: err });
117
+ }
78
118
  this.patternBuckets.set(bucketKey, bucket);
79
119
  }
80
120
  else {
@@ -107,9 +147,10 @@ export class BaseRouteMap {
107
147
  const cachedMatch = this.pathMatchCache.get(cleanPath);
108
148
  if (cachedMatch !== undefined)
109
149
  return cachedMatch;
110
- const candidates = this.getCandidates(this.getIndexKey(cleanPath));
150
+ const candidates = getCandidates(this.patternBuckets, getIndexKey(cleanPath));
111
151
  for (const { pattern, stateId } of candidates) {
112
- if (pattern.test(cleanPath)) {
152
+ const match = pattern.exec({ pathname: cleanPath });
153
+ if (match) {
113
154
  this.pathMatchCache.set(cleanPath, stateId);
114
155
  return stateId;
115
156
  }
@@ -120,56 +161,22 @@ export class BaseRouteMap {
120
161
  /**
121
162
  * Look up the path pattern registered for a state ID.
122
163
  *
164
+ * Accepts the stateId in either `"#stateId"` or `"stateId"` form regardless of
165
+ * which form was used at registration — lookups are canonicalized internally,
166
+ * so consumers never need to try both forms.
167
+ *
123
168
  * @param stateId - State machine state ID (e.g., `"profile"`, `"#settings"`)
124
169
  * @returns The registered path pattern, or `null` if the state ID is unknown
125
170
  *
126
171
  * @example
127
172
  * ```typescript
128
173
  * map.getPathByStateId("profile"); // "/profile/:userId"
174
+ * map.getPathByStateId("#profile"); // "/profile/:userId" (same entry)
129
175
  * map.getPathByStateId("missing"); // null
130
176
  * ```
131
177
  */
132
178
  getPathByStateId(stateId) {
133
- return this.stateIdToPath.get(stateId) ?? null;
134
- }
135
- getIndexKey(path) {
136
- const trimmed = path.startsWith("/") ? path.slice(1) : path;
137
- if (trimmed.length === 0)
138
- return "/";
139
- const segment = trimmed.split("/")[0];
140
- if (segment === undefined || segment.startsWith(":"))
141
- return "*";
142
- return segment;
143
- }
144
- getCandidates(indexKey) {
145
- const bucket = this.patternBuckets.get(indexKey) ?? [];
146
- const wildcardBucket = this.patternBuckets.get("*") ?? [];
147
- if (bucket.length === 0)
148
- return wildcardBucket;
149
- if (wildcardBucket.length === 0)
150
- return bucket;
151
- const merged = [];
152
- let b = 0;
153
- let w = 0;
154
- while (b < bucket.length && w < wildcardBucket.length) {
155
- if (bucket[b].order < wildcardBucket[w].order) {
156
- merged.push(bucket[b]);
157
- b += 1;
158
- }
159
- else {
160
- merged.push(wildcardBucket[w]);
161
- w += 1;
162
- }
163
- }
164
- while (b < bucket.length) {
165
- merged.push(bucket[b]);
166
- b += 1;
167
- }
168
- while (w < wildcardBucket.length) {
169
- merged.push(wildcardBucket[w]);
170
- w += 1;
171
- }
172
- return merged;
179
+ return this.stateIdToPath.get(canonicalStateIdKey(stateId)) ?? null;
173
180
  }
174
181
  }
175
182
  //# sourceMappingURL=base-route-map.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"base-route-map.js","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"base-route-map.js","sourceRoot":"","sources":["../src/base-route-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,EAEN,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,sBAAsB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,0BAA0B,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAEnF;;;;;;;;GAQG;AACH,SAAS,mBAAmB,CAAC,OAAe;IAC3C,OAAO,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AAC7D,CAAC;AAwBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,OAAO,QAAQ;IACpB,gEAAgE;IACxD,aAAa,CAAsB;IACnC,aAAa,CAAsB;IACnC,cAAc,CAGpB;IACM,cAAc,CAAkC;IAExD;;;;;;;;;;;;;;;OAeG;IACH,YAAY,QAAwB,EAAE,EAAE,SAAS,GAAG,GAAG,KAA6B,EAAE;QACrF,IAAI,CAAC,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,aAAa,GAAG,IAAI,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC,cAAc,GAAG,IAAI,GAAG,EAAE,CAAC;QAChC,IAAI,CAAC,cAAc,GAAG,IAAI,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC;QACxE,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,KAAK,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1C,oEAAoE;YACpE,qEAAqE;YACrE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;YAE3D,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC;gBAClC,MAAM,gBAAgB,GAAG,iBAAiB,EAAE,CAAC;gBAC7C,IAAI,CAAC,gBAAgB,EAAE,CAAC;oBACvB,MAAM,IAAI,0BAA0B,EAAE,CAAC;gBACxC,CAAC;gBACD,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;gBACpC,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;gBACxD,IAAI,CAAC;oBACJ,MAAM,CAAC,IAAI,CAAC;wBACX,OAAO,EAAE,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,CAAC;wBACnD,OAAO;wBACP,KAAK,EAAE,YAAY,EAAE;qBACrB,CAAC,CAAC;gBACJ,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,MAAM,IAAI,wBAAwB,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;gBAC1D,CAAC;gBACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;iBAAM,CAAC;gBACP,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACvC,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,gBAAgB,CAAC,IAAY;QAC5B,oDAAoD;QACpD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAEnD,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrD,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,UAAU,CAAC;QAEhD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,WAAW,KAAK,SAAS;YAAE,OAAO,WAAW,CAAC;QAElD,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9E,KAAK,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,UAAU,EAAE,CAAC;YAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;YACpD,IAAI,KAAK,EAAE,CAAC;gBACX,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;gBAC5C,OAAO,OAAO,CAAC;YAChB,CAAC;QACF,CAAC;QAED,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC;IACb,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,gBAAgB,CAAC,OAAe;QAC/B,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC;IACrE,CAAC;CACD"}
@@ -6,8 +6,15 @@ import type { RouteInfo, RouteTree } from "./types.js";
6
6
  * Absolute routes become top-level, relative routes nest under parents.
7
7
  * Creates bidirectional maps for state ID ↔ path lookup.
8
8
  *
9
+ * Duplicate detection runs here — after relative routes are resolved against
10
+ * their parent's full path — so two relative routes with the same raw string
11
+ * under different parents are valid, while distinct raw strings that resolve to
12
+ * the same full path (which would silently overwrite each other in `byPath`)
13
+ * are rejected.
14
+ *
9
15
  * @param routes - Flat list of RouteInfo from extraction
10
16
  * @returns RouteTree with root, byStateId map, and byPath map
17
+ * @throws {DuplicateRoutePathError} If two states resolve to the same full path
11
18
  */
12
19
  export declare const buildRouteTree: (routes: RouteInfo[]) => RouteTree;
13
20
  //# sourceMappingURL=build-tree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-tree.d.ts","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,SAAS,EAAE,MAAM,YAAY,CAAC;AAElE;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,SAAS,EAAE,KAAG,SAgEpD,CAAC"}
1
+ {"version":3,"file":"build-tree.d.ts","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAA4B,SAAS,EAAE,MAAM,YAAY,CAAC;AAGjF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,SAAS,EAAE,KAAG,SAwEpD,CAAC"}
@@ -1,3 +1,4 @@
1
+ import { detectDuplicateRoutes } from "./validate-routes.js";
1
2
  /**
2
3
  * Build hierarchical route tree from flat route list
3
4
  *
@@ -5,8 +6,15 @@
5
6
  * Absolute routes become top-level, relative routes nest under parents.
6
7
  * Creates bidirectional maps for state ID ↔ path lookup.
7
8
  *
9
+ * Duplicate detection runs here — after relative routes are resolved against
10
+ * their parent's full path — so two relative routes with the same raw string
11
+ * under different parents are valid, while distinct raw strings that resolve to
12
+ * the same full path (which would silently overwrite each other in `byPath`)
13
+ * are rejected.
14
+ *
8
15
  * @param routes - Flat list of RouteInfo from extraction
9
16
  * @returns RouteTree with root, byStateId map, and byPath map
17
+ * @throws {DuplicateRoutePathError} If two states resolve to the same full path
10
18
  */
11
19
  export const buildRouteTree = (routes) => {
12
20
  // 1. Create root node
@@ -18,20 +26,23 @@ export const buildRouteTree = (routes) => {
18
26
  routable: false, // Root is not a routable state
19
27
  children: [],
20
28
  parent: null,
21
- metadata: {},
29
+ metadata: "", // Synthetic non-routable root — no real route metadata
22
30
  };
23
31
  // 2. Initialize maps
24
32
  const byStateId = new Map([[root.stateId, root]]);
25
33
  const byPath = new Map([[root.fullPath, root]]);
26
34
  // 3. Sort routes by depth (shallowest first for proper parent linking)
35
+ // oxlint-disable-next-line unicorn/no-array-sort
27
36
  const sorted = routes.slice().sort((a, b) => a.statePath.length - b.statePath.length);
28
- // 4. Build tree
37
+ // 4. Build tree, collecting inserted nodes for resolved-path duplicate detection
38
+ const insertedNodes = [];
29
39
  for (const route of sorted) {
30
- // Find parent by walking up state path
40
+ // Find parent by walking up state path.
41
+ // Each entry in statePath is a full dotted stateId (e.g., "chain.app.section"),
42
+ // so we check each ancestor stateId directly from the end of the list.
31
43
  let parentNode = root;
32
- for (let i = route.statePath.length - 1; i >= 0; i--) {
33
- const parentPath = route.statePath.slice(0, i);
34
- const parentStateId = parentPath.join(".");
44
+ for (let i = route.statePath.length - 2; i >= 0; i--) {
45
+ const parentStateId = route.statePath[i]; // nosemgrep: gitlab.eslint.detect-object-injection
35
46
  if (byStateId.has(parentStateId)) {
36
47
  parentNode = byStateId.get(parentStateId);
37
48
  break;
@@ -61,7 +72,11 @@ export const buildRouteTree = (routes) => {
61
72
  // Add to maps for bidirectional lookup
62
73
  byStateId.set(node.stateId, node);
63
74
  byPath.set(node.fullPath, node);
75
+ insertedNodes.push(node);
64
76
  }
77
+ // 5. Reject resolved full-path collisions — a silent byPath overwrite would
78
+ // leave one state unreachable by URL.
79
+ detectDuplicateRoutes(insertedNodes);
65
80
  return { root, byStateId, byPath };
66
81
  };
67
82
  //# sourceMappingURL=build-tree.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAmB,EAAa,EAAE;IAChE,sBAAsB;IACtB,MAAM,IAAI,GAAc;QACvB,EAAE,EAAE,UAAU;QACd,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,GAAG;QACb,OAAO,EAAE,UAAU;QACnB,QAAQ,EAAE,KAAK,EAAE,+BAA+B;QAChD,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,EAAE;KACZ,CAAC;IAEF,qBAAqB;IACrB,MAAM,SAAS,GAAG,IAAI,GAAG,CAAoB,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAoB,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IAEnE,uEAAuE;IACvE,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAEtF,gBAAgB;IAChB,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,uCAAuC;QACvC,IAAI,UAAU,GAAG,IAAI,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACtD,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/C,MAAM,aAAa,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC3C,IAAI,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;gBAClC,UAAU,GAAG,SAAS,CAAC,GAAG,CAAC,aAAa,CAAE,CAAC;gBAC3C,MAAM;YACP,CAAC;QACF,CAAC;QAED,kBAAkB;QAClB,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU;YAChC,CAAC,CAAC,KAAK,CAAC,SAAS;YACjB,CAAC,CAAC,GAAG,UAAU,CAAC,QAAQ,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAEpE,cAAc;QACd,MAAM,IAAI,GAAc;YACvB,EAAE,EAAE,KAAK,CAAC,OAAO;YACjB,IAAI,EAAE,KAAK,CAAC,SAAS;YACrB,QAAQ;YACR,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,EAAE;YACZ,MAAM,EAAE,UAAU;YAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACxB,CAAC;QAEF,2BAA2B;QAC3B,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;QAC9B,CAAC;QAED,iBAAiB;QACjB,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE/B,uCAAuC;QACvC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAClC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AACpC,CAAC,CAAC"}
1
+ {"version":3,"file":"build-tree.js","sourceRoot":"","sources":["../src/build-tree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { RouteTree } from "./types.js";
2
+ import { RouteMap } from "./base-route-map.js";
3
+ import type { RouteMapOptions } from "./create-route-map.js";
4
+ /**
5
+ * Create a `RouteMap` from a `RouteTree` node structure.
6
+ *
7
+ * Used by framework-router adapters that pass a
8
+ * `RouteTree` produced by `extractMachineRoutes()` rather than calling
9
+ * `createRouteMap()` directly.
10
+ *
11
+ * Traverses all nodes collecting `{ stateId: node.id, path: node.fullPath }` pairs.
12
+ * `node.fullPath` is always the absolute resolved path (e.g. `"/dashboard/overview"`),
13
+ * which is what `RouteMap` needs for browser URL matching. This matches the
14
+ * behaviour of `createRouteMap(machine)`, which also uses `node.fullPath`.
15
+ *
16
+ * @param routeTree - A `RouteTree` as returned by `extractMachineRoutes()`.
17
+ * @param options - Optional configuration (e.g. `{ cacheSize }` to override the LRU cache size).
18
+ * @returns A `RouteMap` for use with any `RouterBridgeBase`-based adapter.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Preferred — single call for XState machines:
23
+ * import { createRouteMap } from '@xmachines/play-router';
24
+ * const routeMap = createRouteMap(machine); // takes AnyStateMachine
25
+ *
26
+ * // Two-step form used by framework adapters that work with route trees:
27
+ * import { extractMachineRoutes, createRouteMapFromTree } from '@xmachines/play-router';
28
+ * const routeTree = extractMachineRoutes(machine);
29
+ * const routeMap = createRouteMapFromTree(routeTree); // uses node.fullPath (absolute)
30
+ * ```
31
+ */
32
+ export declare function createRouteMapFromTree(routeTree: RouteTree, options?: RouteMapOptions): RouteMap;
33
+ //# sourceMappingURL=create-route-map-from-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-route-map-from-tree.d.ts","sourceRoot":"","sources":["../src/create-route-map-from-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,QAAQ,CAchG"}
@@ -0,0 +1,43 @@
1
+ import { RouteMap } from "./base-route-map.js";
2
+ /**
3
+ * Create a `RouteMap` from a `RouteTree` node structure.
4
+ *
5
+ * Used by framework-router adapters that pass a
6
+ * `RouteTree` produced by `extractMachineRoutes()` rather than calling
7
+ * `createRouteMap()` directly.
8
+ *
9
+ * Traverses all nodes collecting `{ stateId: node.id, path: node.fullPath }` pairs.
10
+ * `node.fullPath` is always the absolute resolved path (e.g. `"/dashboard/overview"`),
11
+ * which is what `RouteMap` needs for browser URL matching. This matches the
12
+ * behaviour of `createRouteMap(machine)`, which also uses `node.fullPath`.
13
+ *
14
+ * @param routeTree - A `RouteTree` as returned by `extractMachineRoutes()`.
15
+ * @param options - Optional configuration (e.g. `{ cacheSize }` to override the LRU cache size).
16
+ * @returns A `RouteMap` for use with any `RouterBridgeBase`-based adapter.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * // Preferred — single call for XState machines:
21
+ * import { createRouteMap } from '@xmachines/play-router';
22
+ * const routeMap = createRouteMap(machine); // takes AnyStateMachine
23
+ *
24
+ * // Two-step form used by framework adapters that work with route trees:
25
+ * import { extractMachineRoutes, createRouteMapFromTree } from '@xmachines/play-router';
26
+ * const routeTree = extractMachineRoutes(machine);
27
+ * const routeMap = createRouteMapFromTree(routeTree); // uses node.fullPath (absolute)
28
+ * ```
29
+ */
30
+ export function createRouteMapFromTree(routeTree, options) {
31
+ const routes = [];
32
+ function traverse(node) {
33
+ if (node.id && node.fullPath) {
34
+ routes.push({ stateId: node.id, path: node.fullPath });
35
+ }
36
+ if (node.children) {
37
+ node.children.forEach(traverse);
38
+ }
39
+ }
40
+ traverse(routeTree.root);
41
+ return new RouteMap(routes, options);
42
+ }
43
+ //# sourceMappingURL=create-route-map-from-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-route-map-from-tree.js","sourceRoot":"","sources":["../src/create-route-map-from-tree.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAG/C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAoB,EAAE,OAAyB;IACrF,MAAM,MAAM,GAA6C,EAAE,CAAC;IAE5D,SAAS,QAAQ,CAAC,IAAe;QAChC,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;IACF,CAAC;IAED,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC"}
@@ -1,66 +1,42 @@
1
- import type { RouteTree } from "./types.js";
1
+ import type { AnyStateMachine } from "xstate";
2
+ import { RouteMap } from "./base-route-map.js";
2
3
  /**
3
- * Bidirectional route resolution interface returned by `createRouteMap()`.
4
- *
5
- * Resolves incoming URL paths to state IDs (with `#` prefix) and extracts
6
- * route parameters. Used by `RouterBridgeBase` to translate router navigation
7
- * events into `play.route` actor events.
8
- *
9
- * @see {@link createRouteMap} for the factory function
10
- * @see {@link BaseRouteMap} for direct state ID ↔ path lookups without params extraction
4
+ * Options for `createRouteMap` and `createRouteMapFromTree`.
11
5
  */
12
- export interface RouteMap {
6
+ export interface RouteMapOptions {
13
7
  /**
14
- * Resolve a URL path to a state ID and params.
15
- *
16
- * @param path - URL pathname (e.g., "/settings/account")
17
- * @returns Object with `to` (state ID with # prefix) and `params`
8
+ * Maximum number of resolved parameterized path lookups to cache.
18
9
  *
19
- * Example:
20
- * ```typescript
21
- * const { to, params } = routeMap.resolve('/settings/account');
22
- * // { to: "#settings", params: { section: "account" } }
10
+ * `RouteMap.getStateIdByPath()` resolves parameterized patterns (e.g.
11
+ * `/profile/:userId`) via URLPattern on every call. Frequently visited
12
+ * paths are cached in an LRU so subsequent lookups are O(1).
23
13
  *
24
- * if (to) {
25
- * actor.send({ type: 'play.route', to, params });
26
- * }
27
- * ```
14
+ * Increase this value for applications with large parameterized route sets
15
+ * or high navigation frequency. Default: `500`.
28
16
  */
29
- resolve(path: string): {
30
- to: string | null;
31
- params: Record<string, string>;
32
- };
17
+ cacheSize?: number;
33
18
  }
34
19
  /**
35
- * Create a RouteMap from a RouteTree for efficient path lookups.
20
+ * Create a `RouteMap` from an XState state machine.
36
21
  *
37
- * Architecture:
38
- * - Pure function - no side effects
39
- * - O(1) lookups for exact matches via Map
40
- * - O(k) bucket-indexed pattern matching with URLPattern, where k = routes in the
41
- * first-segment bucket (typically << total routes)
42
- * - Returns formatted state IDs (with # prefix)
43
- * - Extracts params from matched patterns
22
+ * Extracts all routable states (those with `meta.route`) and builds a bidirectional
23
+ * path ↔ stateId lookup structure. The returned map is used by `RouterBridgeBase`
24
+ * subclasses to translate browser URL changes into `play.route` actor events and
25
+ * vice-versa.
44
26
  *
45
- * **URLPattern requirement:** Dynamic route pattern matching requires `URLPattern`
46
- * to be available on `globalThis`. On Node.js < 24 and older browsers, load a polyfill
47
- * before calling this function:
48
- * ```typescript
49
- * import "urlpattern-polyfill"; // before importing @xmachines/play-router
50
- * ```
51
- * If `URLPattern` is unavailable, parameterized routes will not match and a
52
- * `console.warn` is emitted. Static (exact) routes are unaffected.
27
+ * @param machine - XState v5 state machine with `meta.route` annotations on states.
28
+ * @param options - Optional configuration. Pass `{ cacheSize }` to override the
29
+ * default LRU cache size for parameterized path lookups.
30
+ * @returns A `RouteMap` for passing to any `RouterBridgeBase`-based adapter.
53
31
  *
54
- * Usage:
32
+ * @example
55
33
  * ```typescript
56
- * const routeTree = extractMachineRoutes(machine);
57
- * const routeMap = createRouteMap(routeTree);
34
+ * import { createRouteMap } from '@xmachines/play-router';
35
+ * import { connectRouter } from '@xmachines/play-dom-router';
58
36
  *
59
- * const { to, params } = routeMap.resolve('/settings/account');
60
- * if (to) {
61
- * actor.send({ type: 'play.route', to, params });
62
- * }
37
+ * const routeMap = createRouteMap(myMachine);
38
+ * const disconnect = connectRouter({ actor, router, routeMap });
63
39
  * ```
64
40
  */
65
- export declare function createRouteMap(routeTree: RouteTree): RouteMap;
41
+ export declare function createRouteMap(machine: AnyStateMachine, options?: RouteMapOptions): RouteMap;
66
42
  //# sourceMappingURL=create-route-map.d.ts.map