@xmachines/play-router 1.0.0-beta.8 → 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 +259 -335
  3. package/dist/base-route-map.d.ts +136 -0
  4. package/dist/base-route-map.d.ts.map +1 -0
  5. package/dist/base-route-map.js +182 -0
  6. package/dist/base-route-map.js.map +1 -0
  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 +29 -33
  16. package/dist/create-route-map.d.ts.map +1 -1
  17. package/dist/create-route-map.js +25 -64
  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 -51
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +13 -133
  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 +196 -32
  44. package/dist/router-bridge-base.d.ts.map +1 -1
  45. package/dist/router-bridge-base.js +319 -85
  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 +42 -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 -119
  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 -68
  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
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Shared URLPattern utilities — internal module, NOT exported from index.ts
3
+ *
4
+ * Provides the common URLPattern plumbing shared by:
5
+ * - `create-route-map.ts`
6
+ * - `base-route-map.ts`
7
+ * - `find-route.ts`
8
+ * - `router-bridge-base.ts`
9
+ *
10
+ * URLPattern is accessed via globalThis — consumers must load a polyfill on
11
+ * environments without native support (Node < 24, older browsers).
12
+ *
13
+ * @internal
14
+ */
15
+ /**
16
+ * Retrieve the URLPattern constructor from `globalThis`, or `undefined` if
17
+ * it is not available (Node < 24, older browsers without a polyfill).
18
+ */
19
+ export function getURLPatternCtor() {
20
+ return globalThis["URLPattern"];
21
+ }
22
+ /**
23
+ * Check whether a route path is a parameterized pattern rather than a concrete URL.
24
+ *
25
+ * A pattern contains `:param` segments (required or optional) and/or the `*`
26
+ * wildcard — both require URLPattern matching and carry no concrete values, so
27
+ * they can never be pushed to a browser URL verbatim.
28
+ *
29
+ * This is THE single source of truth for "is this path a pattern?" — used by
30
+ * `RouteMap` to decide static-map vs pattern-bucket indexing and by
31
+ * `RouterBridgeBase.resolveNavigationPath` to skip non-navigable routes, so the
32
+ * two checks can never drift apart.
33
+ *
34
+ * @param path - URL path or route pattern string (e.g. `/profile/:userId`, `/docs/*`)
35
+ * @returns `true` if the path contains `:param` or `*` pattern syntax
36
+ */
37
+ export function isParameterizedPattern(path) {
38
+ return path.includes(":") || path.includes("*");
39
+ }
40
+ /**
41
+ * Matches hyphenated `:param` names (e.g. `:cat-id`, `:page-num?`).
42
+ * URLPattern requires parameter names to be valid JS identifiers, so hyphenated
43
+ * names must be rewritten before compilation.
44
+ */
45
+ const HYPHENATED_PARAM_RE = /:([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)+)(\??)/g;
46
+ /**
47
+ * Normalize a route path so its parameter names are valid URLPattern identifiers.
48
+ *
49
+ * URLPattern requires parameter names to be valid JS identifiers (no hyphens).
50
+ * Hyphenated names like `:cat-id` are replaced with underscored equivalents
51
+ * (`:cat_id`) for the URLPattern compilation step. Callers that need the captured
52
+ * group values (not just a match test) must map the normalized group names back to
53
+ * the original param names via {@link getNormalizedParamNameMap}.
54
+ *
55
+ * @example
56
+ * normalizeParamNames("/docs/:cat-id/:page-num?")
57
+ * // → "/docs/:cat_id/:page_num?"
58
+ */
59
+ export function normalizeParamNames(path) {
60
+ return path.replace(HYPHENATED_PARAM_RE, (_m, name, optional) => {
61
+ return `:${name.replace(/-/g, "_")}${optional}`;
62
+ });
63
+ }
64
+ /**
65
+ * Build a map from normalized URLPattern group names back to the original
66
+ * (hyphenated) param names for a route pattern.
67
+ *
68
+ * Only params rewritten by {@link normalizeParamNames} appear in the map —
69
+ * params that are already valid identifiers keep their own name and need no entry.
70
+ *
71
+ * @example
72
+ * getNormalizedParamNameMap("/docs/:cat-id/:plain")
73
+ * // → Map { "cat_id" → "cat-id" }
74
+ */
75
+ export function getNormalizedParamNameMap(path) {
76
+ const nameMap = new Map();
77
+ for (const match of path.matchAll(HYPHENATED_PARAM_RE)) {
78
+ const original = match[1];
79
+ nameMap.set(original.replace(/-/g, "_"), original);
80
+ }
81
+ return nameMap;
82
+ }
83
+ /**
84
+ * Module-level cache of compiled URLPattern instances keyed by pattern string.
85
+ *
86
+ * Route patterns are compiled on every navigation-time lookup path
87
+ * (`extractRouteParams`, `findRouteByPath`) and once per `RouteMap`
88
+ * construction; compiling the same route pattern each time is wasted work.
89
+ * Growth is bounded by the number of distinct route patterns in the
90
+ * application's route maps. The cache is invalidated when the URLPattern
91
+ * constructor changes (tests swap `globalThis.URLPattern`).
92
+ */
93
+ const compiledPatternCache = new Map();
94
+ let compiledPatternCtor;
95
+ /**
96
+ * Compile a route pattern to a `URLPattern`, reusing the module-level cache.
97
+ *
98
+ * The pattern is normalized with {@link normalizeParamNames} before compilation
99
+ * so hyphenated param names (`:cat-id`) become valid URLPattern identifiers
100
+ * (`:cat_id`). Callers that extract group values must map the normalized names
101
+ * back via {@link getNormalizedParamNameMap}.
102
+ *
103
+ * Compilation errors from the URLPattern constructor propagate as-is — callers
104
+ * that need a typed error wrap this in `InvalidRoutePatternError`.
105
+ *
106
+ * @param pattern - Route pattern string (e.g. `/profile/:userId`)
107
+ * @param Ctor - URLPattern constructor retrieved via {@link getURLPatternCtor}
108
+ */
109
+ export function getCompiledPattern(pattern, Ctor) {
110
+ if (Ctor !== compiledPatternCtor) {
111
+ compiledPatternCache.clear();
112
+ compiledPatternCtor = Ctor;
113
+ }
114
+ let compiled = compiledPatternCache.get(pattern);
115
+ if (compiled === undefined) {
116
+ compiled = new Ctor({ pathname: normalizeParamNames(pattern) });
117
+ compiledPatternCache.set(pattern, compiled);
118
+ }
119
+ return compiled;
120
+ }
121
+ /**
122
+ * Compute the bucket index key for a given path or pattern.
123
+ *
124
+ * The key is the first path segment (e.g. `"settings"` for `"/settings/:id"`).
125
+ * Parameterised first segments (`:lang`) fall into the `"*"` wildcard bucket.
126
+ * The root path `"/"` maps to the key `"/"`.
127
+ *
128
+ * @param path - URL path or route pattern string
129
+ */
130
+ export function getIndexKey(path) {
131
+ const trimmed = path.startsWith("/") ? path.slice(1) : path;
132
+ if (trimmed.length === 0)
133
+ return "/";
134
+ const segment = trimmed.split("/")[0];
135
+ if (segment === undefined || segment.startsWith(":"))
136
+ return "*";
137
+ return segment;
138
+ }
139
+ /**
140
+ * Merge the named-segment bucket and the wildcard bucket for a given index key,
141
+ * preserving insertion order (by `order` field).
142
+ *
143
+ * @param patternBuckets - Map of bucket key → ordered pattern entries
144
+ * @param indexKey - First-segment key computed by `getIndexKey`
145
+ */
146
+ export function getCandidates(patternBuckets, indexKey) {
147
+ const bucket = patternBuckets.get(indexKey) ?? [];
148
+ const wildcardBucket = patternBuckets.get("*") ?? [];
149
+ if (bucket.length === 0)
150
+ return wildcardBucket;
151
+ if (wildcardBucket.length === 0)
152
+ return bucket;
153
+ const merged = [];
154
+ let b = 0;
155
+ let w = 0;
156
+ while (b < bucket.length && w < wildcardBucket.length) {
157
+ if (bucket[b].order < wildcardBucket[w].order) {
158
+ merged.push(bucket[b]);
159
+ b += 1;
160
+ }
161
+ else {
162
+ merged.push(wildcardBucket[w]);
163
+ w += 1;
164
+ }
165
+ }
166
+ while (b < bucket.length) {
167
+ merged.push(bucket[b]);
168
+ b += 1;
169
+ }
170
+ while (w < wildcardBucket.length) {
171
+ merged.push(wildcardBucket[w]);
172
+ w += 1;
173
+ }
174
+ return merged;
175
+ }
176
+ //# sourceMappingURL=url-pattern-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-pattern-utils.js","sourceRoot":"","sources":["../src/url-pattern-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAgBH;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAChC,OAAQ,UAAsC,CAAC,YAAY,CAA+B,CAAC;AAC5F,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IAClD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,mBAAmB,GAAG,iDAAiD,CAAC;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC/C,OAAO,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC,EAAE,EAAE,IAAY,EAAE,QAAgB,EAAE,EAAE;QAC/E,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,QAAQ,EAAE,CAAC;IACjD,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACrD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACxD,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAA0B,CAAC;AAC/D,IAAI,mBAA+C,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe,EAAE,IAAoB;IACvE,IAAI,IAAI,KAAK,mBAAmB,EAAE,CAAC;QAClC,oBAAoB,CAAC,KAAK,EAAE,CAAC;QAC7B,mBAAmB,GAAG,IAAI,CAAC;IAC5B,CAAC;IACD,IAAI,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACjD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC5B,QAAQ,GAAG,IAAI,IAAI,CAAC,EAAE,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAChE,oBAAoB,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,GAAG,CAAC;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IACjE,OAAO,OAAO,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC5B,cAAgC,EAChC,QAAgB;IAEhB,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IAClD,MAAM,cAAc,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;IACrD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,cAAc,CAAC;IAC/C,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAE/C,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC;QACvD,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YACvB,CAAC,IAAI,CAAC,CAAC;QACR,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/B,CAAC,IAAI,CAAC,CAAC;QACR,CAAC;IACF,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC,IAAI,CAAC,CAAC;IACR,CAAC;IACD,OAAO,CAAC,GAAG,cAAc,CAAC,MAAM,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/B,CAAC,IAAI,CAAC,CAAC;IACR,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC"}
@@ -1,39 +1,57 @@
1
- import type { StateNode } from "xstate";
2
- import type { RouteInfo } from "./types.js";
1
+ /**
2
+ * A route entry with its RESOLVED full path — the path after relative routes have
3
+ * been prefixed with their parent's full path during tree construction.
4
+ *
5
+ * `RouteNode` satisfies this shape structurally.
6
+ */
7
+ export interface ResolvedRoutePath {
8
+ /** State identifier owning the route (e.g. `"app.dashboard.settings"`). */
9
+ stateId: string;
10
+ /** Resolved absolute route path (e.g. `"/dashboard/settings"`). */
11
+ fullPath: string;
12
+ }
3
13
  /**
4
14
  * Validate route path format
5
15
  *
6
- * Ensures route paths start with "/" (absolute or relative).
7
- * Per Route Protocol spec, all routes must have explicit path structure.
16
+ * Ensures route paths are non-empty strings. Both absolute paths ("/foo")
17
+ * and relative paths ("foo", "child/nested") are accepted. Relative routes
18
+ * inherit their parent state's path prefix when the tree is built.
8
19
  *
9
- * @param routePath - Route path to validate
20
+ * @param routePath - Route path to validate (absolute or relative)
10
21
  * @param stateId - State identifier for error messages
11
- * @throws {Error} If route path doesn't start with /
22
+ * @throws {EmptyRoutePathError} If route path is empty
12
23
  */
13
24
  export declare const validateRouteFormat: (routePath: string, stateId: string) => void;
14
25
  /**
15
- * Validate state exists in state map
26
+ * Validate state exists in state ID set
16
27
  *
17
28
  * Ensures referenced state IDs exist in the machine graph.
18
29
  * Build-time validation prevents broken route references.
19
30
  *
20
31
  * @param stateId - State identifier to validate
21
- * @param stateMap - Map of all state IDs to StateNodes
22
- * @throws {Error} If state ID doesn't exist in map
32
+ * @param stateIds - Set of all known state IDs from the machine graph
33
+ * @throws {InvalidStateIdError} If state ID doesn't exist in set
23
34
  */
24
- export declare const validateStateExists: (stateId: string, stateMap: Map<string, StateNode>) => void;
35
+ export declare const validateStateExists: (stateId: string, stateIds: Set<string>) => void;
25
36
  /**
26
37
  * Detect duplicate route paths
27
38
  *
28
- * THROWS ERROR when multiple states share the same route path.
39
+ * THROWS ERROR when multiple states share the same RESOLVED full path.
40
+ *
41
+ * Detection operates on resolved full paths (after relative routes have been
42
+ * prefixed with their parent's path in `buildRouteTree`), not on raw route
43
+ * strings: two relative `"settings"` routes under different parents resolve to
44
+ * distinct full paths and are valid, while a relative route and an absolute
45
+ * route resolving to the same URL genuinely collide and must be rejected.
46
+ * Called from `buildRouteTree` once full paths are known.
29
47
  *
30
48
  * Rationale: URL-based routing requires one-to-one mapping between URLs and states.
31
49
  * Multiple states at the same path creates ambiguity for browser navigation (back button,
32
50
  * direct URL access). Use different paths for different states, or single state with
33
51
  * conditional rendering.
34
52
  *
35
- * @param routes - Array of extracted RouteInfo objects
36
- * @throws {Error} If duplicate route paths are detected
53
+ * @param routes - Array of route entries with resolved full paths (e.g. `RouteNode`s)
54
+ * @throws {DuplicateRoutePathError} If duplicate resolved route paths are detected
37
55
  */
38
- export declare const detectDuplicateRoutes: (routes: RouteInfo[]) => void;
56
+ export declare const detectDuplicateRoutes: (routes: ResolvedRoutePath[]) => void;
39
57
  //# sourceMappingURL=validate-routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-routes.d.ts","sourceRoot":"","sources":["../src/validate-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,EAAE,SAAS,MAAM,KAAG,IAMxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,UAAU,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,KAAG,IAIvF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,SAAS,EAAE,KAAG,IAyB3D,CAAC"}
1
+ {"version":3,"file":"validate-routes.d.ts","sourceRoot":"","sources":["../src/validate-routes.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,WAAW,MAAM,EAAE,SAAS,MAAM,KAAG,IAIxE,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,MAAM,EAAE,UAAU,GAAG,CAAC,MAAM,CAAC,KAAG,IAI5E,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,iBAAiB,EAAE,KAAG,IAqBnE,CAAC"}
@@ -1,53 +1,62 @@
1
+ import { EmptyRoutePathError, InvalidStateIdError, DuplicateRoutePathError } from "./errors.js";
1
2
  /**
2
3
  * Validate route path format
3
4
  *
4
- * Ensures route paths start with "/" (absolute or relative).
5
- * Per Route Protocol spec, all routes must have explicit path structure.
5
+ * Ensures route paths are non-empty strings. Both absolute paths ("/foo")
6
+ * and relative paths ("foo", "child/nested") are accepted. Relative routes
7
+ * inherit their parent state's path prefix when the tree is built.
6
8
  *
7
- * @param routePath - Route path to validate
9
+ * @param routePath - Route path to validate (absolute or relative)
8
10
  * @param stateId - State identifier for error messages
9
- * @throws {Error} If route path doesn't start with /
11
+ * @throws {EmptyRoutePathError} If route path is empty
10
12
  */
11
13
  export const validateRouteFormat = (routePath, stateId) => {
12
- if (!routePath.startsWith("/")) {
13
- throw new Error(`Invalid route path "${routePath}" in state "${stateId}": routes must start with /`);
14
+ if (!routePath) {
15
+ throw new EmptyRoutePathError(stateId);
14
16
  }
15
17
  };
16
18
  /**
17
- * Validate state exists in state map
19
+ * Validate state exists in state ID set
18
20
  *
19
21
  * Ensures referenced state IDs exist in the machine graph.
20
22
  * Build-time validation prevents broken route references.
21
23
  *
22
24
  * @param stateId - State identifier to validate
23
- * @param stateMap - Map of all state IDs to StateNodes
24
- * @throws {Error} If state ID doesn't exist in map
25
+ * @param stateIds - Set of all known state IDs from the machine graph
26
+ * @throws {InvalidStateIdError} If state ID doesn't exist in set
25
27
  */
26
- export const validateStateExists = (stateId, stateMap) => {
27
- if (!stateMap.has(stateId)) {
28
- throw new Error(`Route references non-existent state ID: ${stateId}`);
28
+ export const validateStateExists = (stateId, stateIds) => {
29
+ if (!stateIds.has(stateId)) {
30
+ throw new InvalidStateIdError(stateId);
29
31
  }
30
32
  };
31
33
  /**
32
34
  * Detect duplicate route paths
33
35
  *
34
- * THROWS ERROR when multiple states share the same route path.
36
+ * THROWS ERROR when multiple states share the same RESOLVED full path.
37
+ *
38
+ * Detection operates on resolved full paths (after relative routes have been
39
+ * prefixed with their parent's path in `buildRouteTree`), not on raw route
40
+ * strings: two relative `"settings"` routes under different parents resolve to
41
+ * distinct full paths and are valid, while a relative route and an absolute
42
+ * route resolving to the same URL genuinely collide and must be rejected.
43
+ * Called from `buildRouteTree` once full paths are known.
35
44
  *
36
45
  * Rationale: URL-based routing requires one-to-one mapping between URLs and states.
37
46
  * Multiple states at the same path creates ambiguity for browser navigation (back button,
38
47
  * direct URL access). Use different paths for different states, or single state with
39
48
  * conditional rendering.
40
49
  *
41
- * @param routes - Array of extracted RouteInfo objects
42
- * @throws {Error} If duplicate route paths are detected
50
+ * @param routes - Array of route entries with resolved full paths (e.g. `RouteNode`s)
51
+ * @throws {DuplicateRoutePathError} If duplicate resolved route paths are detected
43
52
  */
44
53
  export const detectDuplicateRoutes = (routes) => {
45
54
  const routeMap = new Map();
46
- // Build map of route paths to state IDs
55
+ // Build map of resolved full paths to state IDs
47
56
  for (const route of routes) {
48
- const stateIds = routeMap.get(route.routePath) || [];
57
+ const stateIds = routeMap.get(route.fullPath) || [];
49
58
  stateIds.push(route.stateId);
50
- routeMap.set(route.routePath, stateIds);
59
+ routeMap.set(route.fullPath, stateIds);
51
60
  }
52
61
  // Check for duplicates and throw error
53
62
  const duplicates = [];
@@ -57,9 +66,7 @@ export const detectDuplicateRoutes = (routes) => {
57
66
  }
58
67
  }
59
68
  if (duplicates.length > 0) {
60
- throw new Error(`Duplicate route paths detected:\n${duplicates.join("\n")}\n\n` +
61
- `Each route path must map to exactly one state.\n` +
62
- `Use different paths (e.g., /dashboard vs /) or conditional rendering within a single state.`);
69
+ throw new DuplicateRoutePathError(duplicates);
63
70
  }
64
71
  };
65
72
  //# sourceMappingURL=validate-routes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"validate-routes.js","sourceRoot":"","sources":["../src/validate-routes.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAE,OAAe,EAAQ,EAAE;IAC/E,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CACd,uBAAuB,SAAS,eAAe,OAAO,6BAA6B,CACnF,CAAC;IACH,CAAC;AACF,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAe,EAAE,QAAgC,EAAQ,EAAE;IAC9F,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,2CAA2C,OAAO,EAAE,CAAC,CAAC;IACvE,CAAC;AACF,CAAC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,MAAmB,EAAQ,EAAE;IAClE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE7C,wCAAwC;IACxC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACrD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACzC,CAAC;IAED,uCAAuC;IACvC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QACxD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,UAAU,CAAC,IAAI,CAAC,KAAK,SAAS,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC;IACF,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACd,oCAAoC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM;YAC9D,kDAAkD;YAClD,6FAA6F,CAC9F,CAAC;IACH,CAAC;AACF,CAAC,CAAC"}
1
+ {"version":3,"file":"validate-routes.js","sourceRoot":"","sources":["../src/validate-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAehG;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,SAAiB,EAAE,OAAe,EAAQ,EAAE;IAC/E,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;AACF,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,OAAe,EAAE,QAAqB,EAAQ,EAAE;IACnF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;AACF,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,MAA2B,EAAQ,EAAE;IAC1E,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE7C,gDAAgD;IAChD,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpD,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC7B,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,uCAAuC;IACvC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC;QACxD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzB,UAAU,CAAC,IAAI,CAAC,KAAK,SAAS,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC7D,CAAC;IACF,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,uBAAuB,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC;AACF,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmachines/play-router",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0",
4
4
  "description": "Route tree extraction from XState v5 state machines. Part of @xmachines/play Universal Player Architecture.",
5
5
  "keywords": [
6
6
  "routing",
@@ -11,6 +11,11 @@
11
11
  ],
12
12
  "license": "MIT",
13
13
  "author": "XMachines",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+ssh://git@gitlab.com/xmachin-es/xmachines-js.git",
17
+ "directory": "packages/play-router"
18
+ },
14
19
  "files": [
15
20
  "dist",
16
21
  "README.md",
@@ -19,8 +24,14 @@
19
24
  "type": "module",
20
25
  "exports": {
21
26
  ".": {
27
+ "source": "./src/index.ts",
22
28
  "types": "./dist/index.d.ts",
23
29
  "import": "./dist/index.js"
30
+ },
31
+ "./errors": {
32
+ "source": "./src/errors.ts",
33
+ "types": "./dist/errors.d.ts",
34
+ "import": "./dist/errors.js"
24
35
  }
25
36
  },
26
37
  "publishConfig": {
@@ -28,25 +39,42 @@
28
39
  },
29
40
  "scripts": {
30
41
  "build": "tsc --build",
31
- "clean": "rm -rf dist *.tsbuildinfo node_modules/.vite node_modules/.vite-temp",
32
- "typecheck": "tsc --noEmit",
42
+ "clean": "rm -rf dist *.tsbuildinfo coverage .vitest-attachments test/browser/__screenshots__ node_modules/.svelte2tsx-* node_modules/.vite*",
43
+ "lint": "oxlint .",
44
+ "format": "oxfmt .",
33
45
  "test": "vitest",
34
46
  "prepublishOnly": "npm run build"
35
47
  },
36
48
  "dependencies": {
37
- "@xmachines/play": "1.0.0-beta.8",
38
- "@xmachines/play-actor": "1.0.0-beta.8",
39
- "@xmachines/play-signals": "1.0.0-beta.8",
40
- "urlpattern-polyfill": "^10.1.0"
49
+ "@statelyai/graph": "^2.1.0",
50
+ "@xmachines/play": "1.0.0",
51
+ "@xmachines/play-actor": "1.0.0",
52
+ "@xmachines/play-signals": "1.0.0",
53
+ "quick-lru": "^7.3.0"
41
54
  },
42
55
  "devDependencies": {
43
- "@types/node": "^25.5.0",
44
- "@xmachines/shared": "1.0.0-beta.8",
45
- "typescript": "^5.9.3",
46
- "vitest": "^4.1.0",
47
- "xstate": "^5.28.0"
56
+ "@testing-library/jest-dom": "^6.9.1",
57
+ "@types/node": "^26.1.1",
58
+ "@vitest/browser-playwright": "^4.1.10",
59
+ "@xmachines/shared": "1.0.0",
60
+ "oxfmt": "^0.58.0",
61
+ "oxlint": "^1.73.0",
62
+ "typescript": "^5.9.3 || ^6.0.3",
63
+ "urlpattern-polyfill": "^10.1.0",
64
+ "vitest": "^4.1.10",
65
+ "xstate": "^5.31.0"
48
66
  },
49
67
  "peerDependencies": {
50
- "xstate": "^5.28.0"
51
- }
68
+ "urlpattern-polyfill": "^10.1.0",
69
+ "xstate": "^5.31.0"
70
+ },
71
+ "peerDependenciesMeta": {
72
+ "urlpattern-polyfill": {
73
+ "optional": true
74
+ }
75
+ },
76
+ "engines": {
77
+ "node": ">=22.0.0"
78
+ },
79
+ "_devDependencies_note": "xstate appears in both peerDependencies and devDependencies intentionally. devDependencies provides workspace resolution for local builds and tests. peerDependencies declares the consumer version constraint. Both are pinned to ^5.30.0 to prevent drift. urlpattern-polyfill is an optional peer dependency — consumers on Node < 24 or older browsers install it themselves. devDependencies entry ensures workspace test resolution."
52
80
  }
@@ -1,56 +0,0 @@
1
- import type { AbstractActor, Routable } from "@xmachines/play-actor";
2
- import type { AnyActorLogic } from "xstate";
3
- import type { VanillaRouter } from "./create-router.js";
4
- import type { RouteMap } from "./create-route-map.js";
5
- export interface ConnectRouterOptions {
6
- actor: AbstractActor<AnyActorLogic> & Routable;
7
- router: VanillaRouter;
8
- routeMap: RouteMap;
9
- }
10
- /**
11
- * Connect vanilla router to actor (pure browser integration).
12
- *
13
- * This is the LOW-LEVEL API for maximum control. No JSX, no rendering,
14
- * just actor ↔ router synchronization.
15
- *
16
- * Use this when:
17
- * - You want manual control over rendering
18
- * - Using non-JSX framework (jQuery, Alpine, HTMX, etc.)
19
- * - Building custom integration
20
- *
21
- * For JSX frameworks, use framework adapters instead:
22
- * - @xmachines/play-react (React components)
23
- * - Future: @xmachines/play-preact, @xmachines/play-solid, @xmachines/play-vue
24
- *
25
- * Architecture:
26
- * - Subscribes to history changes → sends play.route to actor
27
- * - Watches actor.currentRoute signal → updates browser URL
28
- * - Prevents circular updates (history change triggers actor, actor triggers history)
29
- * - Returns cleanup function
30
- *
31
- * Usage:
32
- * ```typescript
33
- * import { createBrowserHistory, createRouter, connectRouter } from '@xmachines/play-router';
34
- *
35
- * const history = createBrowserHistory({ window });
36
- * const router = createRouter({ routeTree, history });
37
- *
38
- * // Connect router to actor
39
- * const disconnect = connectRouter({ actor, router, routeMap });
40
- *
41
- * // User handles rendering (vanilla JS)
42
- * const watcher = new Signal.subtle.Watcher(() => {
43
- * queueMicrotask(() => {
44
- * const view = actor.currentView.get();
45
- * document.getElementById('app').innerHTML = render(view);
46
- * });
47
- * });
48
- * watcher.watch(actor.currentView);
49
- *
50
- * // Later: cleanup
51
- * disconnect();
52
- * watcher.unwatch(actor.currentView);
53
- * ```
54
- */
55
- export declare function connectRouter(options: ConnectRouterOptions): () => void;
56
- //# sourceMappingURL=connect-router.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"connect-router.d.ts","sourceRoot":"","sources":["../src/connect-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC;IAC/C,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,QAAQ,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,MAAM,IAAI,CAmFvE"}
@@ -1,119 +0,0 @@
1
- import { Signal } from "@xmachines/play-signals";
2
- /**
3
- * Connect vanilla router to actor (pure browser integration).
4
- *
5
- * This is the LOW-LEVEL API for maximum control. No JSX, no rendering,
6
- * just actor ↔ router synchronization.
7
- *
8
- * Use this when:
9
- * - You want manual control over rendering
10
- * - Using non-JSX framework (jQuery, Alpine, HTMX, etc.)
11
- * - Building custom integration
12
- *
13
- * For JSX frameworks, use framework adapters instead:
14
- * - @xmachines/play-react (React components)
15
- * - Future: @xmachines/play-preact, @xmachines/play-solid, @xmachines/play-vue
16
- *
17
- * Architecture:
18
- * - Subscribes to history changes → sends play.route to actor
19
- * - Watches actor.currentRoute signal → updates browser URL
20
- * - Prevents circular updates (history change triggers actor, actor triggers history)
21
- * - Returns cleanup function
22
- *
23
- * Usage:
24
- * ```typescript
25
- * import { createBrowserHistory, createRouter, connectRouter } from '@xmachines/play-router';
26
- *
27
- * const history = createBrowserHistory({ window });
28
- * const router = createRouter({ routeTree, history });
29
- *
30
- * // Connect router to actor
31
- * const disconnect = connectRouter({ actor, router, routeMap });
32
- *
33
- * // User handles rendering (vanilla JS)
34
- * const watcher = new Signal.subtle.Watcher(() => {
35
- * queueMicrotask(() => {
36
- * const view = actor.currentView.get();
37
- * document.getElementById('app').innerHTML = render(view);
38
- * });
39
- * });
40
- * watcher.watch(actor.currentView);
41
- *
42
- * // Later: cleanup
43
- * disconnect();
44
- * watcher.unwatch(actor.currentView);
45
- * ```
46
- */
47
- export function connectRouter(options) {
48
- const { actor, router, routeMap } = options;
49
- const { history } = router;
50
- // Prevent circular updates
51
- let isProcessingHistoryChange = false;
52
- let isProcessingActorChange = false;
53
- let isProcessingPopstate = false;
54
- // Subscribe to history changes (browser navigation)
55
- const unsubscribeHistory = history.subscribe((location) => {
56
- if (isProcessingActorChange)
57
- return;
58
- const { to, params } = routeMap.resolve(location.pathname);
59
- if (to) {
60
- isProcessingHistoryChange = true;
61
- isProcessingPopstate = true;
62
- actor.send({ type: "play.route", to, params });
63
- // Check immediately if actor redirected (always-guard)
64
- // XState processes events synchronously, so snapshot is already updated
65
- const newActorRoute = actor.currentRoute.get();
66
- if (newActorRoute && newActorRoute !== location.pathname) {
67
- // Actor redirected - update URL
68
- isProcessingActorChange = true;
69
- history.replace(newActorRoute);
70
- isProcessingActorChange = false;
71
- }
72
- // Clear flags synchronously to prevent race conditions
73
- isProcessingHistoryChange = false;
74
- isProcessingPopstate = false;
75
- }
76
- });
77
- // Watch actor's currentRoute signal (actor-driven navigation)
78
- const watcher = new Signal.subtle.Watcher(() => {
79
- queueMicrotask(() => {
80
- if (isProcessingHistoryChange)
81
- return;
82
- // Skip if we're processing popstate - let the history subscriber handle URL updates
83
- if (isProcessingPopstate)
84
- return;
85
- for (const signal of watcher.getPending()) {
86
- signal.get();
87
- }
88
- watcher.watch(actor.currentRoute);
89
- // Sync URL from actor
90
- const route = actor.currentRoute.get();
91
- const currentPath = history.location.pathname;
92
- // Find path from route (reverse lookup)
93
- // Note: This assumes route tree has path metadata
94
- // For now, simple implementation - can enhance later
95
- if (route && route !== currentPath) {
96
- isProcessingActorChange = true;
97
- history.push(route);
98
- isProcessingActorChange = false;
99
- }
100
- });
101
- });
102
- watcher.watch(actor.currentRoute);
103
- // Sync initial URL to actor
104
- // ONLY send route event if URL doesn't match actor's initial state
105
- const initialPath = history.location.pathname;
106
- const initialActorRoute = actor.currentRoute.get();
107
- if (initialPath !== initialActorRoute) {
108
- const { to, params } = routeMap.resolve(initialPath);
109
- if (to) {
110
- actor.send({ type: "play.route", to, params });
111
- }
112
- }
113
- // Return cleanup
114
- return () => {
115
- unsubscribeHistory();
116
- watcher.unwatch(actor.currentRoute);
117
- };
118
- }
119
- //# sourceMappingURL=connect-router.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"connect-router.js","sourceRoot":"","sources":["../src/connect-router.ts"],"names":[],"mappings":"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"}