@xmachines/play-tanstack-solid-router-demo
Examples / @xmachines/play-tanstack-solid-router-demo
Solid + @tanstack/solid-router integration demo for the XMachines Play architecture with actor-authoritative routing.
What This Demonstrates
- Shared auth machine reused without framework-specific business logic
PlayRouterProviderrenderer-based integration with TanStack Router- Shell-driven rendering via
PlayRendererwith actor-authoritative navigation - Object-based routing and deep location observation using TanStack’s Solid primitives
- Non-browser invariant tests plus browser E2E coverage
Running the Demo
From the repository root:
npm installnpm run dev -w @xmachines/play-tanstack-solid-router-demoThen open http://localhost:3005.
Step-by-Step Code Flow
Use this order to understand the implementation:
src/main.tsxbootstraps the demo app and mounts<App />onto#app.src/runtime.tsinitializes the actor, builds the registry, and creates the route map usingcreateRouteMap(authMachine).src/App.tsxconstructs the TanStack Router route tree from machine routes and wiresPlayRouterProviderwith the actor, router, and route map fromruntime.ts.- Router infrastructure stays passive: it forwards navigation intent to the actor via the
PlayRouterProviderand reflects actor-approved route changes back to TanStack. Shell(from@xmachines/play-solid-demo) renders actor-projected state (PlayRenderer) and emits actor events via theregistryResultactions.test/library-pattern.test.tsplustest/browser/verify invariants and browser routing behavior.
// src/main.tsx (shape)render(() => <App />, document.getElementById("app")!);// src/runtime.ts (shape)const createPlayer = definePlayer({ machine: authMachine });export const actor = createPlayer();actor.start();
export const routeMap = createRouteMap(authMachine);const routeTree = extractMachineRoutes(authMachine);export const routes = getRoutableRoutes(routeTree);// src/App.tsx (shape)import { actor, registryResult, routeMap, routes } from "./runtime.js";
return ( <PlayRouterProvider actor={actor} router={router} routeMap={routeMap} renderer={(currentActor, currentRouter) => ( <Shell actor={currentActor} router={currentRouter} registryResult={registryResult} /> )} />);// src/runtime.ts — actor events wired via registryResult actionslogin: async (args) => actor.send({ type: "auth.login", username: args.username }),logout: async () => actor.send({ type: "auth.logout" }),route: async (args) => actor.send({ type: "play.route", to: args.to, ...params }),Key Files
src/runtime.ts- actor lifecycle, registry, route map, and routable routes setupsrc/App.tsx- TanStack router construction andPlayRouterProviderwiringsrc/main.tsx- Vite bootstrap that mounts<App />via Solid’srendertest/library-pattern.test.ts- architecture boundary and invariant assertionstest/browser/shared-demo.browser.test.ts- browser startup and full auth flow coveragetest/browser/xstate-route-events.browser.test.ts- actor route event behavior in the browser
State Machine & Architecture Details
The demo utilizes XMachines architectural invariants:
- Actor Authority: When a user clicks a link, TanStack Router updates the location object. The
PlayRouterProviderintercepts this, translates it to aplay.routeevent, and sends it to the actor. The actor evaluates guards and applies state transitions. - Passive Infrastructure: The router does not execute route loaders or guards for business logic. The actor dictates whether navigation is permitted.
- Signal-Only Reactivity: The bridge leverages Solid’s
createEffectand TanStack’srouter.subscribeto observe location changes and signal updates without polling.
Watcher Lifecycle and Cleanup Contract
This demo follows the canonical watcher lifecycle:
notifyqueueMicrotaskgetPending()- Read actor signals and trigger state updates
- Re-arm with
watch()/watch(...signals)
In Solid, this is largely abstracted by createEffect within the bridge adapter. Cleanup remains explicit: the provider executes bridge.disconnect() on unmount (onCleanup) to prevent ghost subscriptions, ensuring safe component teardown and hot module replacement.
Adapter Boundaries
The TanStack Solid adapter wraps TanStack’s router.navigate({ to }) and router.subscribe methods while delegating core synchronization policy to RouterBridgeBase. This keeps complex double-dispatch logic normalized across all frameworks.
Available Scripts
These commands are defined in package.json:
| Command | Description |
|---|---|
npm run dev -w @xmachines/play-tanstack-solid-router-demo | Start Vite dev server |
npm run build -w @xmachines/play-tanstack-solid-router-demo | Build production bundle |
npm run preview -w @xmachines/play-tanstack-solid-router-demo | Preview built bundle |
npm run test -w @xmachines/play-tanstack-solid-router-demo | Run Vitest test suite |
npm run test:browser -w @xmachines/play-tanstack-solid-router-demo | Run browser-focused Vitest suite |
Verification
Use these checks to validate README claims against the current demo implementation:
npm run test -w @xmachines/play-tanstack-solid-router-demonpm run test:browser -w @xmachines/play-tanstack-solid-router-demoExpected result: library-pattern invariant tests and the browser shared-demo suite both pass, confirming actor-driven route and view updates including protected route redirection.