@xmachines/play-vue-router 1.0.0-beta.1

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