Skip to content

@xmachines/play-sveltekit-router-demo

Examples / @xmachines/play-sveltekit-router-demo

Svelte 5 + SvelteKit router adapter integration demo for the XMachines Play architecture using browser pathname URL routing.

License: MIT

What This Demonstrates

  • Shared auth machine reused without framework-specific business logic
  • connectRouter pathname URL ↔ actor sync via @xmachines/play-sveltekit-router
  • Shell-driven rendering via PlayUIProvider + PlayRenderer with actor-authoritative navigation
  • Canonical atoms lifecycle integrated with Svelte 5 runes
  • Non-browser invariant tests plus browser E2E coverage
  • Stately inspector attached at creation via options.inspect — the debug panel’s Show Inspector button opens the live machine in its own window

Running the Demo

From the repository root:

Terminal window
pnpm install
pnpm --filter @xmachines/play-sveltekit-router-demo run dev

Then open http://localhost:3009.

Step-by-Step Code Flow

Use this order to understand the implementation:

  1. src/main.ts mounts App.svelte and registers HMR cleanup via cleanup.
  2. src/runtime.ts creates the actor, builds the registry, calls createRouteMap(authMachine), and wires connectRouter({ actor, routeMap }).
  3. connectRouter synchronizes browser pathname changes with the actor via play.route events and reflects actor-approved routes back to the pathname URL.
  4. src/App.svelte imports the singleton actor, registryResult, and inspector from src/runtime.ts and delegates to the shared Shell.svelte.
  5. Shell.svelte renders <PlayUIProvider> + <PlayRenderer /> with the actor and registryResult and provides the shared UI shell (nav, debug panel with the Show Inspector button).
  6. Browser tests in test/browser/ validate startup and auth route transitions.
// src/runtime.ts (shape)
export const inspector = createDemoInspector();
const createDemoPlayer = definePlayer({
machine: authMachine,
options: { inspect: inspector.inspect },
actor: compose(PlayerActor, withRouting, withView),
});
export const actor = createDemoPlayer();
actor.start();
export const routeMap = createRouteMap(authMachine);
export const disconnectRouter = connectRouter({ actor, routeMap });
export function cleanup(): void {
disconnectRouter();
actor.stop();
}
<!-- src/App.svelte (shape) -->
<script lang="ts">
import { Shell } from "@xmachines/play-svelte-demo";
import { actor, inspector, registryResult } from "./runtime.js";
</script>
<Shell {actor} {registryResult} {inspector} />
<!-- Shell.svelte — rendering (shape) -->
<PlayUIProvider actor={props.actor} registryResult={props.registryResult}>
<PlayRenderer />
</PlayUIProvider>

Key Files

  • src/main.ts - Svelte app mount and HMR cleanup registration
  • src/runtime.ts - actor creation, registry construction, route map, and connectRouter wiring
  • src/lib/navigation.ts - History API shim implementing SvelteKit’s $app/navigation contract for Vite dev and production builds
  • src/app-navigation.d.ts - TypeScript ambient declaration for the $app/navigation module
  • src/App.svelte - minimal root component delegating to shared Shell.svelte
  • test/library-pattern.test.ts - architecture boundary and invariant assertions
  • test/browser/shared-demo.browser.test.ts - browser startup and auth route flow coverage

State Machine & Architecture Details

The demo utilizes XMachines architectural invariants:

  1. Actor Authority: When a user clicks a nav link, the browser pathname changes. connectRouter intercepts this, translates it to a play.route event, and sends it to the actor. The actor evaluates guards (e.g. isAuthenticated) and transitions to the appropriate state.
  2. Passive Infrastructure: The SvelteKit router adapter does not execute business logic. The actor dictates whether navigation is permitted. The Svelte application only renders the state.
  3. Atom-Only Reactivity: The bridge leverages Svelte 5 rune reactivity internally to react precisely when actor atoms update, without ad-hoc $state stores for business logic.

Watcher Lifecycle and Cleanup Contract

This demo follows the canonical watcher lifecycle used across all @xmachines framework adapters:

  1. The atom notifies the subscription, synchronously, from the set that changed it
  2. watchAtom queues one microtask, and it drops each duplicate notification of that batch
  3. The microtask reads the actor atoms and projects Svelte-local render state

One subscription lives for the whole watch, and no caller arms a watcher again. Cleanup is explicit: cleanup() calls disconnectRouter() and actor.stop() during HMR disposal, preventing memory leaks and ghost subscriptions.

Adapter Boundaries

connectRouter (from @xmachines/play-sveltekit-router) is passive infrastructure. It translates pathname URL changes into play.route events and reflects actor-approved route changes back to the browser pathname. Business validity remains actor-owned. The shared Shell.svelte from @xmachines/play-svelte-demo is reused to avoid duplicating view components across router demos.

The $app/navigation module is shimmed by src/lib/navigation.ts using the History API, so the demo runs outside a full SvelteKit project (plain Vite dev server and production build).

Available Scripts

These commands are defined in package.json:

CommandDescription
pnpm --filter @xmachines/play-sveltekit-router-demo run devStart Vite dev server (http://localhost:3009/)
pnpm --filter @xmachines/play-sveltekit-router-demo run buildBuild production bundle
pnpm --filter @xmachines/play-sveltekit-router-demo run previewPreview built bundle
pnpm --filter @xmachines/play-sveltekit-router-demo run testRun Vitest test suite
pnpm --filter @xmachines/play-sveltekit-router-demo run test:browserRun browser-focused Vitest suite

Learn More