@xmachines/play-svelte-spa-router
API / @xmachines/play-svelte-spa-router
Svelte SPA Router adapter for the XMachines Play architecture — connects hash-based routing to a
Routableactor so state machines own navigation.
Part of the XMachines Play monorepo.
Installation
npm install @xmachines/play-svelte-spa-routerPeer dependencies — install alongside the adapter:
npm install svelte@^5.0.0 svelte-spa-router@^5.0.0 xstate@^5.31.0Usage
1. Define a routable XState machine
Declare routes in each state’s meta.route field:
import { setup } from "xstate";import { formatPlayRouteTransitions } from "@xmachines/play-xstate";import type { PlayRouteEvent } from "@xmachines/play-svelte-spa-router";
const machine = setup({ types: { events: {} as PlayRouteEvent },}).createMachine( formatPlayRouteTransitions({ id: "app", initial: "home", states: { home: { meta: { route: "/home" } }, dashboard: { meta: { route: "/dashboard" } }, settings: { meta: { route: "/settings/:section?" } }, }, }),);2. Create a player and connect the router
Use createRouteMap to build a bidirectional route map from the machine, then call connectRouter once — typically in your root Svelte component or a dedicated runtime module:
import { definePlayer } from "@xmachines/play-xstate";import { connectRouter, createRouteMap } from "@xmachines/play-svelte-spa-router";
const createPlayer = definePlayer({ machine });export const actor = createPlayer();actor.start();
const routeMap = createRouteMap(machine);const disconnectRouter = connectRouter({ actor, routeMap });3. Clean up on destroy
Return the cleanup function from connectRouter to onDestroy (or your lifecycle equivalent):
<script lang="ts"> import { onDestroy } from "svelte"; import { connectRouter, createRouteMap } from "@xmachines/play-svelte-spa-router";
const routeMap = createRouteMap(machine); const disconnect = connectRouter({ actor, routeMap });
onDestroy(() => disconnect());</script>API Summary
connectRouter(options): () => void
Connects svelte-spa-router to a Routable actor. Returns a cleanup (disconnect) function.
interface ConnectRouterOptions { readonly actor: RoutableActor; readonly routeMap: RouteMap; /** * Window-like object for `hashchange` subscriptions. * Defaults to the global `window`. Pass a mock in tests * or a no-op in SSR environments. */ readonly window?: WindowLike;}Behaviour:
- On
connect: reads the current hash URL viarouter.loc.location, syncs the actor via aplay.routeevent, and starts watching forhashchangeevents. - On actor
currentRoutechange: callspush(path)(svelte-spa-router) to update the URL. - On
hashchange: parses the new location fromrouter.locand sendsplay.routeto the actor. - On disconnect: removes the
hashchangelistener and unsubscribes from actor signals.
createRouteMap(machine): RouteMap
Re-exported from @xmachines/play-router. Extracts all meta.route entries from an XState machine and builds a bidirectional route map supporting URLPattern matching (including parameterised and optional segments).
RouteMap
Re-exported from @xmachines/play-router. Bidirectional map between state IDs and URL paths.
| Method | Description |
|---|---|
getStateIdByPath(path) | Returns the state ID for a URL path, or null |
getPathByStateId(stateId) | Returns the URL path for a state ID, or null |
Exported types
| Type | Description |
|---|---|
ConnectRouterOptions | Options accepted by connectRouter |
RoutableActor | Minimal actor interface — AbstractActor from @xmachines/play-actor combined with the Routable mixin |
PlayRouteEvent | The play.route event type sent to the actor |
RouterBridge | Interface that all router bridges must satisfy |
RouteMapping | Route-entry shape within a RouteMap |
RouteMapOptions | Options for createRouteMap |
WindowLike | Minimal window interface for testability |
How it works
SvelteSpaRouterBridge extends RouterBridgeBase from @xmachines/play-router and implements three abstract methods:
| Method | svelte-spa-router equivalent |
|---|---|
navigateRouter(path) | push(path) |
watchRouterChanges() | win.addEventListener("hashchange", …) |
unwatchRouterChanges() | win.removeEventListener("hashchange", …) |
Initial path is read from router.loc.location and initial search from router.loc.querystring. Restore-vs-deeplink detection, guard-redirect flows, and isProcessingNavigation debouncing are inherited from RouterBridgeBase.
Testing
Run the package tests in isolation:
npm test -w @xmachines/play-svelte-spa-routerOr from inside this package directory:
npm testCoverage thresholds: 80% lines / functions / branches / statements.
License
MIT © XMachines Contributors. See LICENSE.