@xmachines/docs 1.0.0-beta.26 → 1.0.0-beta.27

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 (216) hide show
  1. package/api/@xmachines/play/classes/PlayError.md +4 -4
  2. package/api/@xmachines/play/type-aliases/PlayEvent.md +2 -2
  3. package/api/@xmachines/play-actor/classes/AbstractActor.md +3 -3
  4. package/api/@xmachines/play-actor/functions/typedSpec.md +1 -1
  5. package/api/@xmachines/play-actor/interfaces/PlaySpec.md +2 -2
  6. package/api/@xmachines/play-actor/interfaces/Routable.md +3 -3
  7. package/api/@xmachines/play-actor/interfaces/ViewMetadata.md +3 -3
  8. package/api/@xmachines/play-actor/interfaces/Viewable.md +2 -2
  9. package/api/@xmachines/play-dom/README.md +24 -8
  10. package/api/@xmachines/play-dom/classes/PlayRenderer.md +4 -4
  11. package/api/@xmachines/play-dom/functions/connectRenderer.md +1 -1
  12. package/api/@xmachines/play-dom/functions/defineRegistry.md +61 -0
  13. package/api/@xmachines/play-dom/functions/renderSpec.md +29 -12
  14. package/api/@xmachines/play-dom/interfaces/ComponentContext.md +62 -0
  15. package/api/@xmachines/play-dom/interfaces/ConnectRendererOptions.md +9 -9
  16. package/api/@xmachines/play-dom/interfaces/DefineRegistryOptions.md +25 -0
  17. package/api/@xmachines/play-dom/interfaces/DefineRegistryResult.md +18 -0
  18. package/api/@xmachines/play-dom/interfaces/DomRenderContext.md +13 -10
  19. package/api/@xmachines/play-dom/interfaces/EventHandle.md +29 -0
  20. package/api/@xmachines/play-dom/interfaces/PlayDomOptions.md +5 -5
  21. package/api/@xmachines/play-dom/type-aliases/ComponentFn.md +53 -0
  22. package/api/@xmachines/play-dom/type-aliases/ComponentRegistry.md +21 -0
  23. package/api/@xmachines/play-dom/type-aliases/DomComponentRenderer.md +16 -3
  24. package/api/@xmachines/play-dom/type-aliases/DomRegistry.md +5 -2
  25. package/api/@xmachines/play-dom-router/functions/connectRouter.md +1 -1
  26. package/api/@xmachines/play-dom-router/functions/createBrowserHistory.md +1 -1
  27. package/api/@xmachines/play-dom-router/functions/createRouteMap.md +1 -1
  28. package/api/@xmachines/play-dom-router/functions/createRouter.md +1 -1
  29. package/api/@xmachines/play-dom-router/interfaces/BrowserHistory.md +14 -14
  30. package/api/@xmachines/play-dom-router/interfaces/BrowserWindow.md +14 -14
  31. package/api/@xmachines/play-dom-router/interfaces/ConnectRouterOptions.md +4 -4
  32. package/api/@xmachines/play-dom-router/interfaces/PlayRouteEvent.md +6 -6
  33. package/api/@xmachines/play-dom-router/interfaces/RouteMap.md +3 -3
  34. package/api/@xmachines/play-dom-router/interfaces/RouteMapLike.md +3 -3
  35. package/api/@xmachines/play-dom-router/interfaces/RouteMapping.md +3 -3
  36. package/api/@xmachines/play-dom-router/interfaces/RouterBridge.md +3 -3
  37. package/api/@xmachines/play-dom-router/interfaces/VanillaRouter.md +4 -4
  38. package/api/@xmachines/play-dom-router/type-aliases/RoutableActor.md +1 -1
  39. package/api/@xmachines/play-dom-router-demo/README.md +46 -51
  40. package/api/@xmachines/play-react/classes/PlayErrorBoundary.md +5 -5
  41. package/api/@xmachines/play-react/functions/useActor.md +1 -1
  42. package/api/@xmachines/play-react/functions/useSignalEffect.md +1 -1
  43. package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryProps.md +4 -4
  44. package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryState.md +3 -3
  45. package/api/@xmachines/play-react/interfaces/PlayRendererProps.md +7 -7
  46. package/api/@xmachines/play-react/type-aliases/PlayActor.md +1 -1
  47. package/api/@xmachines/play-react/variables/PlayRenderer.md +1 -1
  48. package/api/@xmachines/play-react-router/classes/ReactRouterBridge.md +23 -23
  49. package/api/@xmachines/play-react-router/classes/RouteMap.md +4 -4
  50. package/api/@xmachines/play-react-router/functions/PlayRouterProvider.md +1 -1
  51. package/api/@xmachines/play-react-router/functions/createRouteMapFromTree.md +1 -1
  52. package/api/@xmachines/play-react-router/interfaces/PlayRouteEvent.md +6 -6
  53. package/api/@xmachines/play-react-router/interfaces/PlayRouterProviderProps.md +5 -5
  54. package/api/@xmachines/play-react-router/interfaces/RouteMapping.md +3 -3
  55. package/api/@xmachines/play-react-router/interfaces/RouterBridge.md +3 -3
  56. package/api/@xmachines/play-router/classes/BaseRouteMap.md +4 -4
  57. package/api/@xmachines/play-router/classes/RouterBridgeBase.md +23 -23
  58. package/api/@xmachines/play-router/functions/buildPlayRouteEvent.md +1 -1
  59. package/api/@xmachines/play-router/functions/buildRouteTree.md +1 -1
  60. package/api/@xmachines/play-router/functions/createRouteMap.md +1 -1
  61. package/api/@xmachines/play-router/functions/createRouteMapFromMachine.md +1 -1
  62. package/api/@xmachines/play-router/functions/createRouteMapFromTree.md +1 -1
  63. package/api/@xmachines/play-router/functions/detectDuplicateRoutes.md +1 -1
  64. package/api/@xmachines/play-router/functions/extractMachineRoutes.md +1 -1
  65. package/api/@xmachines/play-router/functions/extractQuery.md +1 -1
  66. package/api/@xmachines/play-router/functions/extractRouteParams.md +1 -1
  67. package/api/@xmachines/play-router/functions/findRouteById.md +1 -1
  68. package/api/@xmachines/play-router/functions/findRouteByPath.md +1 -1
  69. package/api/@xmachines/play-router/functions/getNavigableRoutes.md +1 -1
  70. package/api/@xmachines/play-router/functions/getRoutableRoutes.md +1 -1
  71. package/api/@xmachines/play-router/functions/getTransitionReachableRoutes.md +1 -1
  72. package/api/@xmachines/play-router/functions/isRouteReachable.md +1 -1
  73. package/api/@xmachines/play-router/functions/machineToGraph.md +1 -1
  74. package/api/@xmachines/play-router/functions/routeExists.md +1 -1
  75. package/api/@xmachines/play-router/functions/sanitizePathname.md +1 -1
  76. package/api/@xmachines/play-router/functions/validateRouteFormat.md +1 -1
  77. package/api/@xmachines/play-router/functions/validateStateExists.md +1 -1
  78. package/api/@xmachines/play-router/interfaces/BuildPlayRouteEventOptions.md +4 -4
  79. package/api/@xmachines/play-router/interfaces/LocationLike.md +3 -3
  80. package/api/@xmachines/play-router/interfaces/MachineEdgeData.md +3 -3
  81. package/api/@xmachines/play-router/interfaces/MachineNodeData.md +5 -5
  82. package/api/@xmachines/play-router/interfaces/PlayRouteEvent.md +6 -6
  83. package/api/@xmachines/play-router/interfaces/RouteInfo.md +8 -8
  84. package/api/@xmachines/play-router/interfaces/RouteMap.md +4 -4
  85. package/api/@xmachines/play-router/interfaces/RouteMapping.md +3 -3
  86. package/api/@xmachines/play-router/interfaces/RouteMatch.md +3 -3
  87. package/api/@xmachines/play-router/interfaces/RouteNode.md +10 -10
  88. package/api/@xmachines/play-router/interfaces/RouteObject.md +2 -2
  89. package/api/@xmachines/play-router/interfaces/RouteTree.md +5 -5
  90. package/api/@xmachines/play-router/interfaces/RouteWatcherHandle.md +3 -3
  91. package/api/@xmachines/play-router/interfaces/RouterBridge.md +3 -3
  92. package/api/@xmachines/play-router/interfaces/WindowLike.md +3 -3
  93. package/api/@xmachines/play-router/type-aliases/MachineGraph.md +1 -1
  94. package/api/@xmachines/play-router/type-aliases/RouteMetadata.md +1 -1
  95. package/api/@xmachines/play-signals/functions/watchSignal.md +1 -1
  96. package/api/@xmachines/play-signals/interfaces/ComputedOptions.md +2 -2
  97. package/api/@xmachines/play-signals/interfaces/SignalComputed.md +2 -2
  98. package/api/@xmachines/play-signals/interfaces/SignalOptions.md +2 -2
  99. package/api/@xmachines/play-signals/interfaces/SignalState.md +3 -3
  100. package/api/@xmachines/play-signals/interfaces/SignalWatcher.md +4 -4
  101. package/api/@xmachines/play-signals/type-aliases/WatcherNotify.md +1 -1
  102. package/api/@xmachines/play-solid/functions/useActor.md +1 -1
  103. package/api/@xmachines/play-solid/interfaces/PlayRendererProps.md +7 -7
  104. package/api/@xmachines/play-solid/type-aliases/PlayActor.md +1 -1
  105. package/api/@xmachines/play-solid/variables/PlayRenderer.md +1 -1
  106. package/api/@xmachines/play-solid-router/classes/RouteMap.md +4 -4
  107. package/api/@xmachines/play-solid-router/classes/SolidRouterBridge.md +24 -24
  108. package/api/@xmachines/play-solid-router/functions/PlayRouterProvider.md +1 -1
  109. package/api/@xmachines/play-solid-router/functions/createRouteMap.md +1 -1
  110. package/api/@xmachines/play-solid-router/interfaces/AbstractActor.md +3 -3
  111. package/api/@xmachines/play-solid-router/interfaces/PlayRouteEvent.md +6 -6
  112. package/api/@xmachines/play-solid-router/interfaces/PlayRouterProviderProps.md +5 -5
  113. package/api/@xmachines/play-solid-router/interfaces/RouteMapping.md +3 -3
  114. package/api/@xmachines/play-solid-router/interfaces/RouterBridge.md +3 -3
  115. package/api/@xmachines/play-solid-router/type-aliases/RoutableActor.md +1 -1
  116. package/api/@xmachines/play-solid-router/type-aliases/SolidRouterHooks.md +4 -4
  117. package/api/@xmachines/play-solid-router-demo/README.md +25 -28
  118. package/api/@xmachines/play-svelte/interfaces/PlayRendererProps.md +7 -7
  119. package/api/@xmachines/play-svelte-spa-router/classes/RouteMap.md +4 -4
  120. package/api/@xmachines/play-svelte-spa-router/functions/connectRouter.md +1 -1
  121. package/api/@xmachines/play-svelte-spa-router/functions/createRouteMap.md +1 -1
  122. package/api/@xmachines/play-svelte-spa-router/interfaces/ConnectRouterOptions.md +4 -4
  123. package/api/@xmachines/play-svelte-spa-router/interfaces/PlayRouteEvent.md +6 -6
  124. package/api/@xmachines/play-svelte-spa-router/interfaces/RouteMapping.md +3 -3
  125. package/api/@xmachines/play-svelte-spa-router/interfaces/RouterBridge.md +3 -3
  126. package/api/@xmachines/play-svelte-spa-router/interfaces/WindowLike.md +3 -3
  127. package/api/@xmachines/play-svelte-spa-router/type-aliases/RoutableActor.md +1 -1
  128. package/api/@xmachines/play-svelte-spa-router-demo/README.md +119 -12
  129. package/api/@xmachines/play-sveltekit-router/classes/RouteMap.md +4 -4
  130. package/api/@xmachines/play-sveltekit-router/functions/connectRouter.md +1 -1
  131. package/api/@xmachines/play-sveltekit-router/functions/createRouteMap.md +1 -1
  132. package/api/@xmachines/play-sveltekit-router/interfaces/ConnectRouterOptions.md +4 -4
  133. package/api/@xmachines/play-sveltekit-router/interfaces/LocationLike.md +3 -3
  134. package/api/@xmachines/play-sveltekit-router/interfaces/PlayRouteEvent.md +6 -6
  135. package/api/@xmachines/play-sveltekit-router/interfaces/RouteMapping.md +3 -3
  136. package/api/@xmachines/play-sveltekit-router/interfaces/RouterBridge.md +3 -3
  137. package/api/@xmachines/play-sveltekit-router/type-aliases/RoutableActor.md +1 -1
  138. package/api/@xmachines/play-sveltekit-router-demo/README.md +120 -12
  139. package/api/@xmachines/play-tanstack-react-router/classes/RouteMap.md +4 -4
  140. package/api/@xmachines/play-tanstack-react-router/classes/TanStackReactRouterBridge.md +23 -23
  141. package/api/@xmachines/play-tanstack-react-router/functions/PlayRouterProvider.md +1 -1
  142. package/api/@xmachines/play-tanstack-react-router/functions/createRouteMap.md +1 -1
  143. package/api/@xmachines/play-tanstack-react-router/functions/createRouteMapFromTree.md +1 -1
  144. package/api/@xmachines/play-tanstack-react-router/functions/extractMachineRoutes.md +1 -1
  145. package/api/@xmachines/play-tanstack-react-router/interfaces/PlayRouteEvent.md +6 -6
  146. package/api/@xmachines/play-tanstack-react-router/interfaces/PlayRouterProviderProps.md +5 -5
  147. package/api/@xmachines/play-tanstack-react-router/interfaces/RouteMapping.md +3 -3
  148. package/api/@xmachines/play-tanstack-react-router/interfaces/RouteNavigateEvent.md +3 -3
  149. package/api/@xmachines/play-tanstack-react-router/interfaces/RouterBridge.md +3 -3
  150. package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterInstance.md +1 -1
  151. package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterLike.md +17 -3
  152. package/api/@xmachines/play-tanstack-react-router-demo/README.md +23 -29
  153. package/api/@xmachines/play-tanstack-solid-router/classes/RouteMap.md +4 -4
  154. package/api/@xmachines/play-tanstack-solid-router/classes/SolidRouterBridge.md +24 -24
  155. package/api/@xmachines/play-tanstack-solid-router/functions/PlayRouterProvider.md +1 -1
  156. package/api/@xmachines/play-tanstack-solid-router/functions/createRouteMap.md +1 -1
  157. package/api/@xmachines/play-tanstack-solid-router/interfaces/PlayRouteEvent.md +6 -6
  158. package/api/@xmachines/play-tanstack-solid-router/interfaces/PlayRouterProviderProps.md +5 -5
  159. package/api/@xmachines/play-tanstack-solid-router/interfaces/RouteMapping.md +3 -3
  160. package/api/@xmachines/play-tanstack-solid-router/interfaces/RouterBridge.md +3 -3
  161. package/api/@xmachines/play-tanstack-solid-router/type-aliases/RoutableActor.md +1 -1
  162. package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterInstance.md +1 -1
  163. package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterLike.md +3 -3
  164. package/api/@xmachines/play-tanstack-solid-router-demo/README.md +25 -26
  165. package/api/@xmachines/play-vue/functions/defineRegistry.md +1 -1
  166. package/api/@xmachines/play-vue/functions/useActor.md +1 -1
  167. package/api/@xmachines/play-vue/interfaces/PlayRendererProps.md +5 -5
  168. package/api/@xmachines/play-vue/type-aliases/ComponentEntry.md +1 -1
  169. package/api/@xmachines/play-vue/type-aliases/ComponentsMap.md +1 -1
  170. package/api/@xmachines/play-vue/type-aliases/DefineRegistryOptions.md +4 -3
  171. package/api/@xmachines/play-vue/type-aliases/PlayActor.md +1 -1
  172. package/api/@xmachines/play-vue/variables/PlayRenderer.md +1 -1
  173. package/api/@xmachines/play-vue-router/classes/RouteMap.md +4 -4
  174. package/api/@xmachines/play-vue-router/classes/VueBaseRouteMap.md +4 -4
  175. package/api/@xmachines/play-vue-router/classes/VueRouterBridge.md +24 -24
  176. package/api/@xmachines/play-vue-router/functions/createRouteMap.md +1 -1
  177. package/api/@xmachines/play-vue-router/interfaces/PlayRouteEvent.md +6 -6
  178. package/api/@xmachines/play-vue-router/interfaces/RouteMapping.md +3 -3
  179. package/api/@xmachines/play-vue-router/interfaces/RouterBridge.md +3 -3
  180. package/api/@xmachines/play-vue-router/type-aliases/RoutableActor.md +1 -1
  181. package/api/@xmachines/play-vue-router/variables/PlayRouterProvider.md +1 -1
  182. package/api/@xmachines/play-vue-router-demo/README.md +47 -40
  183. package/api/@xmachines/play-xstate/classes/PlayerActor.md +11 -11
  184. package/api/@xmachines/play-xstate/functions/buildRouteUrl.md +1 -1
  185. package/api/@xmachines/play-xstate/functions/composeGuards.md +1 -1
  186. package/api/@xmachines/play-xstate/functions/composeGuardsOr.md +1 -1
  187. package/api/@xmachines/play-xstate/functions/contextFieldMatches.md +1 -1
  188. package/api/@xmachines/play-xstate/functions/definePlayer.md +1 -1
  189. package/api/@xmachines/play-xstate/functions/deriveRoute.md +1 -1
  190. package/api/@xmachines/play-xstate/functions/eventMatches.md +1 -1
  191. package/api/@xmachines/play-xstate/functions/formatPlayRouteTransitions.md +1 -1
  192. package/api/@xmachines/play-xstate/functions/hasContext.md +1 -1
  193. package/api/@xmachines/play-xstate/functions/isAbsoluteRoute.md +1 -1
  194. package/api/@xmachines/play-xstate/functions/negateGuard.md +1 -1
  195. package/api/@xmachines/play-xstate/interfaces/PlayerConfig.md +3 -3
  196. package/api/@xmachines/play-xstate/interfaces/PlayerFactoryResumeOptions.md +2 -2
  197. package/api/@xmachines/play-xstate/interfaces/PlayerOptions.md +6 -6
  198. package/api/@xmachines/play-xstate/interfaces/RouteContext.md +5 -5
  199. package/api/@xmachines/play-xstate/type-aliases/ComposedGuard.md +1 -1
  200. package/api/@xmachines/play-xstate/type-aliases/Guard.md +1 -1
  201. package/api/@xmachines/play-xstate/type-aliases/GuardArray.md +1 -1
  202. package/api/@xmachines/play-xstate/type-aliases/PlayerFactory.md +1 -1
  203. package/api/@xmachines/play-xstate/type-aliases/RouteMachineConfig.md +4 -4
  204. package/api/@xmachines/play-xstate/type-aliases/RouteStateNode.md +4 -4
  205. package/api/@xmachines/shared/functions/defineXmVitestConfig.md +3 -7
  206. package/api/@xmachines/shared/functions/xmAliases.md +1 -1
  207. package/examples/README.md +48 -35
  208. package/examples/basic-state-machine.md +75 -31
  209. package/examples/form-validation.md +199 -127
  210. package/examples/multi-router-integration.md +312 -230
  211. package/examples/routing-patterns.md +243 -189
  212. package/examples/traffic-light.md +114 -65
  213. package/guides/README.md +29 -15
  214. package/guides/getting-started.md +224 -144
  215. package/guides/installation.md +153 -213
  216. package/package.json +2 -2
@@ -1,261 +1,315 @@
1
- # Routing Patterns - Play Architecture
1
+ <!-- generated-by: gsd-doc-writer -->
2
2
 
3
- Learn how to implement parameter-aware navigation using routing patterns with `meta.route` URL patterns and `play.route` events.
3
+ # Routing Patterns
4
+
5
+ How the `authMachine` uses `meta.route`, `play.route` events, `formatPlayRouteTransitions`, and `always` guards to implement actor-authoritative URL routing.
4
6
 
5
7
  ## Overview
6
8
 
7
- introduces enhanced routing capabilities that allow state machines to handle dynamic routes with parameters while maintaining Actor Authority (INV-01). The router observes actor state changes and syncs the browser URL, but the actor decides which routes are valid through guards.
9
+ XMachines inverts the usual routing model. The **actor** owns navigation its guards decide which states are valid. The **router** is passive infrastructure that observes `actor.currentRoute` and keeps the browser URL in sync. This is **Actor Authority (INV-01)**.
10
+
11
+ Every routing interaction starts with a `play.route` event sent to the actor. If the machine's guards allow the transition, the state changes and `actor.currentRoute` updates. The router bridge sees the new signal value and pushes a history entry. The URL never changes unless the actor approves.
8
12
 
9
13
  ## Key Concepts
10
14
 
11
- ### Route Marker
15
+ ### `meta.route` — Marking States as Routable
12
16
 
13
- States with `meta.route` property are routable - they can receive `play.route` events and be navigated to by URL. States without `meta.route` are internal machine states that don't correspond to URLs.
17
+ Add a `meta.route` URL template to any state to make it routable:
14
18
 
15
19
  ```typescript
16
20
  states: {
21
+ home: {
22
+ id: "home", // required: used as the play.route target ("#home")
23
+ meta: {
24
+ route: "/", // absolute URL path
25
+ },
26
+ },
17
27
  dashboard: {
28
+ id: "dashboard",
29
+ meta: {
30
+ route: "/dashboard", // absolute URL path
31
+ },
32
+ states: {
33
+ overview: {
34
+ id: "dashboard-overview",
35
+ meta: {
36
+ route: "overview", // RELATIVE — resolves to /dashboard/overview
37
+ },
38
+ },
39
+ },
40
+ },
41
+ profile: {
42
+ id: "profile",
43
+ meta: {
44
+ route: "/profile/:username", // required parameter
45
+ },
46
+ },
47
+ settings: {
48
+ id: "settings",
18
49
  meta: {
19
- route: '/dashboard', // Marks state as routable
20
- view: { component: 'Dashboard' }
21
- }
22
- }
50
+ route: "/settings/:section?", // optional parameter
51
+ },
52
+ },
23
53
  }
24
54
  ```
25
55
 
26
- ### Play.route Events with Parameters
27
-
28
- Use `play.route` events to navigate with parameters. Unlike `xstate.route` (which doesn't support parameters), `play.route` allows passing data alongside navigation:
29
-
30
- ```typescript
31
- // Navigate to profile with userId parameter
32
- actor.send({
33
- type: "play.route",
34
- to: "/profile/user123",
35
- params: { userId: "user123" },
36
- });
37
- ```
38
-
39
- ### Parameter Patterns in meta.route
40
-
41
- Define URL patterns with `:param` syntax for required parameters and `:param?` for optional ones:
42
-
43
- ```typescript
44
- meta: {
45
- route: '/profile/:userId', // Required parameter
46
- route: '/settings/:section?', // Optional parameter
47
- }
48
- ```
56
+ **Rules:**
49
57
 
50
- ## Complete Example
58
+ - Absolute paths start with `/`.
59
+ - Relative paths (no leading `/`) are resolved against the parent state's route.
60
+ - Parameter segments use `:param` (required) and `:param?` (optional).
61
+ - Every routable state **must** have an `id` — `formatPlayRouteTransitions` throws `MissingStateIdError` otherwise.
51
62
 
52
- For runnable implementations of these routing patterns, see the [Examples Index](./README.md).
63
+ ### `formatPlayRouteTransitions` Auto-Generating Route Handlers
53
64
 
54
- ### Machine Configuration
65
+ Instead of hand-writing `play.route` event handlers for every routable state, wrap your machine config with `formatPlayRouteTransitions`:
55
66
 
56
67
  ```typescript
57
68
  import { setup } from "xstate";
58
- import { definePlayer } from "@xmachines/play-xstate";
69
+ import { definePlayer, formatPlayRouteTransitions } from "@xmachines/play-xstate";
70
+ import type { PlayRouteEvent } from "@xmachines/play-router";
71
+
72
+ interface AuthContext {
73
+ isAuthenticated: boolean;
74
+ username: string | null;
75
+ params: Record<string, string>; // required for formatPlayRouteTransitions
76
+ query: Record<string, string>; // required for formatPlayRouteTransitions
77
+ }
59
78
 
60
- const authMachine = setup({
79
+ const authSetup = setup({
61
80
  types: {
62
- context: {} as {
63
- isAuthenticated: boolean;
64
- userId: string;
65
- params: Record<string, string>;
66
- query: Record<string, string>;
67
- },
81
+ context: {} as AuthContext,
68
82
  events: {} as
69
- | { type: "play.route"; to: string; params?: Record<string, string> }
70
- | { type: "auth.login"; userId: string },
71
- },
72
- }).createMachine({
73
- initial: "login",
74
- context: {
75
- isAuthenticated: false,
76
- userId: "",
77
- params: {},
78
- query: {},
79
- },
80
- states: {
81
- login: {
82
- id: "login",
83
- meta: {
84
- route: "/login",
85
- view: { component: "LoginView" },
86
- },
87
- on: {
88
- "auth.login": {
89
- target: "profile",
90
- actions: ({ context, event }) => {
91
- context.isAuthenticated = true;
92
- context.userId = event.userId;
93
- },
94
- },
95
- },
96
- },
97
-
98
- profile: {
99
- id: "profile",
100
- meta: {
101
- route: "/profile/:userId",
102
- view: {
103
- component: "ProfileView",
104
- userId: (ctx) => ctx.params.userId || ctx.userId,
105
- },
106
- },
107
- always: [
108
- {
109
- target: "login",
110
- guard: ({ context }) => !context.isAuthenticated,
111
- // Redirect to login if not authenticated
112
- },
113
- ],
114
- on: {
115
- "play.route": {
116
- actions: ({ context, event }) => {
117
- if (event.params?.userId) {
118
- context.params = { userId: event.params.userId };
119
- }
120
- },
121
- },
122
- },
123
- },
83
+ | PlayRouteEvent
84
+ | { type: "auth.login"; username: string }
85
+ | { type: "auth.logout" },
86
+ input: {} as Partial<AuthContext> | undefined,
124
87
  },
125
88
  });
126
89
 
127
- const createPlayer = definePlayer({ machine: authMachine });
90
+ const authMachine = authSetup.createMachine(
91
+ formatPlayRouteTransitions({
92
+ id: "auth",
93
+ initial: "home",
94
+ context: ({ input }) => ({
95
+ isAuthenticated: input?.isAuthenticated ?? false,
96
+ username: input?.username ?? null,
97
+ params: input?.params ?? {},
98
+ query: input?.query ?? {},
99
+ }),
100
+ states: {
101
+ home: { id: "home", meta: { route: "/" } },
102
+ about: { id: "about", meta: { route: "/about" } },
103
+ login: { id: "login", meta: { route: "/login" } },
104
+ profile: { id: "profile", meta: { route: "/profile/:username" } },
105
+ },
106
+ }),
107
+ );
128
108
  ```
129
109
 
130
- ### Using the Actor
110
+ `formatPlayRouteTransitions` generates root-level handlers equivalent to:
131
111
 
132
112
  ```typescript
133
- // Create and start actor
134
- const actor = createPlayer();
135
- actor.start();
113
+ on: {
114
+ "play.route": [
115
+ { target: ".home", guard: ({ event }) => event.to === "#home", reenter: true, actions: assign({ params, query }) },
116
+ { target: ".about", guard: ({ event }) => event.to === "#about", reenter: true, actions: assign({ params, query }) },
117
+ { target: ".login", guard: ({ event }) => event.to === "#login", reenter: true, actions: assign({ params, query }) },
118
+ { target: ".profile", guard: ({ event }) => event.to === "#profile", reenter: true, actions: assign({ params, query }) },
119
+ ],
120
+ }
121
+ ```
122
+
123
+ ### `play.route` Events — Navigation
136
124
 
137
- // Login (transitions to profile)
138
- actor.send({ type: "auth.login", userId: "user123" });
125
+ To navigate, send a `play.route` event with `to: "#stateId"`:
126
+
127
+ ```typescript
128
+ // Navigate to home
129
+ actor.send({ type: "play.route", to: "#home" });
139
130
 
140
- // Navigate to different profile with parameters
131
+ // Navigate to profile params are stored in context and used to resolve the URL
141
132
  actor.send({
142
133
  type: "play.route",
143
- to: "/profile/user456",
144
- params: { userId: "user456" },
134
+ to: "#profile",
135
+ params: { username: "alice" },
145
136
  });
146
137
 
147
- // Parameter extracted and available in view
148
- const view = actor.currentView.get();
149
- console.log(view.userId); // 'user456'
138
+ // actor.currentRoute.get() "/profile/alice"
139
+
140
+ // Navigate with query string
141
+ actor.send({
142
+ type: "play.route",
143
+ to: "#settings",
144
+ params: { section: "privacy" },
145
+ query: { tab: "advanced" },
146
+ });
150
147
  ```
151
148
 
152
- ## Pattern Matching for Dynamic Routes
149
+ **`to` always uses `"#stateId"` format** — the state's `id` field prefixed with `#`. Do not pass URL paths here.
153
150
 
154
- Routes with parameters use pattern matching to resolve URL paths:
151
+ ### `always` Guards Protected Routes
155
152
 
156
- - `/profile/:userId` matches `/profile/user123`, `/profile/alice`, etc.
157
- - `/settings/:section?` matches `/settings` (no section) or `/settings/privacy`
153
+ Use XState `always` transitions to protect states. If the guard fires, the machine redirects _before_ the state is fully entered:
158
154
 
159
- The router extracts parameters from the URL and passes them in `play.route` events, which the machine stores in context for view projection.
155
+ ```typescript
156
+ dashboard: {
157
+ id: "dashboard",
158
+ meta: { route: "/dashboard" },
159
+ always: {
160
+ // If not authenticated, redirect to login immediately
161
+ guard: ({ context }) => !context.isAuthenticated,
162
+ target: "login",
163
+ },
164
+ },
165
+ profile: {
166
+ id: "profile",
167
+ meta: { route: "/profile/:username" },
168
+ always: {
169
+ guard: ({ context }) => !context.isAuthenticated,
170
+ target: "login",
171
+ },
172
+ },
173
+ ```
160
174
 
161
- ## Guard Placement Architecture
175
+ **Why `always` and not event guards?** Guards on events check "can I TAKE this transition?". `always` guards check "can I BE in this state?" — the correct invariant for authentication. The actor enforces the guard even on direct URL access (browser back/forward or deep link), because the router sends a `play.route` event which triggers the `always` guard.
162
176
 
163
- **Core Principle:** Guards check if you can BE in a state (state entry), not if you can TAKE an event (event handlers).
177
+ ### Root-Level Event Handlers
164
178
 
165
- ### Pattern 1: Using formatPlayRouteTransitions (RECOMMENDED)
179
+ Domain events placed at the root `on:` level are handled from any state:
166
180
 
167
181
  ```typescript
168
- import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
169
-
170
- const machineConfig = {
171
- initial: "home",
172
- context: { isAuthenticated: false },
173
- states: {
174
- home: { id: "home", meta: { route: "/" } },
175
- profile: {
176
- id: "profile",
177
- meta: { route: "/profile/:userId" },
178
- // Always-guard validates state entry
179
- always: [
180
- {
181
- target: "login",
182
- guard: ({ context }) => !context.isAuthenticated,
183
- },
184
- ],
182
+ const authMachine = authSetup.createMachine(
183
+ formatPlayRouteTransitions({
184
+ // ...
185
+ on: {
186
+ "auth.login": {
187
+ target: ".dashboard",
188
+ guard: ({ context }) => !context.isAuthenticated,
189
+ actions: authSetup.assign({
190
+ isAuthenticated: true,
191
+ username: ({ event }) => (event.type === "auth.login" ? event.username : null),
192
+ }),
193
+ },
194
+ "auth.logout": {
195
+ target: ".home",
196
+ guard: ({ context }) => context.isAuthenticated,
197
+ actions: authSetup.assign({
198
+ isAuthenticated: false,
199
+ username: null,
200
+ }),
201
+ },
185
202
  },
186
- },
187
- };
188
-
189
- // Utility handles routing infrastructure
190
- const machine = createMachine(formatPlayRouteTransitions(machineConfig));
203
+ states: {
204
+ /* ... */
205
+ },
206
+ }),
207
+ );
191
208
  ```
192
209
 
193
- **Why this works:**
210
+ ## Complete Actor Usage
211
+
212
+ ```typescript
213
+ const createPlayer = definePlayer({ machine: authMachine });
214
+ const actor = createPlayer();
215
+ actor.start();
216
+
217
+ // Initial state
218
+ console.log(actor.currentRoute.get()); // "/"
194
219
 
195
- - `formatPlayRouteTransitions` adds routing infrastructure (event.to matching)
196
- - Always-guards handle business logic (authentication, authorization)
197
- - Clear separation: routing layer (infrastructure) vs validation layer (business logic)
220
+ // Navigate to about
221
+ actor.send({ type: "play.route", to: "#about" });
222
+ console.log(actor.currentRoute.get()); // "/about"
198
223
 
199
- ### Pattern 2: Manual Always-Guards (Advanced)
224
+ // Attempt protected route (redirect fires because isAuthenticated=false)
225
+ actor.send({ type: "play.route", to: "#dashboard" });
226
+ console.log(actor.getSnapshot().value); // "login" — guard redirected
227
+
228
+ // Login first
229
+ actor.send({ type: "auth.login", username: "alice" });
230
+ // auth.login root handler transitions to dashboard
231
+ console.log(actor.getSnapshot().value); // "dashboard"
232
+ console.log(actor.getSnapshot().context.username); // "alice"
233
+
234
+ // Navigate to profile with params
235
+ actor.send({ type: "play.route", to: "#profile", params: { username: "alice" } });
236
+ console.log(actor.currentRoute.get()); // "/profile/alice"
237
+
238
+ actor.stop();
239
+ ```
200
240
 
201
- For advanced users who need full control:
241
+ ## Vanilla Router Setup
242
+
243
+ To sync the browser URL with `actor.currentRoute`, use `@xmachines/play-dom-router`:
202
244
 
203
245
  ```typescript
204
- const machine = createMachine({
205
- context: { intendedRoute: null, isAuthenticated: false },
206
- on: {
207
- "play.route": {
208
- actions: assign({
209
- intendedRoute: ({ event }) => event.to,
210
- }),
211
- },
212
- },
213
- always: [
214
- {
215
- target: "profile",
216
- guard: ({ context }) => context.intendedRoute === "#profile" && context.isAuthenticated,
217
- reenter: true,
218
- },
219
- {
220
- target: "login",
221
- guard: ({ context }) =>
222
- context.intendedRoute === "#profile" && !context.isAuthenticated,
223
- },
224
- ],
246
+ import {
247
+ createBrowserHistory,
248
+ createRouter,
249
+ connectRouter,
250
+ createRouteMap,
251
+ } from "@xmachines/play-dom-router";
252
+ import { extractMachineRoutes } from "@xmachines/play-router";
253
+
254
+ const routeTree = extractMachineRoutes(authMachine);
255
+ const routeMap = createRouteMap(authMachine);
256
+
257
+ const history = createBrowserHistory({ window });
258
+ const router = createRouter({ routeTree, history });
259
+
260
+ // connectRouter handles all bidirectional sync:
261
+ // - actor.currentRoute → browser URL
262
+ // - browser URL changes → play.route event to actor
263
+ const disconnect = connectRouter({ actor, router, routeMap });
264
+
265
+ // cleanup on unload
266
+ window.addEventListener("beforeunload", () => {
267
+ disconnect();
268
+ router.destroy();
225
269
  });
226
270
  ```
227
271
 
228
- ### Anti-Pattern: Guards on Events
272
+ ## React Router Setup
229
273
 
230
- **NEVER do this:**
274
+ For React, use `@xmachines/play-react-router` or `@xmachines/play-tanstack-react-router` with the `PlayRouterProvider` and `createRouteMapFromTree`:
231
275
 
232
276
  ```typescript
233
- // WRONG - Guard on event checking event property
234
- on: {
235
- 'play.route': [
236
- {
237
- guard: ({ event }) => event.to === "#dashboard",
238
- target: "dashboard"
239
- }
240
- ]
277
+ import { extractMachineRoutes } from "@xmachines/play-router";
278
+ import { PlayRouterProvider, createRouteMapFromTree } from "@xmachines/play-tanstack-react-router";
279
+
280
+ const routeTree = extractMachineRoutes(authMachine);
281
+ const routeMap = createRouteMapFromTree(routeTree);
282
+
283
+ function App() {
284
+ return (
285
+ <PlayRouterProvider
286
+ actor={actor}
287
+ router={router}
288
+ routeMap={routeMap}
289
+ renderer={(currentActor, currentRouter) => (
290
+ <Shell actor={currentActor} router={currentRouter} registry={registry} />
291
+ )}
292
+ />
293
+ );
241
294
  }
242
295
  ```
243
296
 
244
- **Why this is wrong:** The state doesn't care HOW it was entered (which event triggered it). Guards should validate state invariants: "Can I BE in this state given current context?" not "Can I TAKE this event?"
245
-
246
- **Correct approach:** Use `formatPlayRouteTransitions` for routing infrastructure, use always-guards for state validation.
247
-
248
- ### Actor Authority in Action
297
+ ## Architectural Invariants
249
298
 
250
- This demonstrates **Actor Authority (INV-01)** — the state machine controls navigation through guards, not the router. If a guard returns `false`, the transition is rejected and the URL doesn't change.
299
+ | Invariant | Description |
300
+ | ----------------------------------- | ---------------------------------------------------------------------------------------------- |
301
+ | **Actor Authority (INV-01)** | Guards on the machine validate every navigation. The router cannot change state directly. |
302
+ | **Passive Infrastructure (INV-04)** | The router observes `actor.currentRoute` — it never decides where to go. |
303
+ | **State-Driven Reset (INV-03)** | Browser back/forward sends `play.route` events to the actor. History is driven by actor state. |
304
+ | **Strict Separation (INV-02)** | The machine has zero framework imports. Guards, actions, and context are pure TypeScript. |
251
305
 
252
306
  ## Next Steps
253
307
 
254
- - **[Multi-Router Integration](./multi-router-integration.md)** - Learn about renderer prop pattern
255
- - **[Examples Index](./README.md)** - Complete catalog of runnable demos and example guides
308
+ - **[Multi-Router Integration](./multi-router-integration.md)** All 8 router adapters and the two integration patterns
309
+ - **[Examples Index](./README.md)** Complete catalog of runnable demos
256
310
 
257
311
  ## Learn More
258
312
 
259
- - [XState Routes Documentation](https://stately.ai/docs/routes) - Official Stately routing patterns
260
- - [Play RFC](../rfc/play.md) - Complete architectural specification
261
- - [play-router README](../api/@xmachines/play-router/README.md) - Route extraction and tree building
313
+ - [Play RFC](../rfc/play.md) Complete architectural specification
314
+ - [play-router README](../api/@xmachines/play-router/README.md) Route extraction and tree building
315
+ - [play-dom-router README](../api/@xmachines/play-dom-router/README.md) Vanilla DOM bindings