@xmachines/docs 1.0.0-beta.10
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.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/api/@xmachines/play/README.md +130 -0
- package/api/@xmachines/play/type-aliases/PlayEvent.md +81 -0
- package/api/@xmachines/play-actor/README.md +247 -0
- package/api/@xmachines/play-actor/classes/AbstractActor.md +520 -0
- package/api/@xmachines/play-actor/interfaces/Routable.md +29 -0
- package/api/@xmachines/play-actor/interfaces/ViewMetadata.md +17 -0
- package/api/@xmachines/play-actor/interfaces/Viewable.md +12 -0
- package/api/@xmachines/play-catalog/README.md +331 -0
- package/api/@xmachines/play-catalog/functions/defineCatalog.md +98 -0
- package/api/@xmachines/play-catalog/functions/defineComponents.md +134 -0
- package/api/@xmachines/play-catalog/type-aliases/Catalog.md +48 -0
- package/api/@xmachines/play-catalog/type-aliases/ComponentsFor.md +20 -0
- package/api/@xmachines/play-catalog/type-aliases/InferComponentProps.md +65 -0
- package/api/@xmachines/play-catalog/type-aliases/NoExtraKeys.md +17 -0
- package/api/@xmachines/play-react/README.md +423 -0
- package/api/@xmachines/play-react/classes/PlayErrorBoundary.md +613 -0
- package/api/@xmachines/play-react/functions/useSignalEffect.md +68 -0
- package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryProps.md +15 -0
- package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryState.md +14 -0
- package/api/@xmachines/play-react/interfaces/PlayRendererProps.md +15 -0
- package/api/@xmachines/play-react/variables/PlayRenderer.md +64 -0
- package/api/@xmachines/play-react-router/README.md +198 -0
- package/api/@xmachines/play-react-router/classes/ReactRouterBridge.md +321 -0
- package/api/@xmachines/play-react-router/classes/RouteMap.md +137 -0
- package/api/@xmachines/play-react-router/functions/PlayRouterProvider.md +19 -0
- package/api/@xmachines/play-react-router/functions/createRouteMapFromTree.md +35 -0
- package/api/@xmachines/play-react-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-react-router/interfaces/PlayRouterProviderProps.md +14 -0
- package/api/@xmachines/play-react-router/interfaces/RouteMapping.md +17 -0
- package/api/@xmachines/play-react-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-react-router-demo/README.md +137 -0
- package/api/@xmachines/play-router/README.md +502 -0
- package/api/@xmachines/play-router/classes/BaseRouteMap.md +142 -0
- package/api/@xmachines/play-router/classes/RouterBridgeBase.md +300 -0
- package/api/@xmachines/play-router/functions/buildRouteTree.md +27 -0
- package/api/@xmachines/play-router/functions/connectRouter.md +67 -0
- package/api/@xmachines/play-router/functions/crawlMachine.md +92 -0
- package/api/@xmachines/play-router/functions/createBrowserHistory.md +47 -0
- package/api/@xmachines/play-router/functions/createRouteMap.md +53 -0
- package/api/@xmachines/play-router/functions/createRouter.md +76 -0
- package/api/@xmachines/play-router/functions/detectDuplicateRoutes.md +32 -0
- package/api/@xmachines/play-router/functions/extractMachineRoutes.md +64 -0
- package/api/@xmachines/play-router/functions/extractRoute.md +45 -0
- package/api/@xmachines/play-router/functions/findRouteById.md +37 -0
- package/api/@xmachines/play-router/functions/findRouteByPath.md +39 -0
- package/api/@xmachines/play-router/functions/getNavigableRoutes.md +35 -0
- package/api/@xmachines/play-router/functions/getRoutableRoutes.md +39 -0
- package/api/@xmachines/play-router/functions/routeExists.md +26 -0
- package/api/@xmachines/play-router/functions/validateRouteFormat.md +29 -0
- package/api/@xmachines/play-router/functions/validateStateExists.md +29 -0
- package/api/@xmachines/play-router/interfaces/BaseRouteMapping.md +27 -0
- package/api/@xmachines/play-router/interfaces/BrowserHistory.md +172 -0
- package/api/@xmachines/play-router/interfaces/BrowserWindow.md +69 -0
- package/api/@xmachines/play-router/interfaces/ConnectRouterOptions.md +13 -0
- package/api/@xmachines/play-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-router/interfaces/RouteInfo.md +19 -0
- package/api/@xmachines/play-router/interfaces/RouteMap.md +56 -0
- package/api/@xmachines/play-router/interfaces/RouteNode.md +21 -0
- package/api/@xmachines/play-router/interfaces/RouteObject.md +21 -0
- package/api/@xmachines/play-router/interfaces/RouteTree.md +20 -0
- package/api/@xmachines/play-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-router/interfaces/StateVisit.md +15 -0
- package/api/@xmachines/play-router/interfaces/VanillaRouter.md +28 -0
- package/api/@xmachines/play-router/type-aliases/RouteMetadata.md +11 -0
- package/api/@xmachines/play-router-demo/README.md +137 -0
- package/api/@xmachines/play-signals/README.md +176 -0
- package/api/@xmachines/play-signals/interfaces/ComputedOptions.md +34 -0
- package/api/@xmachines/play-signals/interfaces/SignalComputed.md +49 -0
- package/api/@xmachines/play-signals/interfaces/SignalOptions.md +35 -0
- package/api/@xmachines/play-signals/interfaces/SignalState.md +68 -0
- package/api/@xmachines/play-signals/interfaces/SignalWatcher.md +97 -0
- package/api/@xmachines/play-signals/namespaces/Signal/README.md +22 -0
- package/api/@xmachines/play-signals/namespaces/Signal/classes/Computed.md +52 -0
- package/api/@xmachines/play-signals/namespaces/Signal/classes/State.md +72 -0
- package/api/@xmachines/play-signals/namespaces/Signal/interfaces/Options.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/README.md +21 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/classes/Watcher.md +85 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/currentComputed.md +13 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/hasSinks.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/hasSources.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/introspectSinks.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/introspectSources.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/untrack.md +25 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/variables/unwatched.md +9 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/variables/watched.md +9 -0
- package/api/@xmachines/play-signals/namespaces/Signal/variables/isComputed.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/variables/isState.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/variables/isWatcher.md +19 -0
- package/api/@xmachines/play-signals/type-aliases/WatcherNotify.md +32 -0
- package/api/@xmachines/play-solid/README.md +311 -0
- package/api/@xmachines/play-solid/interfaces/PlayRendererProps.md +15 -0
- package/api/@xmachines/play-solid/variables/PlayRenderer.md +70 -0
- package/api/@xmachines/play-solid-router/README.md +666 -0
- package/api/@xmachines/play-solid-router/classes/RouteMap.md +150 -0
- package/api/@xmachines/play-solid-router/classes/SolidRouterBridge.md +347 -0
- package/api/@xmachines/play-solid-router/functions/PlayRouterProvider.md +19 -0
- package/api/@xmachines/play-solid-router/functions/createRouteMap.md +32 -0
- package/api/@xmachines/play-solid-router/interfaces/AbstractActor.md +486 -0
- package/api/@xmachines/play-solid-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-solid-router/interfaces/PlayRouterProviderProps.md +14 -0
- package/api/@xmachines/play-solid-router/interfaces/RouteMapping.md +14 -0
- package/api/@xmachines/play-solid-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-solid-router/type-aliases/RoutableActor.md +9 -0
- package/api/@xmachines/play-solid-router/type-aliases/SolidRouterHooks.md +51 -0
- package/api/@xmachines/play-solid-router-demo/README.md +127 -0
- package/api/@xmachines/play-tanstack-react-router/README.md +226 -0
- package/api/@xmachines/play-tanstack-react-router/classes/RouteMap.md +137 -0
- package/api/@xmachines/play-tanstack-react-router/classes/TanStackReactRouterBridge.md +348 -0
- package/api/@xmachines/play-tanstack-react-router/functions/PlayRouterProvider.md +19 -0
- package/api/@xmachines/play-tanstack-react-router/functions/createRouteMap.md +53 -0
- package/api/@xmachines/play-tanstack-react-router/functions/createRouteMapFromTree.md +35 -0
- package/api/@xmachines/play-tanstack-react-router/functions/extractParams.md +38 -0
- package/api/@xmachines/play-tanstack-react-router/functions/extractQueryParams.md +33 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/PlayRouterProviderProps.md +14 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/RouteMapping.md +17 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/RouteNavigateEvent.md +26 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterInstance.md +9 -0
- package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterLike.md +78 -0
- package/api/@xmachines/play-tanstack-react-router/variables/extractMachineRoutes.md +64 -0
- package/api/@xmachines/play-tanstack-react-router-demo/README.md +126 -0
- package/api/@xmachines/play-tanstack-solid-router/README.md +285 -0
- package/api/@xmachines/play-tanstack-solid-router/classes/RouteMap.md +150 -0
- package/api/@xmachines/play-tanstack-solid-router/classes/SolidRouterBridge.md +343 -0
- package/api/@xmachines/play-tanstack-solid-router/functions/PlayRouterProvider.md +19 -0
- package/api/@xmachines/play-tanstack-solid-router/functions/createRouteMap.md +32 -0
- package/api/@xmachines/play-tanstack-solid-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-tanstack-solid-router/interfaces/PlayRouterProviderProps.md +14 -0
- package/api/@xmachines/play-tanstack-solid-router/interfaces/RouteMapping.md +23 -0
- package/api/@xmachines/play-tanstack-solid-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/RoutableActor.md +9 -0
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterInstance.md +9 -0
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterLike.md +78 -0
- package/api/@xmachines/play-tanstack-solid-router-demo/README.md +126 -0
- package/api/@xmachines/play-vue/README.md +292 -0
- package/api/@xmachines/play-vue/interfaces/PlayRendererProps.md +14 -0
- package/api/@xmachines/play-vue/variables/PlayRenderer.md +9 -0
- package/api/@xmachines/play-vue-router/README.md +604 -0
- package/api/@xmachines/play-vue-router/classes/RouteMap.md +209 -0
- package/api/@xmachines/play-vue-router/classes/VueBaseRouteMap.md +201 -0
- package/api/@xmachines/play-vue-router/classes/VueRouterBridge.md +360 -0
- package/api/@xmachines/play-vue-router/functions/createRouteMap.md +19 -0
- package/api/@xmachines/play-vue-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-vue-router/interfaces/RouteMapping.md +15 -0
- package/api/@xmachines/play-vue-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-vue-router/type-aliases/RoutableActor.md +9 -0
- package/api/@xmachines/play-vue-router/variables/PlayRouterProvider.md +67 -0
- package/api/@xmachines/play-vue-router-demo/README.md +133 -0
- package/api/@xmachines/play-xstate/README.md +512 -0
- package/api/@xmachines/play-xstate/classes/PlayerActor.md +527 -0
- package/api/@xmachines/play-xstate/functions/buildRouteUrl.md +43 -0
- package/api/@xmachines/play-xstate/functions/composeGuards.md +79 -0
- package/api/@xmachines/play-xstate/functions/composeGuardsOr.md +67 -0
- package/api/@xmachines/play-xstate/functions/definePlayer.md +127 -0
- package/api/@xmachines/play-xstate/functions/deriveRoute.md +109 -0
- package/api/@xmachines/play-xstate/functions/eventMatches.md +40 -0
- package/api/@xmachines/play-xstate/functions/formatPlayRouteTransitions.md +54 -0
- package/api/@xmachines/play-xstate/functions/hasContext.md +42 -0
- package/api/@xmachines/play-xstate/functions/isAbsoluteRoute.md +41 -0
- package/api/@xmachines/play-xstate/functions/mergeViewProps.md +26 -0
- package/api/@xmachines/play-xstate/functions/negateGuard.md +61 -0
- package/api/@xmachines/play-xstate/functions/stateMatches.md +25 -0
- package/api/@xmachines/play-xstate/functions/validateComponentBinding.md +39 -0
- package/api/@xmachines/play-xstate/functions/validateViewProps.md +80 -0
- package/api/@xmachines/play-xstate/interfaces/CatalogEntry.md +16 -0
- package/api/@xmachines/play-xstate/interfaces/PlayerConfig.md +24 -0
- package/api/@xmachines/play-xstate/interfaces/PlayerOptions.md +26 -0
- package/api/@xmachines/play-xstate/interfaces/RouteContext.md +22 -0
- package/api/@xmachines/play-xstate/type-aliases/Catalog.md +21 -0
- package/api/@xmachines/play-xstate/type-aliases/ComposedGuard.md +14 -0
- package/api/@xmachines/play-xstate/type-aliases/Guard.md +34 -0
- package/api/@xmachines/play-xstate/type-aliases/GuardArray.md +20 -0
- package/api/@xmachines/play-xstate/type-aliases/PlayerFactory.md +29 -0
- package/api/@xmachines/play-xstate/type-aliases/RouteMachineConfig.md +45 -0
- package/api/@xmachines/play-xstate/type-aliases/RouteStateNode.md +51 -0
- package/api/@xmachines/play-xstate/type-aliases/ValidationResult.md +17 -0
- package/api/@xmachines/play-xstate/type-aliases/ViewMergeContext.md +35 -0
- package/api/@xmachines/shared/README.md +379 -0
- package/api/@xmachines/shared/functions/defineXmVitestConfig.md +29 -0
- package/api/@xmachines/shared/functions/xmAliases.md +24 -0
- package/api/README.md +25 -0
- package/api/llms.txt +26 -0
- package/examples/README.md +63 -0
- package/examples/basic-state-machine.md +70 -0
- package/examples/form-validation.md +167 -0
- package/examples/multi-router-integration.md +277 -0
- package/examples/routing-patterns.md +260 -0
- package/examples/traffic-light.md +99 -0
- package/guides/README.md +29 -0
- package/guides/getting-started.md +223 -0
- package/guides/installation.md +323 -0
- package/index.d.ts +3 -0
- package/index.js +4 -0
- package/package.json +54 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Form Validation State Machine
|
|
2
|
+
|
|
3
|
+
Managing form state with validation logic using guards and conditional transitions.
|
|
4
|
+
|
|
5
|
+
## Use Case
|
|
6
|
+
|
|
7
|
+
This example demonstrates a form with idle → validating → valid/invalid states. It's applicable to:
|
|
8
|
+
|
|
9
|
+
- Login and signup forms with validation
|
|
10
|
+
- Multi-step form wizards
|
|
11
|
+
- Data entry forms with complex validation rules
|
|
12
|
+
- Any UI requiring state-driven validation feedback
|
|
13
|
+
|
|
14
|
+
## Complete Code
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { setup } from "xstate";
|
|
18
|
+
|
|
19
|
+
// Define context
|
|
20
|
+
interface FormContext {
|
|
21
|
+
// Context would store form data and validation state
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Define events
|
|
25
|
+
type FormEvent = { type: "SUBMIT"; value: string } | { type: "RESET" };
|
|
26
|
+
|
|
27
|
+
// Create machine
|
|
28
|
+
const formMachine = setup({
|
|
29
|
+
types: {
|
|
30
|
+
context: {} as FormContext,
|
|
31
|
+
events: {} as FormEvent,
|
|
32
|
+
},
|
|
33
|
+
}).createMachine({
|
|
34
|
+
id: "form",
|
|
35
|
+
initial: "idle",
|
|
36
|
+
states: {
|
|
37
|
+
idle: {
|
|
38
|
+
on: {
|
|
39
|
+
SUBMIT: "validating",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
validating: {
|
|
43
|
+
on: {
|
|
44
|
+
SUBMIT: [
|
|
45
|
+
{
|
|
46
|
+
target: "valid",
|
|
47
|
+
cond: (event) => event.value.length >= 3,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
target: "invalid",
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
valid: {
|
|
56
|
+
on: {
|
|
57
|
+
RESET: "idle",
|
|
58
|
+
SUBMIT: "validating",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
invalid: {
|
|
62
|
+
on: {
|
|
63
|
+
RESET: "idle",
|
|
64
|
+
SUBMIT: "validating",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Usage
|
|
71
|
+
let state = formMachine.initialState;
|
|
72
|
+
console.log(state); // 'idle'
|
|
73
|
+
|
|
74
|
+
// Submit with invalid input
|
|
75
|
+
state = formMachine.transition(state, { type: "SUBMIT", value: "ab" });
|
|
76
|
+
console.log(state); // 'validating'
|
|
77
|
+
|
|
78
|
+
state = formMachine.transition(state, { type: "SUBMIT", value: "ab" });
|
|
79
|
+
console.log(state); // 'invalid' (value too short)
|
|
80
|
+
|
|
81
|
+
// Reset and try valid input
|
|
82
|
+
state = formMachine.transition(state, { type: "RESET" });
|
|
83
|
+
console.log(state); // 'idle'
|
|
84
|
+
|
|
85
|
+
state = formMachine.transition(state, { type: "SUBMIT", value: "abc" });
|
|
86
|
+
console.log(state); // 'validating'
|
|
87
|
+
|
|
88
|
+
state = formMachine.transition(state, { type: "SUBMIT", value: "abc" });
|
|
89
|
+
console.log(state); // 'valid' (meets minimum length)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Code Explanation
|
|
93
|
+
|
|
94
|
+
1. **Guards (conditional transitions)** - The `cond` property defines a function that determines which transition to take. If the condition returns `true`, that transition is used; otherwise, the next transition in the array is tried.
|
|
95
|
+
|
|
96
|
+
2. **Event payloads** - Events can carry data (`value: string`), allowing validation logic to inspect the actual input being validated.
|
|
97
|
+
|
|
98
|
+
3. **Multiple transitions per event** - When an event can lead to different states based on conditions, use an array of transition objects with guards.
|
|
99
|
+
|
|
100
|
+
4. **Validation logic** - Business rules (like minimum length) are encoded as guard functions, keeping validation logic co-located with state definitions.
|
|
101
|
+
|
|
102
|
+
## Key Concepts
|
|
103
|
+
|
|
104
|
+
- **Guards**: Functions that conditionally allow or prevent transitions based on event data or current state
|
|
105
|
+
- **Event payloads**: Events can carry data beyond just the event type
|
|
106
|
+
- **Conditional transitions**: Array of transitions where the first matching guard is taken
|
|
107
|
+
- **State-driven UI**: UI can render different feedback based on state (loading spinner in `validating`, error message in `invalid`)
|
|
108
|
+
|
|
109
|
+
## Advanced Pattern: Multiple Validation Rules
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
const advancedFormMachine = createMachine<FormState, FormEvent>({
|
|
113
|
+
id: "advancedForm",
|
|
114
|
+
initial: "idle",
|
|
115
|
+
states: {
|
|
116
|
+
idle: {
|
|
117
|
+
on: { SUBMIT: "validating" },
|
|
118
|
+
},
|
|
119
|
+
validating: {
|
|
120
|
+
on: {
|
|
121
|
+
SUBMIT: [
|
|
122
|
+
{
|
|
123
|
+
target: "valid",
|
|
124
|
+
cond: (event) => {
|
|
125
|
+
const value = event.value;
|
|
126
|
+
return (
|
|
127
|
+
value.length >= 3 &&
|
|
128
|
+
value.length <= 20 &&
|
|
129
|
+
/^[a-zA-Z0-9]+$/.test(value)
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{ target: "invalid" },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
valid: {
|
|
138
|
+
on: {
|
|
139
|
+
RESET: "idle",
|
|
140
|
+
SUBMIT: "validating",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
invalid: {
|
|
144
|
+
on: {
|
|
145
|
+
RESET: "idle",
|
|
146
|
+
SUBMIT: "validating",
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Extending for Real Forms
|
|
154
|
+
|
|
155
|
+
For production forms, you might add:
|
|
156
|
+
|
|
157
|
+
- **Context**: Store error messages and validation details
|
|
158
|
+
- **Actions**: Side effects like API calls or analytics tracking
|
|
159
|
+
- **Nested states**: Break down `validating` into `checkingFormat`, `checkingUniqueness`, etc.
|
|
160
|
+
- **History states**: Return to the last valid/invalid state after interruption
|
|
161
|
+
|
|
162
|
+
## Next Steps
|
|
163
|
+
|
|
164
|
+
- **[Basic State Machine](basic-state-machine.md)** - Review foundational concepts
|
|
165
|
+
- **[Traffic Light Example](traffic-light.md)** - See cyclic state transitions
|
|
166
|
+
- **[API Documentation](/docs/api/xmachines/play/readme)** - Learn about context, actions, and nested states
|
|
167
|
+
- **[Architecture Guides](/docs/api/guides/core-concepts)** - Understand design patterns and best practices
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# Multi-Router Integration - Renderer Prop Pattern
|
|
2
|
+
|
|
3
|
+
Learn how to integrate Play Architecture with three router modes using the unified renderer prop pattern introduced in
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
introduces a unified multi-router architecture supporting three integration modes with consistent API:
|
|
8
|
+
|
|
9
|
+
1. **TanStack Router** - React + TanStack Router with full feature set
|
|
10
|
+
2. **Vanilla Router** - JSX frameworks (Preact, Solid, Vue) with framework-agnostic router
|
|
11
|
+
3. **Pure Browser** - Manual integration for jQuery, Alpine, vanilla JS
|
|
12
|
+
|
|
13
|
+
All modes use the **renderer prop pattern** where providers receive an `actor` and `router`, then pass a `renderer` function (not children).
|
|
14
|
+
|
|
15
|
+
## Key Concepts
|
|
16
|
+
|
|
17
|
+
### 1. Renderer Prop Pattern
|
|
18
|
+
|
|
19
|
+
All providers use a `renderer` prop that receives the actor and returns a ReactNode:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
<PlayTanStackRouterProvider
|
|
23
|
+
actor={actor}
|
|
24
|
+
router={router}
|
|
25
|
+
renderer={(actor) => <PlayRenderer actor={actor} components={components} />}
|
|
26
|
+
/>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Why renderer prop instead of children?**
|
|
30
|
+
|
|
31
|
+
- Explicit dependency injection (actor passed to renderer function)
|
|
32
|
+
- Type-safe integration (renderer function signature enforced)
|
|
33
|
+
- Consistent pattern across all three modes
|
|
34
|
+
|
|
35
|
+
### 2. RouteMap as Explicit Prop
|
|
36
|
+
|
|
37
|
+
All providers require `routeMap` as an explicit prop. Routers fundamentally don't know about Play state IDs — the map bridges the gap:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const routeTree = createPlayRouter({ machine });
|
|
41
|
+
const router = createRouter({ routeTree, history: createMemoryHistory() });
|
|
42
|
+
|
|
43
|
+
<PlayTanStackRouterProvider
|
|
44
|
+
actor={actor}
|
|
45
|
+
router={router}
|
|
46
|
+
routeMap={routeMap} // Explicit mapping
|
|
47
|
+
renderer={(actor) => <PlayRenderer actor={actor} components={components} />}
|
|
48
|
+
/>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Provider Composition Pattern
|
|
52
|
+
|
|
53
|
+
Providers wrap external providers (like TanStack's `RouterProvider`) and add Play integration layer:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Provider wraps TanStack RouterProvider
|
|
57
|
+
<RouterProvider router={router}>
|
|
58
|
+
{/* Play integration layer */}
|
|
59
|
+
{renderer(actor)}
|
|
60
|
+
</RouterProvider>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Mode 1: TanStack Router (React)
|
|
64
|
+
|
|
65
|
+
Complete integration with React and TanStack Router.
|
|
66
|
+
|
|
67
|
+
See [multi-router-example.ts](./src/multi-router-example.ts) for runnable example.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { StrictMode } from 'react';
|
|
71
|
+
import { createRoot } from 'react-dom/client';
|
|
72
|
+
import { createRouter, createMemoryHistory } from '@tanstack/react-router';
|
|
73
|
+
import { createPlayRouter, PlayTanStackRouterProvider } from '@xmachines/play-tanstack-react-router';
|
|
74
|
+
import { PlayRenderer } from '@xmachines/play-react';
|
|
75
|
+
import { definePlayer } from '@xmachines/play-xstate';
|
|
76
|
+
|
|
77
|
+
// 1. Create actor
|
|
78
|
+
const createPlayer = definePlayer({ machine, catalog });
|
|
79
|
+
const actor = createPlayer();
|
|
80
|
+
|
|
81
|
+
// 2. Create router from machine
|
|
82
|
+
const routeTree = createPlayRouter({ machine });
|
|
83
|
+
const router = createRouter({
|
|
84
|
+
routeTree,
|
|
85
|
+
history: createMemoryHistory()
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// 3. Define React components
|
|
89
|
+
const components = {
|
|
90
|
+
HomeView: ({ send }) => (
|
|
91
|
+
<div>
|
|
92
|
+
<h1>Home</h1>
|
|
93
|
+
<button onClick={() => send({ type: 'play.route', to: '/about' })}>
|
|
94
|
+
Go to About
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
),
|
|
98
|
+
AboutView: ({ send }) => (
|
|
99
|
+
<div>
|
|
100
|
+
<h1>About</h1>
|
|
101
|
+
<button onClick={() => send({ type: 'play.route', to: '/' })}>
|
|
102
|
+
Go to Home
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// 4. Render with provider + renderer prop
|
|
109
|
+
function App() {
|
|
110
|
+
return (
|
|
111
|
+
<PlayTanStackRouterProvider
|
|
112
|
+
actor={actor}
|
|
113
|
+
router={router}
|
|
114
|
+
renderer={(actor) => <PlayRenderer actor={actor} components={components} />}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const root = createRoot(document.getElementById('app')!);
|
|
120
|
+
root.render(<StrictMode><App /></StrictMode>);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Benefits
|
|
124
|
+
|
|
125
|
+
- Full React ecosystem support
|
|
126
|
+
- Type-safe routing with TanStack Router
|
|
127
|
+
- Browser history integration automatic
|
|
128
|
+
- Signal reactivity via `PlayRenderer`
|
|
129
|
+
|
|
130
|
+
## Mode 2: Vanilla Router (Preact, Solid, Vue)
|
|
131
|
+
|
|
132
|
+
Framework-agnostic router for JSX-based frameworks.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { createPlayRouter, connectRouter } from '@xmachines/play-router';
|
|
136
|
+
import { definePlayer } from '@xmachines/play-xstate';
|
|
137
|
+
|
|
138
|
+
// 1. Create actor
|
|
139
|
+
const createPlayer = definePlayer({ machine, catalog });
|
|
140
|
+
const actor = createPlayer();
|
|
141
|
+
|
|
142
|
+
// 2. Create framework-agnostic router
|
|
143
|
+
const router = createPlayRouter({ machine });
|
|
144
|
+
|
|
145
|
+
// 3. Connect router with manual integration
|
|
146
|
+
const disconnect = connectRouter(actor, {
|
|
147
|
+
onNavigate: (path) => {
|
|
148
|
+
// Update your framework's router
|
|
149
|
+
myFrameworkRouter.push(path);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// 4. Framework-specific rendering
|
|
154
|
+
// Preact:
|
|
155
|
+
import { h } from 'preact';
|
|
156
|
+
const view = actor.currentView.get();
|
|
157
|
+
render(h(components[view.component], viewProps), container);
|
|
158
|
+
|
|
159
|
+
// Solid:
|
|
160
|
+
import { Dynamic } from 'solid-js/web';
|
|
161
|
+
<Dynamic component={components[view.component]} {...viewProps} />
|
|
162
|
+
|
|
163
|
+
// Vue 3:
|
|
164
|
+
import { h, resolveComponent } from 'vue';
|
|
165
|
+
h(resolveComponent(view.component), viewProps)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Benefits
|
|
169
|
+
|
|
170
|
+
- Works with any JSX framework
|
|
171
|
+
- Manual control over routing integration
|
|
172
|
+
- No React dependency
|
|
173
|
+
- Same actor/signal API
|
|
174
|
+
|
|
175
|
+
## Mode 3: Pure Browser (jQuery, Alpine, Vanilla JS)
|
|
176
|
+
|
|
177
|
+
Manual browser integration with no framework.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
import { createPlayRouter, connectRouter } from "@xmachines/play-router";
|
|
181
|
+
|
|
182
|
+
// 1. Create actor
|
|
183
|
+
const actor = createPlayer();
|
|
184
|
+
|
|
185
|
+
// 2. Connect router with browser integration
|
|
186
|
+
const disconnect = connectRouter(actor, {
|
|
187
|
+
onNavigate: (path) => {
|
|
188
|
+
// Update browser history
|
|
189
|
+
window.history.pushState({}, "", path);
|
|
190
|
+
renderView();
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 3. Manual rendering
|
|
195
|
+
function renderView() {
|
|
196
|
+
const view = actor.currentView.get();
|
|
197
|
+
|
|
198
|
+
// Vanilla JS rendering
|
|
199
|
+
if (view.component === "HomeView") {
|
|
200
|
+
document.getElementById("app").innerHTML = `
|
|
201
|
+
<h1>Home</h1>
|
|
202
|
+
<button onclick="navigateToAbout()">Go to About</button>
|
|
203
|
+
`;
|
|
204
|
+
} else if (view.component === "AboutView") {
|
|
205
|
+
document.getElementById("app").innerHTML = `
|
|
206
|
+
<h1>About</h1>
|
|
207
|
+
<button onclick="navigateToHome()">Go to Home</button>
|
|
208
|
+
`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 4. Listen to browser back/forward
|
|
213
|
+
window.addEventListener("popstate", () => {
|
|
214
|
+
const path = window.location.pathname;
|
|
215
|
+
actor.send({ type: "play.route", to: path });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// 5. Manual event handlers
|
|
219
|
+
function navigateToAbout() {
|
|
220
|
+
actor.send({ type: "play.route", to: "/about" });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function navigateToHome() {
|
|
224
|
+
actor.send({ type: "play.route", to: "/" });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Start
|
|
228
|
+
actor.start();
|
|
229
|
+
renderView();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Benefits
|
|
233
|
+
|
|
234
|
+
- No framework dependency at all
|
|
235
|
+
- Maximum control and flexibility
|
|
236
|
+
- Works with jQuery, Alpine.js, HTMX, etc.
|
|
237
|
+
- Signals available via `actor.currentView.get()`
|
|
238
|
+
|
|
239
|
+
## Provider API Parallelism
|
|
240
|
+
|
|
241
|
+
All three modes follow the same pattern:
|
|
242
|
+
|
|
243
|
+
| Concept | TanStack Mode | Vanilla Mode | Pure Browser Mode |
|
|
244
|
+
| ----------- | ---------------------------------- | ------------------------------------ | -------------------------- |
|
|
245
|
+
| Actor | `createPlayer()` | `createPlayer()` | `createPlayer()` |
|
|
246
|
+
| Router | `createRouter()` (TanStack) | `createPlayRouter()` | `connectRouter()` |
|
|
247
|
+
| Integration | `<PlayTanStackRouterProvider>` | Manual via `onNavigate` callback | Manual + `window.popstate` |
|
|
248
|
+
| Rendering | `<PlayRenderer>` via renderer prop | Framework-specific (h, Dynamic, etc) | `innerHTML` or DOM API |
|
|
249
|
+
| Events | Component `onClick` → `send()` | Same | Same |
|
|
250
|
+
| Signals | Observed by `useSignalEffect` | Manual `actor.currentView.get()` | Same |
|
|
251
|
+
|
|
252
|
+
The API parallelism ensures consistent patterns regardless of mode.
|
|
253
|
+
|
|
254
|
+
## Architectural Invariants
|
|
255
|
+
|
|
256
|
+
All three modes preserve the 5 architectural invariants:
|
|
257
|
+
|
|
258
|
+
1. **Actor Authority (INV-01):** Guards validate all navigation across all modes
|
|
259
|
+
2. **Strict Separation (INV-02):** Business logic (machine) has zero framework imports
|
|
260
|
+
3. **Signal-Only Reactivity (INV-05):** TC39 Signals work identically in all modes
|
|
261
|
+
4. **Passive Infrastructure (INV-04):** Router observes actor, never decides
|
|
262
|
+
5. **State-Driven Reset (INV-03):** Browser back/forward sends events to actor
|
|
263
|
+
|
|
264
|
+
## Next Steps
|
|
265
|
+
|
|
266
|
+
- **[Routing Patterns](./routing-patterns.md)** - Learn play.route events with parameters
|
|
267
|
+
- **[Integration Example](./src/integration-example.ts)** - Complete authentication flow
|
|
268
|
+
- **[React + TanStack Router Demo](../../packages/play-tanstack-react-router/examples/demo-app/)** - Production example using TanStack mode
|
|
269
|
+
- **[Vue Router Demo](../../packages/play-vue-router/examples/demo/)** - Vue Composition API integration
|
|
270
|
+
- **[SolidJS Router Demo](../../packages/play-solidjs-router/examples/demo/)** - Solid signals integration
|
|
271
|
+
|
|
272
|
+
## Learn More
|
|
273
|
+
|
|
274
|
+
- [play-tanstack-router README](../../packages/play-tanstack-react-router/README.md) - TanStack integration details
|
|
275
|
+
- [play-react README](../../packages/play-react/README.md) - React renderer documentation
|
|
276
|
+
- [play-router README](../../packages/play-router/README.md) - Framework-agnostic routing
|
|
277
|
+
- [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md) - Complete specification
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Routing Patterns - Play Architecture
|
|
2
|
+
|
|
3
|
+
Learn how to implement parameter-aware navigation using routing patterns with `meta.route` URL patterns and `play.route` events.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
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.
|
|
8
|
+
|
|
9
|
+
## Key Concepts
|
|
10
|
+
|
|
11
|
+
### Route Marker
|
|
12
|
+
|
|
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.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
states: {
|
|
17
|
+
dashboard: {
|
|
18
|
+
meta: {
|
|
19
|
+
route: '/dashboard', // Marks state as routable
|
|
20
|
+
view: { component: 'Dashboard' }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
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
|
+
```
|
|
49
|
+
|
|
50
|
+
## Complete Example
|
|
51
|
+
|
|
52
|
+
See [routing-example.ts](./src/routing-example.ts) for a complete runnable example demonstrating all patterns.
|
|
53
|
+
|
|
54
|
+
### Machine Configuration
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { setup } from "xstate";
|
|
58
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
59
|
+
|
|
60
|
+
const authMachine = setup({
|
|
61
|
+
types: {
|
|
62
|
+
context: {} as {
|
|
63
|
+
isAuthenticated: boolean;
|
|
64
|
+
userId: string;
|
|
65
|
+
routeParams: Record<string, string>;
|
|
66
|
+
},
|
|
67
|
+
events: {} as
|
|
68
|
+
| { type: "play.route"; to: string; params?: Record<string, string> }
|
|
69
|
+
| { type: "auth.login"; userId: string },
|
|
70
|
+
},
|
|
71
|
+
}).createMachine({
|
|
72
|
+
initial: "login",
|
|
73
|
+
context: {
|
|
74
|
+
isAuthenticated: false,
|
|
75
|
+
userId: "",
|
|
76
|
+
routeParams: {},
|
|
77
|
+
},
|
|
78
|
+
states: {
|
|
79
|
+
login: {
|
|
80
|
+
id: "login",
|
|
81
|
+
meta: {
|
|
82
|
+
route: "/login",
|
|
83
|
+
view: { component: "LoginView" },
|
|
84
|
+
},
|
|
85
|
+
on: {
|
|
86
|
+
"auth.login": {
|
|
87
|
+
target: "profile",
|
|
88
|
+
actions: ({ context, event }) => {
|
|
89
|
+
context.isAuthenticated = true;
|
|
90
|
+
context.userId = event.userId;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
profile: {
|
|
97
|
+
id: "profile",
|
|
98
|
+
meta: {
|
|
99
|
+
route: "/profile/:userId",
|
|
100
|
+
view: {
|
|
101
|
+
component: "ProfileView",
|
|
102
|
+
userId: (ctx) => ctx.routeParams.userId || ctx.userId,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
always: [
|
|
106
|
+
{
|
|
107
|
+
target: "login",
|
|
108
|
+
guard: ({ context }) => !context.isAuthenticated,
|
|
109
|
+
// Redirect to login if not authenticated
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
on: {
|
|
113
|
+
"play.route": {
|
|
114
|
+
actions: ({ context, event }) => {
|
|
115
|
+
if (event.params?.userId) {
|
|
116
|
+
context.routeParams = { userId: event.params.userId };
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const createPlayer = definePlayer({ machine: authMachine });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Using the Actor
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// Create and start actor
|
|
132
|
+
const actor = createPlayer();
|
|
133
|
+
actor.start();
|
|
134
|
+
|
|
135
|
+
// Login (transitions to profile)
|
|
136
|
+
actor.send({ type: "auth.login", userId: "user123" });
|
|
137
|
+
|
|
138
|
+
// Navigate to different profile with parameters
|
|
139
|
+
actor.send({
|
|
140
|
+
type: "play.route",
|
|
141
|
+
to: "/profile/user456",
|
|
142
|
+
params: { userId: "user456" },
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Parameter extracted and available in view
|
|
146
|
+
const view = actor.currentView.get();
|
|
147
|
+
console.log(view.userId); // 'user456'
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Pattern Matching for Dynamic Routes
|
|
151
|
+
|
|
152
|
+
Routes with parameters use pattern matching to resolve URL paths:
|
|
153
|
+
|
|
154
|
+
- `/profile/:userId` matches `/profile/user123`, `/profile/alice`, etc.
|
|
155
|
+
- `/settings/:section?` matches `/settings` (no section) or `/settings/privacy`
|
|
156
|
+
|
|
157
|
+
The router extracts parameters from the URL and passes them in `play.route` events, which the machine stores in context for view projection.
|
|
158
|
+
|
|
159
|
+
## Guard Placement Architecture
|
|
160
|
+
|
|
161
|
+
**Core Principle:** Guards check if you can BE in a state (state entry), not if you can TAKE an event (event handlers).
|
|
162
|
+
|
|
163
|
+
### Pattern 1: Using formatPlayRouteTransitions (RECOMMENDED)
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
|
|
167
|
+
|
|
168
|
+
const machineConfig = {
|
|
169
|
+
initial: "home",
|
|
170
|
+
context: { isAuthenticated: false },
|
|
171
|
+
states: {
|
|
172
|
+
home: { id: "home", meta: { route: "/" } },
|
|
173
|
+
profile: {
|
|
174
|
+
id: "profile",
|
|
175
|
+
meta: { route: "/profile/:userId" },
|
|
176
|
+
// Always-guard validates state entry
|
|
177
|
+
always: [
|
|
178
|
+
{
|
|
179
|
+
target: "login",
|
|
180
|
+
guard: ({ context }) => !context.isAuthenticated,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Utility handles routing infrastructure
|
|
188
|
+
const machine = createMachine(formatPlayRouteTransitions(machineConfig));
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Why this works:**
|
|
192
|
+
|
|
193
|
+
- `formatPlayRouteTransitions` adds routing infrastructure (event.to matching)
|
|
194
|
+
- Always-guards handle business logic (authentication, authorization)
|
|
195
|
+
- Clear separation: routing layer (infrastructure) vs validation layer (business logic)
|
|
196
|
+
|
|
197
|
+
### Pattern 2: Manual Always-Guards (Advanced)
|
|
198
|
+
|
|
199
|
+
For advanced users who need full control:
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const machine = createMachine({
|
|
203
|
+
context: { intendedRoute: null, isAuthenticated: false },
|
|
204
|
+
on: {
|
|
205
|
+
"play.route": {
|
|
206
|
+
actions: assign({
|
|
207
|
+
intendedRoute: ({ event }) => event.to,
|
|
208
|
+
}),
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
always: [
|
|
212
|
+
{
|
|
213
|
+
target: "profile",
|
|
214
|
+
guard: ({ context }) => context.intendedRoute === "#profile" && context.isAuthenticated,
|
|
215
|
+
reenter: true,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
target: "login",
|
|
219
|
+
guard: ({ context }) =>
|
|
220
|
+
context.intendedRoute === "#profile" && !context.isAuthenticated,
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Anti-Pattern: Guards on Events
|
|
227
|
+
|
|
228
|
+
**NEVER do this:**
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
// ❌ WRONG - Guard on event checking event property
|
|
232
|
+
on: {
|
|
233
|
+
'play.route': [
|
|
234
|
+
{
|
|
235
|
+
guard: ({ event }) => event.to === "#dashboard",
|
|
236
|
+
target: "dashboard"
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**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?"
|
|
243
|
+
|
|
244
|
+
**Correct approach:** Use `formatPlayRouteTransitions` for routing infrastructure, use always-guards for state validation.
|
|
245
|
+
|
|
246
|
+
### Actor Authority in Action
|
|
247
|
+
|
|
248
|
+
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.
|
|
249
|
+
|
|
250
|
+
## Next Steps
|
|
251
|
+
|
|
252
|
+
- **[Multi-Router Integration](./multi-router-integration.md)** - Learn about renderer prop pattern
|
|
253
|
+
- **[Integration Example](./src/integration-example.ts)** - See complete authentication flow with all patterns
|
|
254
|
+
- **[React + TanStack Router Demo](../../packages/play-tanstack-react-router/examples/demo-app/)** - Production example with all 5 architectural invariants
|
|
255
|
+
|
|
256
|
+
## Learn More
|
|
257
|
+
|
|
258
|
+
- [XState Routes Documentation](https://stately.ai/docs/routes) - Official Stately routing patterns
|
|
259
|
+
- [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md) - Complete architectural specification
|
|
260
|
+
- [play-router README](../../packages/play-router/README.md) - Route extraction and tree building
|