@xmachines/play-router 1.0.0-beta.1

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 (141) hide show
  1. package/.oxfmtrc.json +3 -0
  2. package/.oxlintrc.json +3 -0
  3. package/README.md +436 -0
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/build-tree.ts.html +316 -0
  7. package/coverage/connect-router.ts.html +505 -0
  8. package/coverage/coverage-summary.json +15 -0
  9. package/coverage/crawl-machine.ts.html +385 -0
  10. package/coverage/create-browser-history.ts.html +556 -0
  11. package/coverage/create-route-map.ts.html +400 -0
  12. package/coverage/create-router.ts.html +328 -0
  13. package/coverage/extract-route.ts.html +322 -0
  14. package/coverage/extract-routes.ts.html +286 -0
  15. package/coverage/favicon.png +0 -0
  16. package/coverage/index.html +296 -0
  17. package/coverage/index.ts.html +610 -0
  18. package/coverage/prettify.css +1 -0
  19. package/coverage/prettify.js +2 -0
  20. package/coverage/query.ts.html +307 -0
  21. package/coverage/router-bridge-base.ts.html +919 -0
  22. package/coverage/sort-arrow-sprite.png +0 -0
  23. package/coverage/sorter.js +210 -0
  24. package/coverage/types.ts.html +787 -0
  25. package/coverage/validate-routes.ts.html +319 -0
  26. package/dist/build-tree.d.ts +13 -0
  27. package/dist/build-tree.d.ts.map +1 -0
  28. package/dist/build-tree.js +67 -0
  29. package/dist/build-tree.js.map +1 -0
  30. package/dist/connect-router.d.ts +56 -0
  31. package/dist/connect-router.d.ts.map +1 -0
  32. package/dist/connect-router.js +119 -0
  33. package/dist/connect-router.js.map +1 -0
  34. package/dist/crawl-machine.d.ts +74 -0
  35. package/dist/crawl-machine.d.ts.map +1 -0
  36. package/dist/crawl-machine.js +95 -0
  37. package/dist/crawl-machine.js.map +1 -0
  38. package/dist/create-browser-history.d.ts +68 -0
  39. package/dist/create-browser-history.d.ts.map +1 -0
  40. package/dist/create-browser-history.js +94 -0
  41. package/dist/create-browser-history.js.map +1 -0
  42. package/dist/create-route-map.d.ts +46 -0
  43. package/dist/create-route-map.d.ts.map +1 -0
  44. package/dist/create-route-map.js +73 -0
  45. package/dist/create-route-map.js.map +1 -0
  46. package/dist/create-router.d.ts +73 -0
  47. package/dist/create-router.d.ts.map +1 -0
  48. package/dist/create-router.js +63 -0
  49. package/dist/create-router.js.map +1 -0
  50. package/dist/extract-route.d.ts +25 -0
  51. package/dist/extract-route.d.ts.map +1 -0
  52. package/dist/extract-route.js +63 -0
  53. package/dist/extract-route.js.map +1 -0
  54. package/dist/extract-routes.d.ts +41 -0
  55. package/dist/extract-routes.d.ts.map +1 -0
  56. package/dist/extract-routes.js +61 -0
  57. package/dist/extract-routes.js.map +1 -0
  58. package/dist/index.d.ts +56 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +141 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/query.d.ts +52 -0
  63. package/dist/query.d.ts.map +1 -0
  64. package/dist/query.js +69 -0
  65. package/dist/query.js.map +1 -0
  66. package/dist/router-bridge-base.d.ts +150 -0
  67. package/dist/router-bridge-base.d.ts.map +1 -0
  68. package/dist/router-bridge-base.js +240 -0
  69. package/dist/router-bridge-base.js.map +1 -0
  70. package/dist/types.d.ts +228 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/types.js +2 -0
  73. package/dist/types.js.map +1 -0
  74. package/dist/validate-routes.d.ts +39 -0
  75. package/dist/validate-routes.d.ts.map +1 -0
  76. package/dist/validate-routes.js +65 -0
  77. package/dist/validate-routes.js.map +1 -0
  78. package/examples/demo/README.md +127 -0
  79. package/examples/demo/index.html +41 -0
  80. package/examples/demo/package.json +27 -0
  81. package/examples/demo/src/main.ts +28 -0
  82. package/examples/demo/src/router.ts +37 -0
  83. package/examples/demo/src/shell.ts +316 -0
  84. package/examples/demo/test/browser/auth-flow.browser.test.ts +60 -0
  85. package/examples/demo/test/browser/startup.browser.test.ts +37 -0
  86. package/examples/demo/test/library-pattern.test.ts +51 -0
  87. package/examples/demo/tsconfig.json +17 -0
  88. package/examples/demo/vite.config.ts +7 -0
  89. package/examples/demo/vitest.browser.config.ts +20 -0
  90. package/examples/demo/vitest.config.ts +9 -0
  91. package/examples/shared/dist/auth-machine.d.ts +20 -0
  92. package/examples/shared/dist/auth-machine.d.ts.map +1 -0
  93. package/examples/shared/dist/auth-machine.js +212 -0
  94. package/examples/shared/dist/auth-machine.js.map +1 -0
  95. package/examples/shared/dist/catalog.d.ts +85 -0
  96. package/examples/shared/dist/catalog.d.ts.map +1 -0
  97. package/examples/shared/dist/catalog.js +86 -0
  98. package/examples/shared/dist/catalog.js.map +1 -0
  99. package/examples/shared/dist/index.d.ts +4 -0
  100. package/examples/shared/dist/index.d.ts.map +1 -0
  101. package/examples/shared/dist/index.js +3 -0
  102. package/examples/shared/dist/index.js.map +1 -0
  103. package/examples/shared/package.json +37 -0
  104. package/examples/shared/src/auth-machine.ts +234 -0
  105. package/examples/shared/src/catalog.ts +95 -0
  106. package/examples/shared/src/index.css +3 -0
  107. package/examples/shared/src/index.ts +3 -0
  108. package/examples/shared/src/styles/layout.css +413 -0
  109. package/examples/shared/src/styles/reset.css +42 -0
  110. package/examples/shared/src/styles/tokens.css +183 -0
  111. package/examples/shared/tsconfig.json +14 -0
  112. package/examples/shared/tsconfig.tsbuildinfo +1 -0
  113. package/package.json +44 -0
  114. package/src/build-tree.ts +77 -0
  115. package/src/connect-router.ts +142 -0
  116. package/src/crawl-machine.ts +100 -0
  117. package/src/create-browser-history.ts +157 -0
  118. package/src/create-route-map.ts +105 -0
  119. package/src/create-router.ts +87 -0
  120. package/src/extract-route.ts +79 -0
  121. package/src/extract-routes.ts +67 -0
  122. package/src/index.ts +175 -0
  123. package/src/query.ts +74 -0
  124. package/src/router-bridge-base.ts +279 -0
  125. package/src/types.ts +234 -0
  126. package/src/validate-routes.ts +76 -0
  127. package/test/connect-route-map.test.ts +320 -0
  128. package/test/crawl-extract.test.js +473 -0
  129. package/test/create-browser-history.test.ts +123 -0
  130. package/test/create-router.test.ts +23 -0
  131. package/test/extract-routes.test.ts +80 -0
  132. package/test/find-route-by-path-patterns.test.ts +69 -0
  133. package/test/integration.test.js +438 -0
  134. package/test/query.test.ts +56 -0
  135. package/test/router-bridge-base-edge.test.ts +165 -0
  136. package/test/router-bridge-base.test.ts +119 -0
  137. package/test/tree-query.test.js +692 -0
  138. package/test/validation.test.js +158 -0
  139. package/tsconfig.json +14 -0
  140. package/tsconfig.tsbuildinfo +1 -0
  141. package/vitest.config.ts +35 -0
@@ -0,0 +1,319 @@
1
+
2
+ <!doctype html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <title>Code coverage report for validate-routes.ts</title>
7
+ <meta charset="utf-8" />
8
+ <link rel="stylesheet" href="prettify.css" />
9
+ <link rel="stylesheet" href="base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
11
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
12
+ <style type='text/css'>
13
+ .coverage-summary .sorter {
14
+ background-image: url(sort-arrow-sprite.png);
15
+ }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <div class='wrapper'>
21
+ <div class='pad1'>
22
+ <h1><a href="index.html">All files</a> validate-routes.ts</h1>
23
+ <div class='clearfix'>
24
+
25
+ <div class='fl pad1y space-right2'>
26
+ <span class="strong">100% </span>
27
+ <span class="quiet">Statements</span>
28
+ <span class='fraction'>18/18</span>
29
+ </div>
30
+
31
+
32
+ <div class='fl pad1y space-right2'>
33
+ <span class="strong">100% </span>
34
+ <span class="quiet">Branches</span>
35
+ <span class='fraction'>10/10</span>
36
+ </div>
37
+
38
+
39
+ <div class='fl pad1y space-right2'>
40
+ <span class="strong">100% </span>
41
+ <span class="quiet">Functions</span>
42
+ <span class='fraction'>3/3</span>
43
+ </div>
44
+
45
+
46
+ <div class='fl pad1y space-right2'>
47
+ <span class="strong">100% </span>
48
+ <span class="quiet">Lines</span>
49
+ <span class='fraction'>18/18</span>
50
+ </div>
51
+
52
+
53
+ </div>
54
+ <p class="quiet">
55
+ Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
+ </p>
57
+ <template id="filterTemplate">
58
+ <div class="quiet">
59
+ Filter:
60
+ <input type="search" id="fileSearch">
61
+ </div>
62
+ </template>
63
+ </div>
64
+ <div class='status-line high'></div>
65
+ <pre><table class="coverage">
66
+ <tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
67
+ <a name='L2'></a><a href='#L2'>2</a>
68
+ <a name='L3'></a><a href='#L3'>3</a>
69
+ <a name='L4'></a><a href='#L4'>4</a>
70
+ <a name='L5'></a><a href='#L5'>5</a>
71
+ <a name='L6'></a><a href='#L6'>6</a>
72
+ <a name='L7'></a><a href='#L7'>7</a>
73
+ <a name='L8'></a><a href='#L8'>8</a>
74
+ <a name='L9'></a><a href='#L9'>9</a>
75
+ <a name='L10'></a><a href='#L10'>10</a>
76
+ <a name='L11'></a><a href='#L11'>11</a>
77
+ <a name='L12'></a><a href='#L12'>12</a>
78
+ <a name='L13'></a><a href='#L13'>13</a>
79
+ <a name='L14'></a><a href='#L14'>14</a>
80
+ <a name='L15'></a><a href='#L15'>15</a>
81
+ <a name='L16'></a><a href='#L16'>16</a>
82
+ <a name='L17'></a><a href='#L17'>17</a>
83
+ <a name='L18'></a><a href='#L18'>18</a>
84
+ <a name='L19'></a><a href='#L19'>19</a>
85
+ <a name='L20'></a><a href='#L20'>20</a>
86
+ <a name='L21'></a><a href='#L21'>21</a>
87
+ <a name='L22'></a><a href='#L22'>22</a>
88
+ <a name='L23'></a><a href='#L23'>23</a>
89
+ <a name='L24'></a><a href='#L24'>24</a>
90
+ <a name='L25'></a><a href='#L25'>25</a>
91
+ <a name='L26'></a><a href='#L26'>26</a>
92
+ <a name='L27'></a><a href='#L27'>27</a>
93
+ <a name='L28'></a><a href='#L28'>28</a>
94
+ <a name='L29'></a><a href='#L29'>29</a>
95
+ <a name='L30'></a><a href='#L30'>30</a>
96
+ <a name='L31'></a><a href='#L31'>31</a>
97
+ <a name='L32'></a><a href='#L32'>32</a>
98
+ <a name='L33'></a><a href='#L33'>33</a>
99
+ <a name='L34'></a><a href='#L34'>34</a>
100
+ <a name='L35'></a><a href='#L35'>35</a>
101
+ <a name='L36'></a><a href='#L36'>36</a>
102
+ <a name='L37'></a><a href='#L37'>37</a>
103
+ <a name='L38'></a><a href='#L38'>38</a>
104
+ <a name='L39'></a><a href='#L39'>39</a>
105
+ <a name='L40'></a><a href='#L40'>40</a>
106
+ <a name='L41'></a><a href='#L41'>41</a>
107
+ <a name='L42'></a><a href='#L42'>42</a>
108
+ <a name='L43'></a><a href='#L43'>43</a>
109
+ <a name='L44'></a><a href='#L44'>44</a>
110
+ <a name='L45'></a><a href='#L45'>45</a>
111
+ <a name='L46'></a><a href='#L46'>46</a>
112
+ <a name='L47'></a><a href='#L47'>47</a>
113
+ <a name='L48'></a><a href='#L48'>48</a>
114
+ <a name='L49'></a><a href='#L49'>49</a>
115
+ <a name='L50'></a><a href='#L50'>50</a>
116
+ <a name='L51'></a><a href='#L51'>51</a>
117
+ <a name='L52'></a><a href='#L52'>52</a>
118
+ <a name='L53'></a><a href='#L53'>53</a>
119
+ <a name='L54'></a><a href='#L54'>54</a>
120
+ <a name='L55'></a><a href='#L55'>55</a>
121
+ <a name='L56'></a><a href='#L56'>56</a>
122
+ <a name='L57'></a><a href='#L57'>57</a>
123
+ <a name='L58'></a><a href='#L58'>58</a>
124
+ <a name='L59'></a><a href='#L59'>59</a>
125
+ <a name='L60'></a><a href='#L60'>60</a>
126
+ <a name='L61'></a><a href='#L61'>61</a>
127
+ <a name='L62'></a><a href='#L62'>62</a>
128
+ <a name='L63'></a><a href='#L63'>63</a>
129
+ <a name='L64'></a><a href='#L64'>64</a>
130
+ <a name='L65'></a><a href='#L65'>65</a>
131
+ <a name='L66'></a><a href='#L66'>66</a>
132
+ <a name='L67'></a><a href='#L67'>67</a>
133
+ <a name='L68'></a><a href='#L68'>68</a>
134
+ <a name='L69'></a><a href='#L69'>69</a>
135
+ <a name='L70'></a><a href='#L70'>70</a>
136
+ <a name='L71'></a><a href='#L71'>71</a>
137
+ <a name='L72'></a><a href='#L72'>72</a>
138
+ <a name='L73'></a><a href='#L73'>73</a>
139
+ <a name='L74'></a><a href='#L74'>74</a>
140
+ <a name='L75'></a><a href='#L75'>75</a>
141
+ <a name='L76'></a><a href='#L76'>76</a>
142
+ <a name='L77'></a><a href='#L77'>77</a>
143
+ <a name='L78'></a><a href='#L78'>78</a>
144
+ <a name='L79'></a><a href='#L79'>79</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
145
+ <span class="cline-any cline-neutral">&nbsp;</span>
146
+ <span class="cline-any cline-neutral">&nbsp;</span>
147
+ <span class="cline-any cline-neutral">&nbsp;</span>
148
+ <span class="cline-any cline-neutral">&nbsp;</span>
149
+ <span class="cline-any cline-neutral">&nbsp;</span>
150
+ <span class="cline-any cline-neutral">&nbsp;</span>
151
+ <span class="cline-any cline-neutral">&nbsp;</span>
152
+ <span class="cline-any cline-neutral">&nbsp;</span>
153
+ <span class="cline-any cline-neutral">&nbsp;</span>
154
+ <span class="cline-any cline-neutral">&nbsp;</span>
155
+ <span class="cline-any cline-neutral">&nbsp;</span>
156
+ <span class="cline-any cline-neutral">&nbsp;</span>
157
+ <span class="cline-any cline-yes">5x</span>
158
+ <span class="cline-any cline-yes">78x</span>
159
+ <span class="cline-any cline-yes">7x</span>
160
+ <span class="cline-any cline-neutral">&nbsp;</span>
161
+ <span class="cline-any cline-neutral">&nbsp;</span>
162
+ <span class="cline-any cline-neutral">&nbsp;</span>
163
+ <span class="cline-any cline-neutral">&nbsp;</span>
164
+ <span class="cline-any cline-neutral">&nbsp;</span>
165
+ <span class="cline-any cline-neutral">&nbsp;</span>
166
+ <span class="cline-any cline-neutral">&nbsp;</span>
167
+ <span class="cline-any cline-neutral">&nbsp;</span>
168
+ <span class="cline-any cline-neutral">&nbsp;</span>
169
+ <span class="cline-any cline-neutral">&nbsp;</span>
170
+ <span class="cline-any cline-neutral">&nbsp;</span>
171
+ <span class="cline-any cline-neutral">&nbsp;</span>
172
+ <span class="cline-any cline-neutral">&nbsp;</span>
173
+ <span class="cline-any cline-neutral">&nbsp;</span>
174
+ <span class="cline-any cline-neutral">&nbsp;</span>
175
+ <span class="cline-any cline-yes">5x</span>
176
+ <span class="cline-any cline-yes">73x</span>
177
+ <span class="cline-any cline-yes">2x</span>
178
+ <span class="cline-any cline-neutral">&nbsp;</span>
179
+ <span class="cline-any cline-neutral">&nbsp;</span>
180
+ <span class="cline-any cline-neutral">&nbsp;</span>
181
+ <span class="cline-any cline-neutral">&nbsp;</span>
182
+ <span class="cline-any cline-neutral">&nbsp;</span>
183
+ <span class="cline-any cline-neutral">&nbsp;</span>
184
+ <span class="cline-any cline-neutral">&nbsp;</span>
185
+ <span class="cline-any cline-neutral">&nbsp;</span>
186
+ <span class="cline-any cline-neutral">&nbsp;</span>
187
+ <span class="cline-any cline-neutral">&nbsp;</span>
188
+ <span class="cline-any cline-neutral">&nbsp;</span>
189
+ <span class="cline-any cline-neutral">&nbsp;</span>
190
+ <span class="cline-any cline-neutral">&nbsp;</span>
191
+ <span class="cline-any cline-neutral">&nbsp;</span>
192
+ <span class="cline-any cline-neutral">&nbsp;</span>
193
+ <span class="cline-any cline-neutral">&nbsp;</span>
194
+ <span class="cline-any cline-yes">5x</span>
195
+ <span class="cline-any cline-yes">27x</span>
196
+ <span class="cline-any cline-neutral">&nbsp;</span>
197
+ <span class="cline-any cline-neutral">&nbsp;</span>
198
+ <span class="cline-any cline-yes">27x</span>
199
+ <span class="cline-any cline-yes">89x</span>
200
+ <span class="cline-any cline-yes">89x</span>
201
+ <span class="cline-any cline-yes">89x</span>
202
+ <span class="cline-any cline-neutral">&nbsp;</span>
203
+ <span class="cline-any cline-neutral">&nbsp;</span>
204
+ <span class="cline-any cline-neutral">&nbsp;</span>
205
+ <span class="cline-any cline-yes">27x</span>
206
+ <span class="cline-any cline-yes">27x</span>
207
+ <span class="cline-any cline-yes">72x</span>
208
+ <span class="cline-any cline-yes">15x</span>
209
+ <span class="cline-any cline-neutral">&nbsp;</span>
210
+ <span class="cline-any cline-neutral">&nbsp;</span>
211
+ <span class="cline-any cline-neutral">&nbsp;</span>
212
+ <span class="cline-any cline-neutral">&nbsp;</span>
213
+ <span class="cline-any cline-neutral">&nbsp;</span>
214
+ <span class="cline-any cline-yes">27x</span>
215
+ <span class="cline-any cline-yes">12x</span>
216
+ <span class="cline-any cline-neutral">&nbsp;</span>
217
+ <span class="cline-any cline-neutral">&nbsp;</span>
218
+ <span class="cline-any cline-neutral">&nbsp;</span>
219
+ <span class="cline-any cline-neutral">&nbsp;</span>
220
+ <span class="cline-any cline-neutral">&nbsp;</span>
221
+ <span class="cline-any cline-neutral">&nbsp;</span>
222
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type { StateNode } from "xstate";
223
+ import type { RouteInfo } from "./types.js";
224
+ &nbsp;
225
+ /**
226
+ * Validate route path format
227
+ *
228
+ * Ensures route paths start with "/" (absolute or relative).
229
+ * Per Route Protocol spec, all routes must have explicit path structure.
230
+ *
231
+ * @param routePath - Route path to validate
232
+ * @param stateId - State identifier for error messages
233
+ * @throws {Error} If route path doesn't start with /
234
+ */
235
+ export const validateRouteFormat = (routePath: string, stateId: string): void =&gt; {
236
+ if (!routePath.startsWith("/")) {
237
+ throw new Error(
238
+ `Invalid route path "${routePath}" in state "${stateId}": routes must start with /`,
239
+ );
240
+ }
241
+ };
242
+ &nbsp;
243
+ /**
244
+ * Validate state exists in state map
245
+ *
246
+ * Ensures referenced state IDs exist in the machine graph.
247
+ * Build-time validation prevents broken route references.
248
+ *
249
+ * @param stateId - State identifier to validate
250
+ * @param stateMap - Map of all state IDs to StateNodes
251
+ * @throws {Error} If state ID doesn't exist in map
252
+ */
253
+ export const validateStateExists = (stateId: string, stateMap: Map&lt;string, StateNode&gt;): void =&gt; {
254
+ if (!stateMap.has(stateId)) {
255
+ throw new Error(`Route references non-existent state ID: ${stateId}`);
256
+ }
257
+ };
258
+ &nbsp;
259
+ /**
260
+ * Detect duplicate route paths
261
+ *
262
+ * THROWS ERROR when multiple states share the same route path.
263
+ *
264
+ * Rationale: URL-based routing requires one-to-one mapping between URLs and states.
265
+ * Multiple states at the same path creates ambiguity for browser navigation (back button,
266
+ * direct URL access). Use different paths for different states, or single state with
267
+ * conditional rendering.
268
+ *
269
+ * @param routes - Array of extracted RouteInfo objects
270
+ * @throws {Error} If duplicate route paths are detected
271
+ */
272
+ export const detectDuplicateRoutes = (routes: RouteInfo[]): void =&gt; {
273
+ const routeMap = new Map&lt;string, string[]&gt;();
274
+ &nbsp;
275
+ // Build map of route paths to state IDs
276
+ for (const route of routes) {
277
+ const stateIds = routeMap.get(route.routePath) || [];
278
+ stateIds.push(route.stateId);
279
+ routeMap.set(route.routePath, stateIds);
280
+ }
281
+ &nbsp;
282
+ // Check for duplicates and throw error
283
+ const duplicates: string[] = [];
284
+ for (const [routePath, stateIds] of routeMap.entries()) {
285
+ if (stateIds.length &gt; 1) {
286
+ duplicates.push(
287
+ ` ${routePath}: [${stateIds.join(", ")}]`
288
+ );
289
+ }
290
+ }
291
+ &nbsp;
292
+ if (duplicates.length &gt; 0) {
293
+ throw new Error(
294
+ `Duplicate route paths detected:\n${duplicates.join("\n")}\n\n` +
295
+ `Each route path must map to exactly one state.\n` +
296
+ `Use different paths (e.g., /dashboard vs /) or conditional rendering within a single state.`
297
+ );
298
+ }
299
+ };
300
+ &nbsp;</pre></td></tr></table></pre>
301
+
302
+ <div class='push'></div><!-- for sticky footer -->
303
+ </div><!-- /wrapper -->
304
+ <div class='footer quiet pad2 space-top1 center small'>
305
+ Code coverage generated by
306
+ <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
307
+ at 2026-03-08T21:05:56.278Z
308
+ </div>
309
+ <script src="prettify.js"></script>
310
+ <script>
311
+ window.onload = function () {
312
+ prettyPrint();
313
+ };
314
+ </script>
315
+ <script src="sorter.js"></script>
316
+ <script src="block-navigation.js"></script>
317
+ </body>
318
+ </html>
319
+
@@ -0,0 +1,13 @@
1
+ import type { RouteInfo, RouteTree } from "./types.js";
2
+ /**
3
+ * Build hierarchical route tree from flat route list
4
+ *
5
+ * Constructs nested tree respecting parent-child state relationships.
6
+ * Absolute routes become top-level, relative routes nest under parents.
7
+ * Creates bidirectional maps for state ID ↔ path lookup.
8
+ *
9
+ * @param routes - Flat list of RouteInfo from extraction
10
+ * @returns RouteTree with root, byStateId map, and byPath map
11
+ */
12
+ export declare const buildRouteTree: (routes: RouteInfo[]) => RouteTree;
13
+ //# sourceMappingURL=build-tree.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Build hierarchical route tree from flat route list
3
+ *
4
+ * Constructs nested tree respecting parent-child state relationships.
5
+ * Absolute routes become top-level, relative routes nest under parents.
6
+ * Creates bidirectional maps for state ID ↔ path lookup.
7
+ *
8
+ * @param routes - Flat list of RouteInfo from extraction
9
+ * @returns RouteTree with root, byStateId map, and byPath map
10
+ */
11
+ export const buildRouteTree = (routes) => {
12
+ // 1. Create root node
13
+ const root = {
14
+ id: "__root__",
15
+ path: "/",
16
+ fullPath: "/",
17
+ stateId: "__root__",
18
+ routable: false, // Root is not a routable state
19
+ children: [],
20
+ parent: null,
21
+ metadata: {},
22
+ };
23
+ // 2. Initialize maps
24
+ const byStateId = new Map([[root.stateId, root]]);
25
+ const byPath = new Map([[root.fullPath, root]]);
26
+ // 3. Sort routes by depth (shallowest first for proper parent linking)
27
+ const sorted = routes.slice().sort((a, b) => a.statePath.length - b.statePath.length);
28
+ // 4. Build tree
29
+ for (const route of sorted) {
30
+ // Find parent by walking up state path
31
+ 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(".");
35
+ if (byStateId.has(parentStateId)) {
36
+ parentNode = byStateId.get(parentStateId);
37
+ break;
38
+ }
39
+ }
40
+ // Build full path
41
+ const fullPath = route.isAbsolute
42
+ ? route.routePath
43
+ : `${parentNode.fullPath}/${route.routePath}`.replace(/\/+/g, "/");
44
+ // Create node
45
+ const node = {
46
+ id: route.stateId,
47
+ path: route.routePath,
48
+ fullPath,
49
+ stateId: route.stateId,
50
+ routable: route.routable,
51
+ children: [],
52
+ parent: parentNode,
53
+ metadata: route.metadata,
54
+ };
55
+ // Add pattern if it exists
56
+ if (route.pattern) {
57
+ node.pattern = route.pattern;
58
+ }
59
+ // Link to parent
60
+ parentNode.children.push(node);
61
+ // Add to maps for bidirectional lookup
62
+ byStateId.set(node.stateId, node);
63
+ byPath.set(node.fullPath, node);
64
+ }
65
+ return { root, byStateId, byPath };
66
+ };
67
+ //# sourceMappingURL=build-tree.js.map
@@ -0,0 +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"}
@@ -0,0 +1,56 @@
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
@@ -0,0 +1 @@
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;AAGtD,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"}
@@ -0,0 +1,119 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect-router.js","sourceRoot":"","sources":["../src/connect-router.ts"],"names":[],"mappings":"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"}