@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,604 @@
|
|
|
1
|
+
[Documentation](../../README.md) / @xmachines/play-vue-router
|
|
2
|
+
|
|
3
|
+
# @xmachines/play-vue-router
|
|
4
|
+
|
|
5
|
+
**Vue Router 4.x adapter for XMachines Universal Player Architecture**
|
|
6
|
+
|
|
7
|
+
Bidirectional sync between Vue Router and XMachines state machines with Composition API integration.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`@xmachines/play-vue-router` enables Vue.js applications to use XMachines state machines as the source of truth for routing logic. Your state machine controls navigation through Vue Router's reactive primitives.
|
|
12
|
+
|
|
13
|
+
Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
|
|
14
|
+
|
|
15
|
+
- **Actor Authority (INV-01):** State machine validates navigation, router reflects decisions
|
|
16
|
+
- **Passive Infrastructure (INV-04):** Router observes `actor.currentRoute` signal
|
|
17
|
+
- **Signal-Only Reactivity (INV-05):** Watcher synchronizes URL with actor state
|
|
18
|
+
|
|
19
|
+
**Key Benefits:**
|
|
20
|
+
|
|
21
|
+
- **Logic-driven navigation:** Business logic in state machines, not components
|
|
22
|
+
- **Protected routes:** Guards live in state machine, not router config
|
|
23
|
+
- **Bidirectional sync:** Actor ↔ Vue Router with circular update prevention
|
|
24
|
+
- **Type-safe parameters:** Route params flow through state machine context
|
|
25
|
+
- **Composition API:** Integrates with `useRouter`, `useRoute`, `onUnmounted`
|
|
26
|
+
|
|
27
|
+
**Framework Compatibility:**
|
|
28
|
+
|
|
29
|
+
- Vue 3.x with Composition API
|
|
30
|
+
- Vue Router 4.x (`^4.0.0`)
|
|
31
|
+
- Named routes pattern (recommended by Vue Router docs)
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install vue-router@^4.0.0 vue@^3.5.0 @xmachines/play-vue-router @xmachines/play-vue
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Peer dependencies:**
|
|
40
|
+
|
|
41
|
+
- `vue-router` ^4.0.0 || ^5.0.0 — Vue Router library
|
|
42
|
+
- `vue` ^3.5.0 — Vue runtime
|
|
43
|
+
- `@xmachines/play-vue` — Vue renderer (`PlayRenderer`)
|
|
44
|
+
- `@xmachines/play-actor` — Actor base
|
|
45
|
+
- `@xmachines/play-router` — Route extraction
|
|
46
|
+
- `@xmachines/play-signals` — TC39 Signals primitives
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { createApp } from "vue";
|
|
52
|
+
import { createRouter, createWebHistory } from "vue-router";
|
|
53
|
+
import { VueRouterBridge, createRouteMap } from "@xmachines/play-vue-router";
|
|
54
|
+
import { extractMachineRoutes, getRoutableRoutes } from "@xmachines/play-router";
|
|
55
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
56
|
+
import Home from "./views/Home.vue";
|
|
57
|
+
import Profile from "./views/Profile.vue";
|
|
58
|
+
|
|
59
|
+
// 1. Extract routable states from machine (single source of truth)
|
|
60
|
+
const routeTree = extractMachineRoutes(authMachine);
|
|
61
|
+
const routableRoutes = getRoutableRoutes(routeTree);
|
|
62
|
+
const routeComponents = {
|
|
63
|
+
home: Home,
|
|
64
|
+
profile: Profile,
|
|
65
|
+
} as const;
|
|
66
|
+
|
|
67
|
+
// 2. Define Vue Router routes from extracted machine routes
|
|
68
|
+
const router = createRouter({
|
|
69
|
+
history: createWebHistory(),
|
|
70
|
+
routes: routableRoutes.map((route) => ({
|
|
71
|
+
path: route.fullPath,
|
|
72
|
+
name: route.stateId.replace(/^#/, ""),
|
|
73
|
+
component: routeComponents[route.stateId.replace(/^#/, "") as keyof typeof routeComponents],
|
|
74
|
+
})),
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 3. Compute route mapping from machine routes
|
|
78
|
+
const routeMap = createRouteMap(authMachine);
|
|
79
|
+
|
|
80
|
+
// 4. Create actor with state machine
|
|
81
|
+
const createPlayer = definePlayer({
|
|
82
|
+
machine: authMachine,
|
|
83
|
+
catalog: componentCatalog,
|
|
84
|
+
});
|
|
85
|
+
const actor = createPlayer();
|
|
86
|
+
actor.start();
|
|
87
|
+
|
|
88
|
+
// 5. Create bridge to sync actor and router
|
|
89
|
+
const bridge = new VueRouterBridge(router, actor, routeMap);
|
|
90
|
+
|
|
91
|
+
// 6. Connect bridge (required)
|
|
92
|
+
bridge.connect();
|
|
93
|
+
|
|
94
|
+
// 7. Create Vue app
|
|
95
|
+
const app = createApp(App);
|
|
96
|
+
app.use(router);
|
|
97
|
+
app.mount("#app");
|
|
98
|
+
|
|
99
|
+
// 8. Later, when tearing down your app/integration:
|
|
100
|
+
// bridge.disconnect();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## API Reference
|
|
104
|
+
|
|
105
|
+
### `VueRouterBridge`
|
|
106
|
+
|
|
107
|
+
Router adapter implementing the `RouterBridge` protocol for Vue Router 4.x.
|
|
108
|
+
|
|
109
|
+
**Type Signature:**
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
class VueRouterBridge {
|
|
113
|
+
constructor(router: Router, actor: AbstractActor<any>, routeMap: RouteMap);
|
|
114
|
+
connect(): void;
|
|
115
|
+
disconnect(): void;
|
|
116
|
+
dispose(): void;
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Constructor Parameters:**
|
|
121
|
+
|
|
122
|
+
- `router` - Vue Router instance from `createRouter()`
|
|
123
|
+
- `actor` - XMachines actor instance (from `definePlayer().actor`)
|
|
124
|
+
- `routeMap` - Bidirectional state ID ↔ route name mapping
|
|
125
|
+
|
|
126
|
+
**Methods:**
|
|
127
|
+
|
|
128
|
+
- `connect()` - Start bidirectional synchronization.
|
|
129
|
+
- `disconnect()` - Stop synchronization and unhook listeners.
|
|
130
|
+
- `dispose()` - Alias of `disconnect()` for ergonomic teardown.
|
|
131
|
+
|
|
132
|
+
**Internal Behavior:**
|
|
133
|
+
|
|
134
|
+
- Watches `actor.currentRoute` signal via `Signal.subtle.Watcher`
|
|
135
|
+
- Updates Vue Router via `router.push({ name, params })` when actor state changes
|
|
136
|
+
- Listens to router navigation via `router.afterEach()` hook
|
|
137
|
+
- Sends `play.route` events to actor when user navigates
|
|
138
|
+
- Prevents circular updates with multi-layer guards
|
|
139
|
+
|
|
140
|
+
### `RouteMap`
|
|
141
|
+
|
|
142
|
+
Bidirectional mapping between XMachines state IDs and Vue Router route names.
|
|
143
|
+
|
|
144
|
+
**Type Signature:**
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
interface RouteMapping {
|
|
148
|
+
stateId: string;
|
|
149
|
+
routeName: string;
|
|
150
|
+
pattern?: string;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
class RouteMap extends VueBaseRouteMap {
|
|
154
|
+
// Inherits all VueBaseRouteMap methods — no additional API
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Constructor Parameters:**
|
|
159
|
+
|
|
160
|
+
- `mappings` - Array of mapping objects with:
|
|
161
|
+
- `stateId` - State machine state ID (e.g., `'#profile'`)
|
|
162
|
+
- `routeName` - Vue Router route name (e.g., `'profile'`)
|
|
163
|
+
- `pattern` - Optional path pattern for URL resolution (e.g., `'/profile/:userId'`)
|
|
164
|
+
|
|
165
|
+
**Methods (inherited from `VueBaseRouteMap`):**
|
|
166
|
+
|
|
167
|
+
- `getRouteName(stateId)` — Find route name from state ID
|
|
168
|
+
- `getStateId(routeName)` — Find state ID from route name
|
|
169
|
+
- `getPattern(stateId)` — Get URL pattern for state (optional metadata)
|
|
170
|
+
- `getStateIdByPath(path)` — Resolve a URL path to a state ID (from `BaseRouteMap`)
|
|
171
|
+
- `getPathByStateId(stateId)` — Get the URL path pattern for a state ID (from `BaseRouteMap`)
|
|
172
|
+
|
|
173
|
+
### `VueBaseRouteMap`
|
|
174
|
+
|
|
175
|
+
Intermediate base class for Vue Router adapters. Extends `BaseRouteMap` (bucket-indexed O(k) pattern matching + QuickLRU cache) and adds Vue-specific named-route lookup.
|
|
176
|
+
|
|
177
|
+
Exported for consumers who need to extend or test the Vue routing layer directly. Most consumers use `RouteMap` instead.
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
class VueBaseRouteMap extends BaseRouteMap {
|
|
181
|
+
constructor(mappings: RouteMapping[]);
|
|
182
|
+
getRouteName(stateId: string): string | undefined;
|
|
183
|
+
getStateId(routeName: string): string | undefined;
|
|
184
|
+
getPattern(stateId: string): string | undefined;
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Examples
|
|
189
|
+
|
|
190
|
+
### Basic Usage: Simple 2-3 Route Setup
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// State machine with 3 states
|
|
194
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
195
|
+
|
|
196
|
+
const appMachine = setup({
|
|
197
|
+
types: {
|
|
198
|
+
events: {} as PlayRouteEvent,
|
|
199
|
+
},
|
|
200
|
+
}).createMachine({
|
|
201
|
+
id: "app",
|
|
202
|
+
initial: "home",
|
|
203
|
+
states: {
|
|
204
|
+
home: {
|
|
205
|
+
meta: { route: "/", view: { component: "Home" } },
|
|
206
|
+
},
|
|
207
|
+
about: {
|
|
208
|
+
meta: { route: "/about", view: { component: "About" } },
|
|
209
|
+
},
|
|
210
|
+
contact: {
|
|
211
|
+
meta: { route: "/contact", view: { component: "Contact" } },
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const componentCatalog = defineCatalog({
|
|
217
|
+
Home,
|
|
218
|
+
About,
|
|
219
|
+
Contact,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const player = definePlayer({ machine: appMachine, catalog: componentCatalog });
|
|
223
|
+
|
|
224
|
+
// Vue Router configuration
|
|
225
|
+
const routeTree = extractMachineRoutes(appMachine);
|
|
226
|
+
const routableRoutes = getRoutableRoutes(routeTree);
|
|
227
|
+
const routeComponents = {
|
|
228
|
+
home: Home,
|
|
229
|
+
about: About,
|
|
230
|
+
contact: Contact,
|
|
231
|
+
} as const;
|
|
232
|
+
|
|
233
|
+
const router = createRouter({
|
|
234
|
+
history: createWebHistory(),
|
|
235
|
+
routes: routableRoutes.map((route) => ({
|
|
236
|
+
path: route.fullPath,
|
|
237
|
+
name: route.stateId.replace(/^#/, ""),
|
|
238
|
+
component: routeComponents[route.stateId.replace(/^#/, "") as keyof typeof routeComponents],
|
|
239
|
+
})),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Route mapping computed from machine routes
|
|
243
|
+
const routeMap = createRouteMap(appMachine);
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Parameter Handling: Dynamic Routes with `:param` Syntax
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// State machine with parameter routes
|
|
250
|
+
import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
|
|
251
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
252
|
+
|
|
253
|
+
const machineConfig = {
|
|
254
|
+
id: "app",
|
|
255
|
+
context: {},
|
|
256
|
+
states: {
|
|
257
|
+
profile: {
|
|
258
|
+
meta: {
|
|
259
|
+
route: "/profile/:userId",
|
|
260
|
+
view: { component: "Profile" },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
settings: {
|
|
264
|
+
meta: {
|
|
265
|
+
route: "/settings/:section?",
|
|
266
|
+
view: { component: "Settings" },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const appMachine = setup({
|
|
273
|
+
types: {
|
|
274
|
+
context: {} as { userId?: string; section?: string },
|
|
275
|
+
events: {} as PlayRouteEvent,
|
|
276
|
+
},
|
|
277
|
+
}).createMachine(formatPlayRouteTransitions(machineConfig));
|
|
278
|
+
|
|
279
|
+
const componentCatalog = defineCatalog({
|
|
280
|
+
Profile,
|
|
281
|
+
Settings,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const player = definePlayer({ machine: appMachine, catalog: componentCatalog });
|
|
285
|
+
|
|
286
|
+
// Router with dynamic routes
|
|
287
|
+
const routeTree = extractMachineRoutes(appMachine);
|
|
288
|
+
const routableRoutes = getRoutableRoutes(routeTree);
|
|
289
|
+
const routeComponents = {
|
|
290
|
+
profile: Profile,
|
|
291
|
+
settings: Settings,
|
|
292
|
+
} as const;
|
|
293
|
+
|
|
294
|
+
const router = createRouter({
|
|
295
|
+
routes: routableRoutes.map((route) => ({
|
|
296
|
+
path: route.fullPath,
|
|
297
|
+
name: route.stateId.replace(/^#/, ""),
|
|
298
|
+
component: routeComponents[route.stateId.replace(/^#/, "") as keyof typeof routeComponents],
|
|
299
|
+
})),
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Route mapping computed from machine routes
|
|
303
|
+
const routeMap = createRouteMap(appMachine);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Usage in component:**
|
|
307
|
+
|
|
308
|
+
```vue
|
|
309
|
+
<script setup>
|
|
310
|
+
import { inject } from "vue";
|
|
311
|
+
|
|
312
|
+
const actor = inject("actor");
|
|
313
|
+
|
|
314
|
+
function viewProfile(userId) {
|
|
315
|
+
actor.send({ type: "play.route", to: "#profile", params: { userId } });
|
|
316
|
+
}
|
|
317
|
+
</script>
|
|
318
|
+
|
|
319
|
+
<template>
|
|
320
|
+
<button @click="viewProfile('123')">View Profile</button>
|
|
321
|
+
</template>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Query Parameters: Search/Filters via Query Strings
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// State machine with query param handling
|
|
328
|
+
import { formatPlayRouteTransitions } from "@xmachines/play-xstate";
|
|
329
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
330
|
+
|
|
331
|
+
const machineConfig = {
|
|
332
|
+
context: { query: "", filters: {} },
|
|
333
|
+
states: {
|
|
334
|
+
search: {
|
|
335
|
+
meta: {
|
|
336
|
+
route: "/search",
|
|
337
|
+
view: { component: "Search" },
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const searchMachine = setup({
|
|
344
|
+
types: {
|
|
345
|
+
context: {} as { query: string; filters: Record<string, string> },
|
|
346
|
+
events: {} as PlayRouteEvent,
|
|
347
|
+
},
|
|
348
|
+
}).createMachine(formatPlayRouteTransitions(machineConfig));
|
|
349
|
+
|
|
350
|
+
const componentCatalog = defineCatalog({
|
|
351
|
+
Search,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const player = definePlayer({ machine: searchMachine, catalog: componentCatalog });
|
|
355
|
+
|
|
356
|
+
// Component sends query params
|
|
357
|
+
function handleSearch(searchTerm, filters) {
|
|
358
|
+
actor.send({
|
|
359
|
+
type: "play.route",
|
|
360
|
+
to: "#search",
|
|
361
|
+
query: { q: searchTerm, ...filters },
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Vue Router automatically reflects query params in URL:**
|
|
367
|
+
|
|
368
|
+
- `/search?q=xmachines&tag=typescript`
|
|
369
|
+
|
|
370
|
+
### Protected Routes: Authentication Guards
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// State machine with auth guards
|
|
374
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
375
|
+
|
|
376
|
+
const authMachine = setup({
|
|
377
|
+
types: {
|
|
378
|
+
context: {} as { isAuthenticated: boolean },
|
|
379
|
+
events: {} as PlayRouteEvent | { type: "login" } | { type: "logout" },
|
|
380
|
+
},
|
|
381
|
+
}).createMachine({
|
|
382
|
+
context: { isAuthenticated: false },
|
|
383
|
+
initial: "home",
|
|
384
|
+
states: {
|
|
385
|
+
home: {
|
|
386
|
+
meta: { route: "/", view: { component: "Home" } },
|
|
387
|
+
},
|
|
388
|
+
login: {
|
|
389
|
+
meta: { route: "/login", view: { component: "Login" } },
|
|
390
|
+
on: {
|
|
391
|
+
login: {
|
|
392
|
+
target: "dashboard",
|
|
393
|
+
actions: assign({ isAuthenticated: true }),
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
dashboard: {
|
|
398
|
+
meta: { route: "/dashboard", view: { component: "Dashboard" } },
|
|
399
|
+
always: {
|
|
400
|
+
guard: ({ context }) => !context.isAuthenticated,
|
|
401
|
+
target: "login",
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const componentCatalog = defineCatalog({
|
|
408
|
+
Home,
|
|
409
|
+
Login,
|
|
410
|
+
Dashboard,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const player = definePlayer({ machine: authMachine, catalog: componentCatalog });
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Guard behavior:**
|
|
417
|
+
|
|
418
|
+
- User navigates to `/dashboard`
|
|
419
|
+
- Bridge sends `play.route` event to actor
|
|
420
|
+
- Actor's `always` guard checks `isAuthenticated`
|
|
421
|
+
- If `false`, actor transitions to `login` state
|
|
422
|
+
- Bridge detects state change, redirects router to `/login`
|
|
423
|
+
- Actor Authority principle enforced
|
|
424
|
+
|
|
425
|
+
### Cleanup: Proper Disposal on Component Unmount
|
|
426
|
+
|
|
427
|
+
```vue
|
|
428
|
+
<script setup>
|
|
429
|
+
import { onUnmounted } from "vue";
|
|
430
|
+
import { VueRouterBridge } from "@xmachines/play-vue-router";
|
|
431
|
+
|
|
432
|
+
const router = useRouter();
|
|
433
|
+
const actor = inject("actor");
|
|
434
|
+
const routeMap = inject("routeMap");
|
|
435
|
+
|
|
436
|
+
const bridge = new VueRouterBridge(router, actor, routeMap);
|
|
437
|
+
|
|
438
|
+
// CRITICAL: Cleanup watchers and guards
|
|
439
|
+
onUnmounted(() => {
|
|
440
|
+
bridge.dispose();
|
|
441
|
+
});
|
|
442
|
+
</script>
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Why cleanup matters:**
|
|
446
|
+
|
|
447
|
+
- Navigation guards remain active after unmount (memory leak)
|
|
448
|
+
- Watchers continue observing signals (event listeners pile up)
|
|
449
|
+
- Multiple bridge instances send duplicate events
|
|
450
|
+
- Tests fail with "Cannot send to stopped actor" errors
|
|
451
|
+
|
|
452
|
+
## Architecture
|
|
453
|
+
|
|
454
|
+
### Bidirectional Sync (Actor ↔ Router)
|
|
455
|
+
|
|
456
|
+
**Actor → Router (Signal-driven):**
|
|
457
|
+
|
|
458
|
+
1. Actor transitions to new state with `meta.route`
|
|
459
|
+
2. `actor.currentRoute` signal updates
|
|
460
|
+
3. `Signal.subtle.Watcher` detects change in microtask
|
|
461
|
+
4. Bridge extracts state ID from signal
|
|
462
|
+
5. Bridge looks up route name via `routeMap.getRouteName()`
|
|
463
|
+
6. Bridge calls `router.push({ name, params })`
|
|
464
|
+
7. Vue Router updates URL and renders component
|
|
465
|
+
|
|
466
|
+
**Router → Actor (Navigation guard):**
|
|
467
|
+
|
|
468
|
+
1. User clicks link or browser back button
|
|
469
|
+
2. `router.afterEach()` hook fires with `to` route
|
|
470
|
+
3. Bridge resolves state ID from Vue route `name` via `routeMap.getStateId(...)`
|
|
471
|
+
4. Bridge extracts params from `to.params` (not `route.params` - see Pitfalls)
|
|
472
|
+
5. Bridge sends `play.route` event to actor
|
|
473
|
+
6. Actor validates navigation (guards, transitions)
|
|
474
|
+
7. If accepted: Actor transitions, signal updates, URL stays
|
|
475
|
+
8. If rejected: Actor redirects, bridge corrects URL via `router.replace()`
|
|
476
|
+
|
|
477
|
+
### Circular Update Prevention
|
|
478
|
+
|
|
479
|
+
**Multi-layer guards prevent infinite loops:**
|
|
480
|
+
|
|
481
|
+
1. **`lastSyncedPath` tracking:** Stores last synchronized path, skips if unchanged
|
|
482
|
+
2. **`isProcessingNavigation` flag:** Set during router-initiated navigation, prevents actor→router sync
|
|
483
|
+
3. **Microtask timing:** Actor validation happens asynchronously, bridge checks result after transition completes
|
|
484
|
+
|
|
485
|
+
**Pattern proven in the TanStack Router adapter:**
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
private syncRouterFromActor(): void {
|
|
489
|
+
if (this.isProcessingNavigation) return; // Guard 1
|
|
490
|
+
const currentRoute = this.actor.currentRoute.get();
|
|
491
|
+
if (currentRoute === this.lastSyncedPath) return; // Guard 2
|
|
492
|
+
this.lastSyncedPath = currentRoute;
|
|
493
|
+
this.router.push(currentRoute);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private syncActorFromRouter(to: RouteLocation): void {
|
|
497
|
+
this.isProcessingNavigation = true; // Guard 3
|
|
498
|
+
this.actor.send({ type: 'play.route', to: stateId, params });
|
|
499
|
+
queueMicrotask(() => {
|
|
500
|
+
this.isProcessingNavigation = false; // Guard 4
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Relationship to Other Packages
|
|
506
|
+
|
|
507
|
+
**Package Dependencies:**
|
|
508
|
+
|
|
509
|
+
- `@xmachines/play` - Protocol interfaces (`PlayRouteEvent`, `RouterBridge`)
|
|
510
|
+
- `@xmachines/play-actor` - Actor base class with signal protocol
|
|
511
|
+
- `@xmachines/play-router` - Route extraction and tree building
|
|
512
|
+
- `@xmachines/play-signals` - TC39 Signals polyfill for reactivity
|
|
513
|
+
- `@xmachines/play-xstate` - XState integration via `definePlayer()`
|
|
514
|
+
|
|
515
|
+
**Architecture Layers:**
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
┌─────────────────────────────────────┐
|
|
519
|
+
│ Vue Components (View Layer) │
|
|
520
|
+
│ - Uses inject('actor') │
|
|
521
|
+
│ - Sends play.route events │
|
|
522
|
+
└─────────────────────────────────────┘
|
|
523
|
+
↕
|
|
524
|
+
┌─────────────────────────────────────┐
|
|
525
|
+
│ VueRouterBridge (Adapter) │
|
|
526
|
+
│ - Watches actor.currentRoute │
|
|
527
|
+
│ - Listens to router.afterEach │
|
|
528
|
+
└─────────────────────────────────────┘
|
|
529
|
+
↕ ↕
|
|
530
|
+
┌─────────────┐ ┌──────────────────┐
|
|
531
|
+
│ Vue Router │ │ XMachines Actor │
|
|
532
|
+
│ (Infra) │ │ (Business Logic) │
|
|
533
|
+
└─────────────┘ └──────────────────┘
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Vue Router Composition API Integration
|
|
537
|
+
|
|
538
|
+
**Recommended patterns:**
|
|
539
|
+
|
|
540
|
+
- **`useRouter()`** - Get router instance for programmatic navigation (avoid in components - use actor)
|
|
541
|
+
- **`useRoute()`** - Access current route params (prefer actor context for state-driven components)
|
|
542
|
+
- **`onUnmounted()`** - Cleanup bridge to prevent leaks
|
|
543
|
+
|
|
544
|
+
**Named routes requirement:**
|
|
545
|
+
|
|
546
|
+
Vue Router adapter uses **named routes** (`router.push({ name: 'profile', params })`) instead of path-based navigation. This provides:
|
|
547
|
+
|
|
548
|
+
- Type safety (TypeScript route names)
|
|
549
|
+
- Cleaner param handling (object-based)
|
|
550
|
+
- Better Vue Router integration (recommended by docs)
|
|
551
|
+
|
|
552
|
+
All routes in your Vue Router config **must have a `name` property** for the bridge to work.
|
|
553
|
+
|
|
554
|
+
### Pitfall: `to.params` vs `route.params`
|
|
555
|
+
|
|
556
|
+
**⚠️ Common mistake:** Using global `useRoute()` in navigation guards
|
|
557
|
+
|
|
558
|
+
```typescript
|
|
559
|
+
// ❌ WRONG: route.params is stale during transition
|
|
560
|
+
router.afterEach((to) => {
|
|
561
|
+
const route = useRoute(); // Returns "from" route
|
|
562
|
+
const params = route.params; // STALE
|
|
563
|
+
// ...
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// ✅ CORRECT: to.params is fresh
|
|
567
|
+
router.afterEach((to) => {
|
|
568
|
+
const params = to.params; // FRESH
|
|
569
|
+
// ...
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Why it happens:** Vue Router's global `route` object updates asynchronously during navigation. The `to` parameter in `afterEach` is the destination route with correct params.
|
|
574
|
+
|
|
575
|
+
## License
|
|
576
|
+
|
|
577
|
+
Copyright (c) 2016 [Mikael Karon](mailto:mikael@karon.se). All rights reserved.
|
|
578
|
+
|
|
579
|
+
This work is licensed under the terms of the MIT license.
|
|
580
|
+
For a copy, see <https://opensource.org/licenses/MIT>.
|
|
581
|
+
|
|
582
|
+
## Classes
|
|
583
|
+
|
|
584
|
+
- [RouteMap](classes/RouteMap.md)
|
|
585
|
+
- [VueBaseRouteMap](classes/VueBaseRouteMap.md)
|
|
586
|
+
- [VueRouterBridge](classes/VueRouterBridge.md)
|
|
587
|
+
|
|
588
|
+
## Interfaces
|
|
589
|
+
|
|
590
|
+
- [PlayRouteEvent](interfaces/PlayRouteEvent.md)
|
|
591
|
+
- [RouteMapping](interfaces/RouteMapping.md)
|
|
592
|
+
- [RouterBridge](interfaces/RouterBridge.md)
|
|
593
|
+
|
|
594
|
+
## Type Aliases
|
|
595
|
+
|
|
596
|
+
- [RoutableActor](type-aliases/RoutableActor.md)
|
|
597
|
+
|
|
598
|
+
## Variables
|
|
599
|
+
|
|
600
|
+
- [PlayRouterProvider](variables/PlayRouterProvider.md)
|
|
601
|
+
|
|
602
|
+
## Functions
|
|
603
|
+
|
|
604
|
+
- [createRouteMap](functions/createRouteMap.md)
|