Skip to content

@xmachines/play-svelte-spa-router

API / @xmachines/play-svelte-spa-router

Svelte SPA Router adapter for the XMachines Play architecture. It connects hash-based routing to a Routable actor, so the state machine owns the navigation.

License: MIT Version


Installation

Terminal window
pnpm add @xmachines/play-svelte-spa-router

Peer dependencies. Install them with the adapter:

Terminal window
pnpm add svelte@^5.0.0 svelte-spa-router@^5.0.0 xstate@^5.31.0

Usage

1. Define a routable XState machine

Declare the route of each state in its 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 one time, usually in your root Svelte component or in a dedicated runtime module:

runtime.ts
import { definePlayer } from "@xmachines/play-xstate";
import { connectRouter, createRouteMap } from "@xmachines/play-svelte-spa-router";
import { machine } from "./machine.js"; // defined in the previous example
const createPlayer = definePlayer({ machine });
export const actor = createPlayer();
actor.start();
const routeMap = createRouteMap(machine);
export const disconnectRouter = connectRouter({ actor, routeMap });

3. Clean up on destroy

Give the cleanup function of connectRouter to onDestroy, or to the equivalent lifecycle hook:

<script lang="ts">
import { onDestroy } from "svelte";
import { disconnectRouter } from "./runtime.js"; // the module from the previous example
onDestroy(() => disconnectRouter());
</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;
}

Behavior:

  • On connect: it reads the current hash URL from router.loc.location, sets the actor state with a play.route event, and then listens for a hashchange event.
  • On a change of the actor currentRoute: it calls push(path) from svelte-spa-router to update the URL.
  • On hashchange: it reads the new location from router.loc and sends play.route to the actor.
  • On disconnect: it removes the hashchange listener and cancels the subscriptions to the actor signals.

createRouteMap(machine): RouteMap

This function comes from @xmachines/play-router. It reads every meta.route entry of an XState machine. Then it builds a bidirectional route map with URLPattern matching, which includes a parameter segment and an optional segment.

RouteMap

This type comes from @xmachines/play-router. It is the bidirectional map between the state IDs and the 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
ConnectRouterOptionsThe options that connectRouter accepts
RoutableActorMinimal actor interface — AbstractActor from @xmachines/play-actor combined with the Routable mixin
PlayRouteEventThe play.route event type that the bridge sends 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", …)

The bridge reads the initial path from router.loc.location, and the initial search string from router.loc.querystring. It inherits the restore-or-deeplink detection, the guard-redirect flow, and the isProcessingNavigation debounce from RouterBridgeBase.

SvelteSpaRouterBridge is exported from the package — prefer connectRouter for the common case, and instantiate the bridge directly (new SvelteSpaRouterBridge(actor, routeMap) followed by connect()/disconnect()) when you need fine-grained control over the connection lifecycle.


Testing

Run the package tests in isolation:

Terminal window
pnpm --filter @xmachines/play-svelte-spa-router test

Or from inside this package directory:

Terminal window
pnpm test

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


License

MIT © XMachines Contributors. See LICENSE.

Classes

Interfaces

Type Aliases

Functions