Skip to content

@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 Routable actor so state machines own navigation.

License: MIT npm

Part of the XMachines Play monorepo.


Installation

Terminal window
npm install @xmachines/play-svelte-spa-router

Peer dependencies — install alongside the adapter:

Terminal window
npm install svelte@^5.0.0 svelte-spa-router@^5.0.0 xstate@^5.31.0

Usage

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 via router.loc.location, syncs the actor via a play.route event, and starts watching for hashchange events.
  • On actor currentRoute change: calls push(path) (svelte-spa-router) to update the URL.
  • On hashchange: parses the new location from router.loc and sends play.route to the actor.
  • On disconnect: removes the hashchange listener 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.

MethodDescription
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

TypeDescription
ConnectRouterOptionsOptions accepted by connectRouter
RoutableActorMinimal actor interface — AbstractActor from @xmachines/play-actor combined with the Routable mixin
PlayRouteEventThe play.route event type sent to the actor
RouterBridgeInterface that all router bridges must satisfy
RouteMappingRoute-entry shape within a RouteMap
RouteMapOptionsOptions for createRouteMap
WindowLikeMinimal window interface for testability

How it works

SvelteSpaRouterBridge extends RouterBridgeBase from @xmachines/play-router and implements three abstract methods:

Methodsvelte-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:

Terminal window
npm test -w @xmachines/play-svelte-spa-router

Or from inside this package directory:

Terminal window
npm test

Coverage thresholds: 80% lines / functions / branches / statements.


License

MIT © XMachines Contributors. See LICENSE.

Classes

Interfaces

Type Aliases

Functions