@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 +1 @@
1
- {"version":3,"file":"create-route-map.d.ts","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AAgBvD;;;;;;;;;GASG;AACH,MAAM,WAAW,QAAQ;IACxB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG;QACtB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAC/B,CAAC;CACF;AAyCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,GAAG,QAAQ,CA0D7D"}
1
+ {"version":3,"file":"create-route-map.d.ts","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAG9C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC/B;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,QAAQ,CAU5F"}
@@ -1,124 +1,34 @@
1
- function getURLPatternCtor() {
2
- return globalThis["URLPattern"];
3
- }
4
- function getIndexKey(path) {
5
- const trimmed = path.startsWith("/") ? path.slice(1) : path;
6
- if (trimmed.length === 0)
7
- return "/";
8
- const segment = trimmed.split("/")[0];
9
- if (segment === undefined || segment.startsWith(":"))
10
- return "*";
11
- return segment;
12
- }
13
- function getCandidates(patternBuckets, indexKey) {
14
- const bucket = patternBuckets.get(indexKey) ?? [];
15
- const wildcardBucket = patternBuckets.get("*") ?? [];
16
- if (bucket.length === 0)
17
- return wildcardBucket;
18
- if (wildcardBucket.length === 0)
19
- return bucket;
20
- const merged = [];
21
- let b = 0;
22
- let w = 0;
23
- while (b < bucket.length && w < wildcardBucket.length) {
24
- if (bucket[b].order < wildcardBucket[w].order) {
25
- merged.push(bucket[b]);
26
- b += 1;
27
- }
28
- else {
29
- merged.push(wildcardBucket[w]);
30
- w += 1;
31
- }
32
- }
33
- while (b < bucket.length) {
34
- merged.push(bucket[b]);
35
- b += 1;
36
- }
37
- while (w < wildcardBucket.length) {
38
- merged.push(wildcardBucket[w]);
39
- w += 1;
40
- }
41
- return merged;
42
- }
1
+ import { extractMachineRoutes } from "./extract-routes.js";
2
+ import { getRoutableRoutes } from "./query.js";
3
+ import { RouteMap } from "./base-route-map.js";
43
4
  /**
44
- * Create a RouteMap from a RouteTree for efficient path lookups.
5
+ * Create a `RouteMap` from an XState state machine.
45
6
  *
46
- * Architecture:
47
- * - Pure function - no side effects
48
- * - O(1) lookups for exact matches via Map
49
- * - O(k) bucket-indexed pattern matching with URLPattern, where k = routes in the
50
- * first-segment bucket (typically << total routes)
51
- * - Returns formatted state IDs (with # prefix)
52
- * - Extracts params from matched patterns
7
+ * Extracts all routable states (those with `meta.route`) and builds a bidirectional
8
+ * path ↔ stateId lookup structure. The returned map is used by `RouterBridgeBase`
9
+ * subclasses to translate browser URL changes into `play.route` actor events and
10
+ * vice-versa.
53
11
  *
54
- * **URLPattern requirement:** Dynamic route pattern matching requires `URLPattern`
55
- * to be available on `globalThis`. On Node.js < 24 and older browsers, load a polyfill
56
- * before calling this function:
57
- * ```typescript
58
- * import "urlpattern-polyfill"; // before importing @xmachines/play-router
59
- * ```
60
- * If `URLPattern` is unavailable, parameterized routes will not match and a
61
- * `console.warn` is emitted. Static (exact) routes are unaffected.
12
+ * @param machine - XState v5 state machine with `meta.route` annotations on states.
13
+ * @param options - Optional configuration. Pass `{ cacheSize }` to override the
14
+ * default LRU cache size for parameterized path lookups.
15
+ * @returns A `RouteMap` for passing to any `RouterBridgeBase`-based adapter.
62
16
  *
63
- * Usage:
17
+ * @example
64
18
  * ```typescript
65
- * const routeTree = extractMachineRoutes(machine);
66
- * const routeMap = createRouteMap(routeTree);
19
+ * import { createRouteMap } from '@xmachines/play-router';
20
+ * import { connectRouter } from '@xmachines/play-dom-router';
67
21
  *
68
- * const { to, params } = routeMap.resolve('/settings/account');
69
- * if (to) {
70
- * actor.send({ type: 'play.route', to, params });
71
- * }
22
+ * const routeMap = createRouteMap(myMachine);
23
+ * const disconnect = connectRouter({ actor, router, routeMap });
72
24
  * ```
73
25
  */
74
- export function createRouteMap(routeTree) {
75
- // Build bucket-indexed pattern matchers for routes with dynamic segments
76
- const patternBuckets = new Map();
77
- let patternOrder = 0;
78
- const URLPatternCtor = getURLPatternCtor();
79
- function collectPatterns(node) {
80
- if (node.routable && node.pattern) {
81
- if (!URLPatternCtor) {
82
- console.warn("[RouteMap] URLPattern is not available. Load a polyfill (e.g. urlpattern-polyfill) before using @xmachines/play-router on Node < 24 or older browsers.");
83
- return;
84
- }
85
- try {
86
- const urlPattern = new URLPatternCtor({ pathname: node.pattern });
87
- const bucketKey = getIndexKey(node.pattern);
88
- const bucket = patternBuckets.get(bucketKey) ?? [];
89
- bucket.push({ pattern: urlPattern, node, order: patternOrder++ });
90
- patternBuckets.set(bucketKey, bucket);
91
- }
92
- catch (err) {
93
- console.warn("[RouteMap] Invalid pattern:", node.pattern, err);
94
- }
95
- }
96
- for (const child of node.children) {
97
- collectPatterns(child);
98
- }
99
- }
100
- collectPatterns(routeTree.root);
101
- return {
102
- resolve(path) {
103
- // O(1) exact match via routeTree.byPath Map (unchanged)
104
- const exactMatch = routeTree.byPath.get(path);
105
- if (exactMatch?.routable) {
106
- return { to: `#${exactMatch.id}`, params: {} };
107
- }
108
- // Bucket-indexed pattern match: only scan candidates for this path's first segment
109
- const candidates = getCandidates(patternBuckets, getIndexKey(path));
110
- for (const { pattern, node } of candidates) {
111
- const match = pattern.exec({ pathname: path });
112
- if (match) {
113
- const params = {};
114
- if (match.pathname.groups) {
115
- Object.assign(params, match.pathname.groups);
116
- }
117
- return { to: `#${node.id}`, params };
118
- }
119
- }
120
- return { to: null, params: {} };
121
- },
122
- };
26
+ export function createRouteMap(machine, options) {
27
+ const routeTree = extractMachineRoutes(machine);
28
+ const routes = getRoutableRoutes(routeTree);
29
+ return new RouteMap(routes.map((node) => ({
30
+ stateId: node.stateId,
31
+ path: node.fullPath,
32
+ })), options);
123
33
  }
124
34
  //# sourceMappingURL=create-route-map.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-route-map.js","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"create-route-map.js","sourceRoot":"","sources":["../src/create-route-map.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAmB/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,cAAc,CAAC,OAAwB,EAAE,OAAyB;IACjF,MAAM,SAAS,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IAC5C,OAAO,IAAI,QAAQ,CAClB,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,IAAI,CAAC,QAAQ;KACnB,CAAC,CAAC,EACH,OAAO,CACP,CAAC;AACH,CAAC"}
@@ -0,0 +1,152 @@
1
+ import { PlayError } from "@xmachines/play";
2
+ /**
3
+ * Thrown when `RouterBridgeBase.syncActorFromRouter()` fails to process a router
4
+ * location change and send the corresponding `play.route` event to the actor.
5
+ *
6
+ * The `cause` property wraps the original error.
7
+ *
8
+ * **Error code:** `PLAY_ROUTER_SYNC_FAILED`
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { RouterSyncError } from "@xmachines/play-router/errors";
13
+ *
14
+ * try {
15
+ * bridge.connect();
16
+ * } catch (err) {
17
+ * if (err instanceof RouterSyncError) {
18
+ * console.error("Router sync failed:", err.message, err.cause);
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ export declare class RouterSyncError extends PlayError {
24
+ constructor(message: string, options?: ErrorOptions);
25
+ }
26
+ /**
27
+ * Thrown by `RouterBridgeBase.connect()` when a second bridge tries to connect
28
+ * to an actor that already has an active bridge connection.
29
+ *
30
+ * One actor may only be connected to one router bridge at a time. Two bridges
31
+ * sharing the same actor would both receive and process every router navigation
32
+ * event, sending duplicate `play.route` events to the actor and producing
33
+ * conflicting echo-suppression state via `lastSyncedPath`.
34
+ *
35
+ * **How to fix:** Call `bridge.disconnect()` on the first bridge before
36
+ * connecting a second one, or ensure only one `connectRouter()` call is active
37
+ * per actor at any given time.
38
+ *
39
+ * **Error code:** `PLAY_ROUTER_DUPLICATE_BRIDGE`
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { DuplicateBridgeError } from "@xmachines/play-router/errors";
44
+ *
45
+ * try {
46
+ * bridge.connect();
47
+ * } catch (err) {
48
+ * if (err instanceof DuplicateBridgeError) {
49
+ * console.error("Actor already has an active router bridge — disconnect first.");
50
+ * }
51
+ * }
52
+ * ```
53
+ */
54
+ export declare class DuplicateBridgeError extends PlayError {
55
+ constructor();
56
+ }
57
+ /**
58
+ * Thrown by the `RouteMap` constructor when a parameterized route is registered but
59
+ * the `URLPattern` API is not available in the current runtime environment and no
60
+ * polyfill has been loaded.
61
+ *
62
+ * **How to fix:** Load `urlpattern-polyfill` at your application entry point, before
63
+ * any import of `@xmachines/play-router`:
64
+ *
65
+ * ```typescript
66
+ * // app entry point — must run first
67
+ * import "urlpattern-polyfill";
68
+ * ```
69
+ *
70
+ * URLPattern is natively available on Node.js ≥ 24 and modern browsers
71
+ * (Chrome 95+, Firefox 117+, Safari 16.4+). Use the polyfill on older targets.
72
+ *
73
+ * **Error code:** `PLAY_ROUTE_MAP_URLPATTERN_UNAVAILABLE`
74
+ */
75
+ export declare class URLPatternUnavailableError extends PlayError {
76
+ constructor();
77
+ }
78
+ /**
79
+ * Thrown by `validateRouteFormat()` when a state declares `meta.route` with an
80
+ * empty string path.
81
+ *
82
+ * **Error code:** `PLAY_ROUTE_EMPTY_PATH`
83
+ */
84
+ export declare class EmptyRoutePathError extends PlayError {
85
+ /** The state ID whose route path is empty. */
86
+ readonly stateId: string;
87
+ constructor(stateId: string);
88
+ }
89
+ /**
90
+ * Thrown by `validateStateExists()` when a route references a state ID that does
91
+ * not exist in the machine graph.
92
+ *
93
+ * **Error code:** `PLAY_ROUTE_INVALID_STATE_ID`
94
+ */
95
+ export declare class InvalidStateIdError extends PlayError {
96
+ /** The state ID that was referenced but not found. */
97
+ readonly stateId: string;
98
+ constructor(stateId: string);
99
+ }
100
+ /**
101
+ * Thrown by `detectDuplicateRoutes()` when two or more states share the same URL path.
102
+ *
103
+ * **Error code:** `PLAY_ROUTE_DUPLICATE_PATH`
104
+ */
105
+ export declare class DuplicateRoutePathError extends PlayError {
106
+ /** Human-readable list of conflicting path → state-ID mappings. */
107
+ readonly duplicates: string[];
108
+ constructor(duplicates: string[]);
109
+ }
110
+ /**
111
+ * Thrown by `machineToGraph()` when an XState state node has an unrecognised
112
+ * `.type` value.
113
+ *
114
+ * **Error code:** `PLAY_ROUTE_UNKNOWN_STATE_TYPE`
115
+ */
116
+ export declare class UnknownStateTypeError extends PlayError {
117
+ /** The unrecognised state type string. */
118
+ readonly stateType: string;
119
+ /** The ID of the state node with the unknown type. */
120
+ readonly nodeId: string;
121
+ constructor(stateType: string, nodeId: string, validTypes: string[]);
122
+ }
123
+ /**
124
+ * Thrown by the `RouteMap` constructor when a route pattern string cannot be compiled
125
+ * by `URLPattern`. The `pattern` field contains the rejected string; `cause` wraps the
126
+ * original `URLPattern` constructor error.
127
+ *
128
+ * Common causes:
129
+ * - Unmatched parentheses or brackets in the pattern string
130
+ * - Characters that are invalid in a URL pathname pattern
131
+ *
132
+ * **Error code:** `PLAY_ROUTE_MAP_INVALID_PATTERN`
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * import { InvalidRoutePatternError } from "@xmachines/play-router/errors";
137
+ *
138
+ * try {
139
+ * const routeMap = createRouteMap(machine);
140
+ * } catch (err) {
141
+ * if (err instanceof InvalidRoutePatternError) {
142
+ * console.error(`Bad route pattern: "${err.pattern}"`, err.cause);
143
+ * }
144
+ * }
145
+ * ```
146
+ */
147
+ export declare class InvalidRoutePatternError extends PlayError {
148
+ /** The route pattern string that could not be compiled by URLPattern. */
149
+ readonly pattern: string;
150
+ constructor(pattern: string, options?: ErrorOptions);
151
+ }
152
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,eAAgB,SAAQ,SAAS;gBACjC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAInD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,oBAAqB,SAAQ,SAAS;;CAUlD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,0BAA2B,SAAQ,SAAS;;CASxD;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,SAAS;IACjD,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,MAAM;CAS3B;AAED;;;;;GAKG;AACH,qBAAa,mBAAoB,SAAQ,SAAS;IACjD,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,MAAM;CAS3B;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,SAAS;IACrD,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;gBAElB,UAAU,EAAE,MAAM,EAAE;CAWhC;AAED;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,SAAS;IACnD,0CAA0C;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE;CAWnE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,wBAAyB,SAAQ,SAAS;IACtD,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAUnD"}
package/dist/errors.js ADDED
@@ -0,0 +1,186 @@
1
+ import { PlayError } from "@xmachines/play";
2
+ /**
3
+ * Thrown when `RouterBridgeBase.syncActorFromRouter()` fails to process a router
4
+ * location change and send the corresponding `play.route` event to the actor.
5
+ *
6
+ * The `cause` property wraps the original error.
7
+ *
8
+ * **Error code:** `PLAY_ROUTER_SYNC_FAILED`
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { RouterSyncError } from "@xmachines/play-router/errors";
13
+ *
14
+ * try {
15
+ * bridge.connect();
16
+ * } catch (err) {
17
+ * if (err instanceof RouterSyncError) {
18
+ * console.error("Router sync failed:", err.message, err.cause);
19
+ * }
20
+ * }
21
+ * ```
22
+ */
23
+ export class RouterSyncError extends PlayError {
24
+ constructor(message, options) {
25
+ super("RouterBridgeBase", "PLAY_ROUTER_SYNC_FAILED", message, options);
26
+ this.name = "RouterSyncError";
27
+ }
28
+ }
29
+ /**
30
+ * Thrown by `RouterBridgeBase.connect()` when a second bridge tries to connect
31
+ * to an actor that already has an active bridge connection.
32
+ *
33
+ * One actor may only be connected to one router bridge at a time. Two bridges
34
+ * sharing the same actor would both receive and process every router navigation
35
+ * event, sending duplicate `play.route` events to the actor and producing
36
+ * conflicting echo-suppression state via `lastSyncedPath`.
37
+ *
38
+ * **How to fix:** Call `bridge.disconnect()` on the first bridge before
39
+ * connecting a second one, or ensure only one `connectRouter()` call is active
40
+ * per actor at any given time.
41
+ *
42
+ * **Error code:** `PLAY_ROUTER_DUPLICATE_BRIDGE`
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * import { DuplicateBridgeError } from "@xmachines/play-router/errors";
47
+ *
48
+ * try {
49
+ * bridge.connect();
50
+ * } catch (err) {
51
+ * if (err instanceof DuplicateBridgeError) {
52
+ * console.error("Actor already has an active router bridge — disconnect first.");
53
+ * }
54
+ * }
55
+ * ```
56
+ */
57
+ export class DuplicateBridgeError extends PlayError {
58
+ constructor() {
59
+ super("RouterBridgeBase", "PLAY_ROUTER_DUPLICATE_BRIDGE", "An actor can only be connected to one router bridge at a time. " +
60
+ "Call disconnect() on the existing bridge before connecting a new one.");
61
+ this.name = "DuplicateBridgeError";
62
+ }
63
+ }
64
+ /**
65
+ * Thrown by the `RouteMap` constructor when a parameterized route is registered but
66
+ * the `URLPattern` API is not available in the current runtime environment and no
67
+ * polyfill has been loaded.
68
+ *
69
+ * **How to fix:** Load `urlpattern-polyfill` at your application entry point, before
70
+ * any import of `@xmachines/play-router`:
71
+ *
72
+ * ```typescript
73
+ * // app entry point — must run first
74
+ * import "urlpattern-polyfill";
75
+ * ```
76
+ *
77
+ * URLPattern is natively available on Node.js ≥ 24 and modern browsers
78
+ * (Chrome 95+, Firefox 117+, Safari 16.4+). Use the polyfill on older targets.
79
+ *
80
+ * **Error code:** `PLAY_ROUTE_MAP_URLPATTERN_UNAVAILABLE`
81
+ */
82
+ export class URLPatternUnavailableError extends PlayError {
83
+ constructor() {
84
+ super("RouteMap", "PLAY_ROUTE_MAP_URLPATTERN_UNAVAILABLE", "URLPattern is not available. Load a polyfill (e.g. urlpattern-polyfill) before using @xmachines/play-router on Node < 24 or older browsers.");
85
+ this.name = "URLPatternUnavailableError";
86
+ }
87
+ }
88
+ /**
89
+ * Thrown by `validateRouteFormat()` when a state declares `meta.route` with an
90
+ * empty string path.
91
+ *
92
+ * **Error code:** `PLAY_ROUTE_EMPTY_PATH`
93
+ */
94
+ export class EmptyRoutePathError extends PlayError {
95
+ /** The state ID whose route path is empty. */
96
+ stateId;
97
+ constructor(stateId) {
98
+ super("RouteMap", "PLAY_ROUTE_EMPTY_PATH", `Empty route path in state "${stateId}": routes must have a non-empty path`);
99
+ this.name = "EmptyRoutePathError";
100
+ this.stateId = stateId;
101
+ }
102
+ }
103
+ /**
104
+ * Thrown by `validateStateExists()` when a route references a state ID that does
105
+ * not exist in the machine graph.
106
+ *
107
+ * **Error code:** `PLAY_ROUTE_INVALID_STATE_ID`
108
+ */
109
+ export class InvalidStateIdError extends PlayError {
110
+ /** The state ID that was referenced but not found. */
111
+ stateId;
112
+ constructor(stateId) {
113
+ super("RouteMap", "PLAY_ROUTE_INVALID_STATE_ID", `Route references non-existent state ID: ${stateId}`);
114
+ this.name = "InvalidStateIdError";
115
+ this.stateId = stateId;
116
+ }
117
+ }
118
+ /**
119
+ * Thrown by `detectDuplicateRoutes()` when two or more states share the same URL path.
120
+ *
121
+ * **Error code:** `PLAY_ROUTE_DUPLICATE_PATH`
122
+ */
123
+ export class DuplicateRoutePathError extends PlayError {
124
+ /** Human-readable list of conflicting path → state-ID mappings. */
125
+ duplicates;
126
+ constructor(duplicates) {
127
+ super("RouteMap", "PLAY_ROUTE_DUPLICATE_PATH", `Duplicate route paths detected:\n${duplicates.join("\n")}\n\n` +
128
+ `Each route path must map to exactly one state.\n` +
129
+ `Use different paths (e.g., /dashboard vs /) or conditional rendering within a single state.`);
130
+ this.name = "DuplicateRoutePathError";
131
+ this.duplicates = duplicates;
132
+ }
133
+ }
134
+ /**
135
+ * Thrown by `machineToGraph()` when an XState state node has an unrecognised
136
+ * `.type` value.
137
+ *
138
+ * **Error code:** `PLAY_ROUTE_UNKNOWN_STATE_TYPE`
139
+ */
140
+ export class UnknownStateTypeError extends PlayError {
141
+ /** The unrecognised state type string. */
142
+ stateType;
143
+ /** The ID of the state node with the unknown type. */
144
+ nodeId;
145
+ constructor(stateType, nodeId, validTypes) {
146
+ super("machineToGraph", "PLAY_ROUTE_UNKNOWN_STATE_TYPE", `Unknown XState state type "${stateType}" on node "${nodeId}". ` +
147
+ `Expected one of: ${validTypes.join(", ")}.`);
148
+ this.name = "UnknownStateTypeError";
149
+ this.stateType = stateType;
150
+ this.nodeId = nodeId;
151
+ }
152
+ }
153
+ /**
154
+ * Thrown by the `RouteMap` constructor when a route pattern string cannot be compiled
155
+ * by `URLPattern`. The `pattern` field contains the rejected string; `cause` wraps the
156
+ * original `URLPattern` constructor error.
157
+ *
158
+ * Common causes:
159
+ * - Unmatched parentheses or brackets in the pattern string
160
+ * - Characters that are invalid in a URL pathname pattern
161
+ *
162
+ * **Error code:** `PLAY_ROUTE_MAP_INVALID_PATTERN`
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * import { InvalidRoutePatternError } from "@xmachines/play-router/errors";
167
+ *
168
+ * try {
169
+ * const routeMap = createRouteMap(machine);
170
+ * } catch (err) {
171
+ * if (err instanceof InvalidRoutePatternError) {
172
+ * console.error(`Bad route pattern: "${err.pattern}"`, err.cause);
173
+ * }
174
+ * }
175
+ * ```
176
+ */
177
+ export class InvalidRoutePatternError extends PlayError {
178
+ /** The route pattern string that could not be compiled by URLPattern. */
179
+ pattern;
180
+ constructor(pattern, options) {
181
+ super("RouteMap", "PLAY_ROUTE_MAP_INVALID_PATTERN", `Invalid route pattern: "${pattern}"`, options);
182
+ this.name = "InvalidRoutePatternError";
183
+ this.pattern = pattern;
184
+ }
185
+ }
186
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -3,39 +3,13 @@ import type { RouteTree } from "./types.js";
3
3
  /**
4
4
  * Extract complete route tree from state machine graph
5
5
  *
6
- * Crawls machine starting from root, visits all state nodes (including nested
7
- * and parallel), extracts meta.route metadata, validates route references,
8
- * and builds hierarchical tree.
6
+ * Converts the XState machine to a @statelyai/graph Graph via machineToGraph(),
7
+ * then walks graph nodes to extract route metadata, validate route references,
8
+ * and build a hierarchical RouteTree. The graph is attached to the returned
9
+ * RouteTree for downstream transition-aware queries.
9
10
  *
10
11
  * @param machine - XState v5 state machine
11
- * @returns Route tree with root, byStateId map, and byPath map
12
- * @throws {Error} If route references non-existent state
13
- * @throws {Error} If route path is malformed (doesn't start with /)
14
- * @throws {Error} If parallel states define conflicting routes
15
- *
16
- * @example
17
- * ```typescript
18
- * import { createMachine } from 'xstate';
19
- * import { extractMachineRoutes } from '@xmachines/play-router';
20
- *
21
- * const machine = createMachine({
22
- * initial: 'home',
23
- * states: {
24
- * home: { id: 'home', meta: { route: '/' } },
25
- * dashboard: {
26
- * id: 'dashboard',
27
- * meta: { route: '/dashboard' },
28
- * initial: 'overview',
29
- * states: {
30
- * overview: { id: 'overview', meta: { route: '/overview' } }
31
- * }
32
- * }
33
- * }
34
- * });
35
- *
36
- * const tree = extractMachineRoutes(machine);
37
- * console.log(tree.byPath.get('/dashboard/overview'));
38
- * ```
12
+ * @returns Route tree with root, byStateId map, byPath map, and graph
39
13
  */
40
14
  export declare const extractMachineRoutes: (machine: AnyStateMachine) => RouteTree;
41
15
  //# sourceMappingURL=extract-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extract-routes.d.ts","sourceRoot":"","sources":["../src/extract-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAa,MAAM,QAAQ,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAM5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,eAAe,KAAG,SAsB/D,CAAC"}
1
+ {"version":3,"file":"extract-routes.d.ts","sourceRoot":"","sources":["../src/extract-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAA6C,MAAM,YAAY,CAAC;AA2EvF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,eAAe,KAAG,SAmB/D,CAAC"}