@xmachines/play-tanstack-react-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 (203) hide show
  1. package/.oxfmtrc.json +3 -0
  2. package/.oxlintrc.json +3 -0
  3. package/README.md +177 -0
  4. package/dist/extract-params.d.ts +45 -0
  5. package/dist/extract-params.d.ts.map +1 -0
  6. package/dist/extract-params.js +70 -0
  7. package/dist/extract-params.js.map +1 -0
  8. package/dist/index.d.ts +18 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +17 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/play-router-provider.d.ts +33 -0
  13. package/dist/play-router-provider.d.ts.map +1 -0
  14. package/dist/play-router-provider.js +31 -0
  15. package/dist/play-router-provider.js.map +1 -0
  16. package/dist/route-map.d.ts +101 -0
  17. package/dist/route-map.d.ts.map +1 -0
  18. package/dist/route-map.js +139 -0
  19. package/dist/route-map.js.map +1 -0
  20. package/dist/tanstack-router-bridge.d.ts +115 -0
  21. package/dist/tanstack-router-bridge.d.ts.map +1 -0
  22. package/dist/tanstack-router-bridge.js +112 -0
  23. package/dist/tanstack-router-bridge.js.map +1 -0
  24. package/dist/types.d.ts +26 -0
  25. package/dist/types.d.ts.map +1 -0
  26. package/dist/types.js +7 -0
  27. package/dist/types.js.map +1 -0
  28. package/dist/utils.d.ts +9 -0
  29. package/dist/utils.d.ts.map +1 -0
  30. package/dist/utils.js +10 -0
  31. package/dist/utils.js.map +1 -0
  32. package/examples/demo/README.md +100 -0
  33. package/examples/demo/docs/ARCHITECTURE.md +643 -0
  34. package/examples/demo/docs/INVARIANTS.md +461 -0
  35. package/examples/demo/docs/SWAP-REACT.md +635 -0
  36. package/examples/demo/index.html +16 -0
  37. package/examples/demo/package.json +39 -0
  38. package/examples/demo/src/App.tsx +148 -0
  39. package/examples/demo/src/components/About.tsx +49 -0
  40. package/examples/demo/src/components/Contact.tsx +43 -0
  41. package/examples/demo/src/components/Dashboard.tsx +46 -0
  42. package/examples/demo/src/components/DebugPanel.tsx +68 -0
  43. package/examples/demo/src/components/HeaderNav.tsx +103 -0
  44. package/examples/demo/src/components/Home.tsx +41 -0
  45. package/examples/demo/src/components/Login.tsx +82 -0
  46. package/examples/demo/src/components/Navigation.tsx +262 -0
  47. package/examples/demo/src/components/Profile.tsx +46 -0
  48. package/examples/demo/src/components/Register.tsx +109 -0
  49. package/examples/demo/src/components/Settings.tsx +92 -0
  50. package/examples/demo/src/components/index.ts +16 -0
  51. package/examples/demo/src/main.tsx +20 -0
  52. package/examples/demo/test/actor-authority.test.ts +50 -0
  53. package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/Browser-back-button-navigates-through-unique-history--no-duplicates--1.png +0 -0
  54. package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/GAP-12--navigation-via-goto---events-creates-single-history-entries-1.png +0 -0
  55. package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/GAP-12--navigation-via-goto---events-creates-single-history-entries-2.png +0 -0
  56. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--After-authentication-flow-1.png +0 -0
  57. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-1.png +0 -0
  58. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-2.png +0 -0
  59. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--URL-stays-in-sync-with-actor-state-1.png +0 -0
  60. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--URL-stays-in-sync-with-actor-state-2.png +0 -0
  61. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-1.png +0 -0
  62. package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-2.png +0 -0
  63. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--about-loads-about-page-1.png +0 -0
  64. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--contact-loads-contact-page-1.png +0 -0
  65. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--home-loads-home-page-1.png +0 -0
  66. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-authenticated-loads-dashboard-1.png +0 -0
  67. package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-unauthenticated-redirects-to-login-1.png +0 -0
  68. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-1.png +0 -0
  69. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-2.png +0 -0
  70. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/EXACT-USER-SCENARIO--home---about---home---contact---home--then-back-3x-should-land-on-about-1.png +0 -0
  71. package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/EXACT-USER-SCENARIO--home---about---home---contact---home--then-back-3x-should-land-on-about-2.png +0 -0
  72. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-1.png +0 -0
  73. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-2.png +0 -0
  74. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-1.png +0 -0
  75. package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-2.png +0 -0
  76. package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--1.png +0 -0
  77. package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--2.png +0 -0
  78. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-1.png +0 -0
  79. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-2.png +0 -0
  80. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-1.png +0 -0
  81. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-2.png +0 -0
  82. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-1.png +0 -0
  83. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-2.png +0 -0
  84. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---unauthenticated-user-stays-on-public-routes-1.png +0 -0
  85. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---unauthenticated-user-stays-on-public-routes-2.png +0 -0
  86. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-1.png +0 -0
  87. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-2.png +0 -0
  88. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-1.png +0 -0
  89. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-2.png +0 -0
  90. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-1.png +0 -0
  91. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-2.png +0 -0
  92. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Rapid-back-forward-navigation-doesn-t-cause-duplicate-entries-1.png +0 -0
  93. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Rapid-back-forward-navigation-doesn-t-cause-duplicate-entries-2.png +0 -0
  94. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-1.png +0 -0
  95. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-2.png +0 -0
  96. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-1.png +0 -0
  97. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-2.png +0 -0
  98. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-1.png +0 -0
  99. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-2.png +0 -0
  100. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-1.png +0 -0
  101. package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-2.png +0 -0
  102. package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-1.png +0 -0
  103. package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-2.png +0 -0
  104. package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-1.png +0 -0
  105. package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-2.png +0 -0
  106. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
  107. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-2.png +0 -0
  108. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-navigates-from-dashboard-to-settings--protected-route--1.png +0 -0
  109. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-navigates-from-dashboard-to-settings--protected-route--2.png +0 -0
  110. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
  111. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-from-dashboard-to-settings--protected-route--1.png +0 -0
  112. package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-navigates-from-dashboard-to-settings--protected-route--1.png +0 -0
  113. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--account--section-when-navigating-to--settings-account-1.png +0 -0
  114. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--account--section-when-navigating-to--settings-account-2.png +0 -0
  115. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--general--section-when-navigating-to--settings--no-parameter--1.png +0 -0
  116. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--general--section-when-navigating-to--settings--no-parameter--2.png +0 -0
  117. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--profile--section-when-navigating-to--settings-profile-1.png +0 -0
  118. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--profile--section-when-navigating-to--settings-profile-2.png +0 -0
  119. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-update-section-display-when-clicking-section-navigation-buttons-1.png +0 -0
  120. package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-update-section-display-when-clicking-section-navigation-buttons-2.png +0 -0
  121. package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-query-parameters-works-correctly-1.png +0 -0
  122. package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-section-parameter-works-correctly-1.png +0 -0
  123. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04--State-Driven-Reset---Browser-back-sends-event-to-actor-1.png +0 -0
  124. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04--State-Driven-Reset---Browser-back-sends-event-to-actor-2.png +0 -0
  125. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-1.png +0 -0
  126. package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-2.png +0 -0
  127. package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-1.png +0 -0
  128. package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-2.png +0 -0
  129. package/examples/demo/test/browser/__screenshots__/test-multiple-back.browser.test.tsx/Multiple-back--Navigate-forward-3x-then-back-3x-1.png +0 -0
  130. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-1--Opening-with--someinvalidstate-stays-at-current-state-1.png +0 -0
  131. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2--Opening-with--about-renders-About-component--not-Login-1.png +0 -0
  132. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2b--Opening-with--home-renders-Home-component--not-Login-1.png +0 -0
  133. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2c--Opening-with--contact-renders-Contact-component--not-Login-1.png +0 -0
  134. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-3--Back-forward-navigation---rendering-syncs-with-URL-1.png +0 -0
  135. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-3--Back-forward-navigation---rendering-syncs-with-URL-2.png +0 -0
  136. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4--Auth-state-preserved-when-navigating-between-authenticated-anonymous-states-1.png +0 -0
  137. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4--Auth-state-preserved-when-navigating-between-authenticated-anonymous-states-2.png +0 -0
  138. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4b--Browser-back-forward-preserves-auth-state-1.png +0 -0
  139. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4b--Browser-back-forward-preserves-auth-state-2.png +0 -0
  140. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-5--Protected-route-with-play-route-respects-authentication-guard-1.png +0 -0
  141. package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-5--Protected-route-with-play-route-respects-authentication-guard-2.png +0 -0
  142. package/examples/demo/test/browser/__screenshots__/user-reported-scenario.browser.test.tsx/User-scenario--home---about---home---contact---home--then-back-3x-1.png +0 -0
  143. package/examples/demo/test/browser/__screenshots__/user-reported-scenario.browser.test.tsx/User-scenario--login---home---about---home---contact---home--then-back-3x-1.png +0 -0
  144. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-play-route-event-with-correct-state-ID-1.png +0 -0
  145. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-play-route-event-with-correct-state-ID-2.png +0 -0
  146. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-xstate-route-event-with-correct-state-ID-1.png +0 -0
  147. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-play-route-event-1.png +0 -0
  148. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-xstate-route-event-1.png +0 -0
  149. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-1.png +0 -0
  150. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-2.png +0 -0
  151. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-xstate-route-event-1.png +0 -0
  152. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/GAP-12-fix-preserved--No-duplicate-history-entries-with-play-route-1.png +0 -0
  153. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/GAP-12-fix-preserved--No-duplicate-history-entries-with-play-route-2.png +0 -0
  154. package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Protected-route-sends-xstate-route-with-authentication-guard-1.png +0 -0
  155. package/examples/demo/test/browser/back-button-duplicate.browser.test.tsx +148 -0
  156. package/examples/demo/test/browser/back-forward-sync.browser.test.tsx +149 -0
  157. package/examples/demo/test/browser/direct-navigation.browser.test.ts +146 -0
  158. package/examples/demo/test/browser/exact-user-scenario.browser.test.tsx +207 -0
  159. package/examples/demo/test/browser/guard-rejection.browser.test.tsx +52 -0
  160. package/examples/demo/test/browser/history-investigation.browser.test.tsx +82 -0
  161. package/examples/demo/test/browser/history-navigation.browser.test.ts +351 -0
  162. package/examples/demo/test/browser/login-flow.browser.test.tsx +34 -0
  163. package/examples/demo/test/browser/navigation.browser.test.tsx +34 -0
  164. package/examples/demo/test/browser/protected-route-navigation.browser.test.tsx +161 -0
  165. package/examples/demo/test/browser/redirect-url-update.browser.test.tsx +140 -0
  166. package/examples/demo/test/browser/settings-parameter.browser.test.tsx +164 -0
  167. package/examples/demo/test/browser/settings-query-freeze.browser.test.ts +141 -0
  168. package/examples/demo/test/browser/state-driven.browser.test.ts +112 -0
  169. package/examples/demo/test/browser/tanstack-integration.browser.test.tsx +61 -0
  170. package/examples/demo/test/browser/uat-xstate-route-regression.browser.test.ts +58 -0
  171. package/examples/demo/test/browser/xstate-route-events.browser.test.ts +293 -0
  172. package/examples/demo/test/browser-back-view-rendering.test.ts +104 -0
  173. package/examples/demo/test/browser-e2e/auth-flow.browser.test.tsx +49 -0
  174. package/examples/demo/test/invalid-route-redirect.test.ts +40 -0
  175. package/examples/demo/test/passive-infra.test.ts +35 -0
  176. package/examples/demo/test/route-parameters.test.ts +539 -0
  177. package/examples/demo/test/signal-only.test.ts +54 -0
  178. package/examples/demo/test/strict-separation.test.ts +37 -0
  179. package/examples/demo/test/test-utils.ts +49 -0
  180. package/examples/demo/tsconfig.json +21 -0
  181. package/examples/demo/tsconfig.tsbuildinfo +1 -0
  182. package/examples/demo/vite.config.ts +13 -0
  183. package/examples/demo/vitest.browser.config.ts +72 -0
  184. package/examples/demo/vitest.config.e2e.browser.ts +28 -0
  185. package/examples/demo/vitest.config.ts +35 -0
  186. package/package.json +51 -0
  187. package/src/extract-params.ts +75 -0
  188. package/src/index.ts +31 -0
  189. package/src/play-router-provider.tsx +46 -0
  190. package/src/route-map.ts +158 -0
  191. package/src/tanstack-router-bridge.ts +135 -0
  192. package/src/types.ts +26 -0
  193. package/src/utils.ts +12 -0
  194. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/Browser-back-button-sends-route-navigate-event-to-actor-1.png +0 -0
  195. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-prevents-circular-updates-1.png +0 -0
  196. package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-syncs-actor-route-to-browser-URL-1.png +0 -0
  197. package/test/browser/signal-synced-history.browser.test.ts +95 -0
  198. package/test/route-map.test.ts +107 -0
  199. package/test/tanstack-router-bridge.test.ts +318 -0
  200. package/test/urlpattern-integration.test.ts +145 -0
  201. package/tsconfig.json +16 -0
  202. package/tsconfig.tsbuildinfo +1 -0
  203. package/vitest.config.ts +35 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Parameter extraction utilities using URLPattern API
3
+ *
4
+ * Provides robust parameter extraction from URL paths and query strings.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ /**
10
+ * Extract route parameters from URL path based on route pattern using URLPattern API
11
+ *
12
+ * @param pathname - Actual URL path (e.g., "/profile/123")
13
+ * @param pattern - Route pattern with params (e.g., "/profile/:userId")
14
+ * @returns Object with extracted parameters
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * extractParams("/profile/123", "/profile/:userId")
19
+ * // Returns: { userId: "123" }
20
+ *
21
+ * extractParams("/api/123", "/api/:id(\\d+)")
22
+ * // Returns: { id: "123" }
23
+ * // Validates numeric constraint via URLPattern
24
+ *
25
+ * extractParams("/docs/intro", "/docs/*")
26
+ * // Returns: { "0": "intro" }
27
+ * ```
28
+ */
29
+ export function extractParams(pathname: string, pattern: string): Record<string, string> {
30
+ // Reuse existing implementation from signal-synced-history.ts
31
+ // URLPattern-based extraction with :param syntax
32
+ const params: Record<string, string> = {};
33
+
34
+ // Parse pattern and pathname to extract named segments
35
+ const patternParts = pattern.split("/").filter(Boolean);
36
+ const pathParts = pathname.split("/").filter(Boolean);
37
+
38
+ for (let i = 0; i < patternParts.length; i++) {
39
+ const patternPart = patternParts[i];
40
+ const pathPart = pathParts[i];
41
+
42
+ if (patternPart.startsWith(":")) {
43
+ const paramName = patternPart.slice(1).replace("?", "");
44
+ if (pathPart) {
45
+ params[paramName] = decodeURIComponent(pathPart);
46
+ }
47
+ }
48
+ }
49
+
50
+ return params;
51
+ }
52
+
53
+ /**
54
+ * Extract query parameters from search string
55
+ *
56
+ * @param search - Query string with or without leading ? (e.g., "?tab=settings" or "tab=settings")
57
+ * @returns Object mapping query parameter names to values
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * extractQueryParams("?tab=settings&theme=dark")
62
+ * // Returns: { tab: "settings", theme: "dark" }
63
+ *
64
+ * extractQueryParams("q=hello%20world")
65
+ * // Returns: { q: "hello world" } (auto-decoded)
66
+ * ```
67
+ */
68
+ export function extractQueryParams(search: string): Record<string, string> {
69
+ const params = new URLSearchParams(search);
70
+ const result: Record<string, string> = {};
71
+ params.forEach((value, key) => {
72
+ result[key] = value;
73
+ });
74
+ return result;
75
+ }
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @xmachines/play-tanstack-react-router
3
+ *
4
+ * TanStack Router adapter for XMachines Play architecture.
5
+ * Synchronizes browser URL with actor state through passive infrastructure.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ // New exports (primary API - RouterBridge pattern)
11
+ export { TanStackReactRouterBridge } from "./tanstack-router-bridge.js";
12
+ export { RouteMap, createRouteMapFromTree } from "./route-map.js";
13
+ export { extractParams, extractQueryParams } from "./extract-params.js";
14
+ export type { RouteMapping } from "./route-map.js";
15
+ export type { TanStackRouterLike } from "./tanstack-router-bridge.js";
16
+
17
+ // TanStack Router integration (React-only)
18
+ export {
19
+ PlayRouterProvider,
20
+ type PlayRouterProviderProps,
21
+ type TanStackRouterInstance,
22
+ } from "./play-router-provider.js";
23
+
24
+ // Re-export shared utilities for convenience
25
+ export { extractMachineRoutes, createRouteMap } from "@xmachines/play-router";
26
+
27
+ // Core routing utilities
28
+ export type { RouteNavigateEvent } from "./types.js";
29
+
30
+ // Re-exports from protocols
31
+ export type { RouterBridge, PlayRouteEvent } from "@xmachines/play-router";
@@ -0,0 +1,46 @@
1
+ /**
2
+ * PlayRouterProvider — React convenience wrapper for TanStackReactRouterBridge
3
+ *
4
+ * Creates and connects a TanStackReactRouterBridge in a React component lifecycle.
5
+ * Actor state drives router navigation; router events send play.route to actor.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <PlayRouterProvider
10
+ * actor={actor}
11
+ * router={router}
12
+ * routeMap={routeMap}
13
+ * renderer={(currentActor, currentRouter) => {
14
+ * void currentRouter;
15
+ * return <PlayRenderer actor={currentActor} components={components} />;
16
+ * }}
17
+ * />
18
+ * ```
19
+ */
20
+ import { useEffect, type ReactNode } from "react";
21
+ import type { AbstractActor, Routable, Viewable } from "@xmachines/play-actor";
22
+ import type { AnyActorLogic } from "xstate";
23
+ import { TanStackReactRouterBridge } from "./tanstack-router-bridge.js";
24
+ import type { RouteMap } from "./route-map.js";
25
+
26
+ export type TanStackRouterInstance = ConstructorParameters<typeof TanStackReactRouterBridge>[0];
27
+
28
+ export interface PlayRouterProviderProps {
29
+ actor: AbstractActor<AnyActorLogic> & Routable & Viewable;
30
+ router: TanStackRouterInstance;
31
+ routeMap: RouteMap;
32
+ renderer: (
33
+ actor: AbstractActor<AnyActorLogic> & Routable & Viewable,
34
+ router: TanStackRouterInstance,
35
+ ) => ReactNode;
36
+ }
37
+
38
+ export function PlayRouterProvider({ actor, router, routeMap, renderer }: PlayRouterProviderProps) {
39
+ useEffect(() => {
40
+ const bridge = new TanStackReactRouterBridge(router, actor, routeMap);
41
+ bridge.connect();
42
+ return () => bridge.disconnect();
43
+ }, [actor, router, routeMap]);
44
+
45
+ return <>{renderer(actor, router)}</>;
46
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * RouteMap - Bidirectional mapping between state IDs and route paths
3
+ *
4
+ * Provides efficient lookup for:
5
+ * - State ID → Path (actor state changes → router navigation)
6
+ * - Path → State ID (router navigation → actor events)
7
+ *
8
+ * Supports both static paths and parameterized paths with :param syntax.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+
13
+ import type { RouteTree, RouteNode } from "@xmachines/play-router";
14
+
15
+ /**
16
+ * Mapping between state machine state ID and router path
17
+ */
18
+ export interface RouteMapping {
19
+ /** State ID from state machine (e.g., "settings.profile") */
20
+ stateId: string;
21
+ /** Router path with optional parameters (e.g., "/settings/:section?") */
22
+ path: string;
23
+ }
24
+
25
+ /**
26
+ * Bidirectional route mapper with pattern matching support
27
+ *
28
+ * Maps between state IDs (XMachines actor) and paths (router).
29
+ * Handles both exact matches (static paths) and pattern matches (parameterized paths).
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const routes: RouteMapping[] = [
34
+ * { stateId: "home", path: "/" },
35
+ * { stateId: "profile", path: "/profile/:userId" },
36
+ * { stateId: "settings", path: "/settings/:section?" }
37
+ * ];
38
+ *
39
+ * const routeMap = new RouteMap(routes);
40
+ *
41
+ * routeMap.getStateIdByPath("/profile/123"); // "profile"
42
+ * routeMap.getPathByStateId("home"); // "/"
43
+ * ```
44
+ */
45
+ export class RouteMap {
46
+ private stateIdToPath: Map<string, string>;
47
+ private pathToStateId: Map<string, string>;
48
+ private patterns: Array<{ pattern: RegExp; stateId: string; path: string }>;
49
+
50
+ /**
51
+ * Create RouteMap from route mappings
52
+ *
53
+ * @param routes - Array of state ID → path mappings
54
+ */
55
+ constructor(routes: RouteMapping[]) {
56
+ this.stateIdToPath = new Map();
57
+ this.pathToStateId = new Map();
58
+ this.patterns = [];
59
+
60
+ for (const { stateId, path } of routes) {
61
+ this.stateIdToPath.set(stateId, path);
62
+
63
+ // Check if path has parameters
64
+ if (path.includes(":")) {
65
+ // Convert :param and :param? to regex
66
+ const pattern = path
67
+ .replace(/\/:[^/]+\?/g, "(?:/([^/]+))?") // Optional params with optional slash
68
+ .replace(/:[^/]+/g, "([^/]+)"); // Required params
69
+ this.patterns.push({
70
+ pattern: new RegExp(`^${pattern}$`),
71
+ stateId,
72
+ path,
73
+ });
74
+ } else {
75
+ // Exact match for static paths
76
+ this.pathToStateId.set(path, stateId);
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Get state ID from router path
83
+ *
84
+ * Tries exact match first, then pattern match for parameterized paths.
85
+ *
86
+ * @param path - Router path (e.g., "/profile/123")
87
+ * @returns State ID or null if no match
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * routeMap.getStateIdByPath("/"); // "home" (exact match)
92
+ * routeMap.getStateIdByPath("/profile/123"); // "profile" (pattern match)
93
+ * ```
94
+ */
95
+ getStateIdByPath(path: string): string | null {
96
+ // Try exact match first
97
+ const exactMatch = this.pathToStateId.get(path);
98
+ if (exactMatch) return exactMatch;
99
+
100
+ // Try pattern match
101
+ for (const { pattern, stateId } of this.patterns) {
102
+ if (pattern.test(path)) return stateId;
103
+ }
104
+
105
+ return null;
106
+ }
107
+
108
+ /**
109
+ * Get router path from state ID
110
+ *
111
+ * Returns the route pattern with parameter placeholders (not substituted).
112
+ *
113
+ * @param stateId - State machine state ID
114
+ * @returns Router path or null if no match
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * routeMap.getPathByStateId("home"); // "/"
119
+ * routeMap.getPathByStateId("profile"); // "/profile/:userId"
120
+ * ```
121
+ */
122
+ getPathByStateId(stateId: string): string | null {
123
+ return this.stateIdToPath.get(stateId) || null;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Create RouteMap from RouteTree
129
+ *
130
+ * Converts XMachines route tree to RouteMap for use with router adapters.
131
+ *
132
+ * @param routeTree - Route tree from extractMachineRoutes()
133
+ * @returns RouteMap instance
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * import { extractMachineRoutes } from '@xmachines/play-router';
138
+ * import { createRouteMapFromTree } from '@xmachines/play-tanstack-react-router';
139
+ *
140
+ * const routeTree = extractMachineRoutes(machine);
141
+ * const routeMap = createRouteMapFromTree(routeTree);
142
+ * ```
143
+ */
144
+ export function createRouteMapFromTree(routeTree: RouteTree): RouteMap {
145
+ const routes: RouteMapping[] = [];
146
+
147
+ function traverse(node: RouteNode) {
148
+ if (node.id && node.path) {
149
+ routes.push({ stateId: node.id, path: node.path });
150
+ }
151
+ if (node.children) {
152
+ node.children.forEach(traverse);
153
+ }
154
+ }
155
+
156
+ traverse(routeTree.root);
157
+ return new RouteMap(routes);
158
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * TanStackReactRouterBridge - TanStack React Router adapter implementing RouterBridge protocol
3
+ *
4
+ * Provides bidirectional synchronization between XMachines actor state
5
+ * and TanStack React Router. Extends RouterBridgeBase which handles all
6
+ * common lifecycle, sync, and circular-update prevention logic.
7
+ *
8
+ * Only the 3 TanStack-specific methods need to be implemented:
9
+ * - navigateRouter: calls router.navigate({ to: path })
10
+ * - watchRouterChanges: subscribes to router.history for ALL navigation types
11
+ * - unwatchRouterChanges: calls the unsubscribe function
12
+ *
13
+ * Architectural Invariants:
14
+ * - INV-02 (Passive Infrastructure): Router reflects actor state, never decides
15
+ * - Actor validates all navigation via guards before URL changes
16
+ *
17
+ * @packageDocumentation
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { createRouter } from '@tanstack/react-router';
22
+ * import { definePlayer } from '@xmachines/play-xstate';
23
+ * import { TanStackReactRouterBridge, createRouteMapFromTree } from '@xmachines/play-tanstack-react-router';
24
+ *
25
+ * const router = createRouter({ routes });
26
+ * const actor = definePlayer({ machine, catalog })();
27
+ * const routeMap = createRouteMapFromTree(extractMachineRoutes(machine));
28
+ *
29
+ * const bridge = new TanStackReactRouterBridge(router, actor, routeMap);
30
+ * bridge.connect();
31
+ *
32
+ * // Cleanup on unmount
33
+ * return () => bridge.disconnect();
34
+ * ```
35
+ */
36
+
37
+ import { RouterBridgeBase } from "@xmachines/play-router";
38
+ import type { AbstractActor, Routable } from "@xmachines/play-actor";
39
+ import type { AnyActorLogic } from "xstate";
40
+ import type { RouteMap } from "./route-map.js";
41
+
42
+ export type TanStackRouterLike = {
43
+ navigate(args: { to: string }): void;
44
+ state?: { location?: { pathname?: string } };
45
+ history: {
46
+ subscribe(
47
+ handler: (event: { location: { pathname: string; search?: string } }) => void,
48
+ ): () => void;
49
+ };
50
+ };
51
+
52
+ /**
53
+ * TanStack React Router adapter implementing RouterBridge protocol via RouterBridgeBase
54
+ *
55
+ * @remarks
56
+ * Extends RouterBridgeBase to handle all common lifecycle and sync logic.
57
+ * Only 3 TanStack-specific methods are implemented here.
58
+ *
59
+ * Subscribes to router.history (not router.subscribe("onBeforeLoad")) so that
60
+ * back/forward browser navigation (popstate events) are also captured. The
61
+ * router.subscribe("onBeforeLoad") approach only works when TanStack's
62
+ * Transitioner component is mounted (i.e. inside a full <RouterProvider>).
63
+ * Since this bridge is used without <RouterProvider>, we must subscribe to
64
+ * the underlying history object directly — which is exactly what Transitioner
65
+ * does internally: `router.history.subscribe(router.load)`.
66
+ *
67
+ * Architectural Invariants:
68
+ * - INV-02 (Passive Infrastructure): Router reflects actor state, never decides
69
+ * - Actor validates all navigation via guards before URL changes
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const bridge = new TanStackReactRouterBridge(router, actor, routeMap);
74
+ * bridge.connect();
75
+ * return () => bridge.disconnect();
76
+ * ```
77
+ */
78
+ export class TanStackReactRouterBridge extends RouterBridgeBase {
79
+ private routerUnsubscribe: (() => void) | null = null;
80
+
81
+ /**
82
+ * Create TanStack React Router bridge
83
+ *
84
+ * @param router - TanStack React Router instance
85
+ * @param actor - XMachines actor instance
86
+ * @param routeMap - Bidirectional mapping between state IDs and paths
87
+ */
88
+ constructor(
89
+ private readonly router: TanStackRouterLike,
90
+ actor: AbstractActor<AnyActorLogic> & Routable,
91
+ routeMap: RouteMap,
92
+ ) {
93
+ // Adapt RouteMap's null returns to undefined for RouterBridgeBase compatibility
94
+ super(actor, {
95
+ getStateIdByPath: (path: string) => routeMap.getStateIdByPath(path) ?? undefined,
96
+ getPathByStateId: (id: string) => routeMap.getPathByStateId(id) ?? undefined,
97
+ });
98
+ }
99
+
100
+ protected navigateRouter(path: string): void {
101
+ this.router.navigate({ to: path });
102
+ }
103
+
104
+ /**
105
+ * Read the router's current pathname for initial sync.
106
+ *
107
+ * Called once in connect() to handle cold-load / deep-link scenarios where
108
+ * the URL differs from the actor's initial state.
109
+ */
110
+ protected override getInitialRouterPath(): string | null {
111
+ return this.router.state?.location?.pathname ?? null;
112
+ }
113
+
114
+ /**
115
+ * Subscribe to ALL navigation events via router.history.
116
+ *
117
+ * router.history.subscribe fires for PUSH, POP, BACK, FORWARD, REPLACE, and GO —
118
+ * covering both link clicks and browser back/forward button presses.
119
+ *
120
+ * The subscriber callback receives { location, action } where location is the
121
+ * new history location with pathname and search already updated.
122
+ */
123
+ protected watchRouterChanges(): void {
124
+ this.routerUnsubscribe = this.router.history.subscribe(
125
+ ({ location }: { location: { pathname: string; search?: string } }) => {
126
+ this.syncActorFromRouter(location.pathname, location.search ?? "");
127
+ },
128
+ );
129
+ }
130
+
131
+ protected unwatchRouterChanges(): void {
132
+ this.routerUnsubscribe?.();
133
+ this.routerUnsubscribe = null;
134
+ }
135
+ }
package/src/types.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Type definitions for TanStack Router integration with Play Architecture.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ /**
8
+ * Event sent to actor when browser navigates.
9
+ *
10
+ * Browser navigation (user clicks link, back/forward button, direct URL entry)
11
+ * sends this event to the actor. The actor's guards determine if navigation
12
+ * is allowed based on business logic.
13
+ *
14
+ * Invariant: Passive Infrastructure - Router suggests, actor decides validity.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * actor.send({ type: 'route.navigate', path: '/dashboard' });
19
+ * ```
20
+ */
21
+ export interface RouteNavigateEvent {
22
+ /** Event type discriminant (extends XState event pattern) */
23
+ readonly type: "route.navigate";
24
+ /** Target route path (may include parameters, e.g., /posts/123) */
25
+ readonly path: string;
26
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Utility barrel exports
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ // Parameter extraction utilities
8
+ export { extractParams, extractQueryParams } from "./extract-params.js";
9
+
10
+ // RouteMap utilities
11
+ export { RouteMap, createRouteMapFromTree } from "./route-map.js";
12
+ export type { RouteMapping } from "./route-map.js";
@@ -0,0 +1,95 @@
1
+ import { test, expect } from "vitest";
2
+ import { Signal } from "@xmachines/play-signals";
3
+ import { SignalSyncedHistory } from "../../src/signal-synced-history.js";
4
+
5
+ test("SignalSyncedHistory syncs actor route to browser URL", async () => {
6
+ const currentRoute = new Signal.State("/home");
7
+
8
+ const mockActor = {
9
+ currentRoute,
10
+ send: () => {},
11
+ } as any;
12
+
13
+ const history = new SignalSyncedHistory(mockActor, "/home");
14
+
15
+ // Initial URL should match signal (via memory history)
16
+ const memoryHistory = history.getHistory();
17
+ expect(memoryHistory.location.pathname).toBe("/home");
18
+
19
+ // Update signal
20
+ currentRoute.set("/dashboard");
21
+
22
+ // Wait for microtask batch
23
+ await new Promise((resolve) => queueMicrotask(resolve));
24
+
25
+ // Memory history should update
26
+ expect(memoryHistory.location.pathname).toBe("/dashboard");
27
+
28
+ history.dispose();
29
+ });
30
+
31
+ test("Browser back button sends route.navigate event to actor", async () => {
32
+ const currentRoute = new Signal.State("/page1");
33
+ const sentEvents: any[] = [];
34
+
35
+ const mockActor = {
36
+ currentRoute,
37
+ send: (event: any) => {
38
+ sentEvents.push(event);
39
+ },
40
+ } as any;
41
+
42
+ // Initialize with matching path to avoid initial sync
43
+ const history = new SignalSyncedHistory(mockActor, "/page1");
44
+ const memoryHistory = history.getHistory();
45
+
46
+ // Clear any initialization events
47
+ sentEvents.length = 0;
48
+
49
+ // Navigate to page2 through memory history (simulating user navigation)
50
+ memoryHistory.push("/page2");
51
+
52
+ // Wait for navigation
53
+ await new Promise((resolve) => setTimeout(resolve, 100));
54
+
55
+ // Verify event sent to actor
56
+ const navEvents = sentEvents.filter((e) => e.type === "route.navigate");
57
+ expect(navEvents.length).toBeGreaterThan(0);
58
+ expect(navEvents[0].path).toBe("/page2");
59
+
60
+ // Navigate back
61
+ memoryHistory.back();
62
+ await new Promise((resolve) => setTimeout(resolve, 100));
63
+
64
+ // Should have sent another event for going back to /page1
65
+ expect(sentEvents.length).toBeGreaterThan(1);
66
+
67
+ history.dispose();
68
+ });
69
+
70
+ test("SignalSyncedHistory prevents circular updates", async () => {
71
+ const currentRoute = new Signal.State("/initial");
72
+ const sentEvents: any[] = [];
73
+
74
+ const mockActor = {
75
+ currentRoute,
76
+ send: (event: any) => {
77
+ sentEvents.push(event);
78
+ },
79
+ } as any;
80
+
81
+ // Initialize with matching path to avoid initial sync
82
+ const history = new SignalSyncedHistory(mockActor, "/initial");
83
+
84
+ // Clear any initialization events
85
+ sentEvents.length = 0;
86
+
87
+ // Update signal directly (actor decision)
88
+ currentRoute.set("/new-path");
89
+ await new Promise((resolve) => queueMicrotask(resolve));
90
+
91
+ // Should not trigger send events (actor initiated the change)
92
+ expect(sentEvents.length).toBe(0); // No circular events
93
+
94
+ history.dispose();
95
+ });