@xmachines/play-actor
API / @xmachines/play-actor
Abstract Actor base class for XMachines Play Architecture.
Part of the xmachines-js monorepo.
Installation
npm install @xmachines/play-actorPeer dependencies — install alongside the package:
npm install xstate @xmachines/play @xmachines/play-signalsOverview
@xmachines/play-actor provides AbstractActor, a minimal base class that extends the XState Actor class while enforcing the Play Architecture’s signal protocol (RFC section 5.3). It exposes reactive TC39 Signals for infrastructure-layer communication while preserving full XState ecosystem compatibility (devtools, inspection).
The core protocol is deliberately minimal:
| Property | Type | Description |
|---|---|---|
state | Signal.State<unknown> | Reactive snapshot of current actor state |
send | (event: TEvent) => void | Event dispatch method |
Optional capabilities are declared as separate interfaces — a concrete actor opts in only to what it needs:
| Interface | Property | Description |
|---|---|---|
Routable | currentRoute: Signal.Computed<string | null> | Current route path derived from state |
Routable | initialRoute: string | null | Route the actor starts on |
Viewable | currentView: Signal.State<PlaySpec | null> | Current JSON-render view spec |
Concrete implementations are created by adapters such as @xmachines/play-xstate.
API Summary
AbstractActor<TLogic, TEvent>
Abstract base class extending XState Actor<TLogic>.
import { AbstractActor } from "@xmachines/play-actor";import { Signal } from "@xmachines/play-signals";import type { AnyActorLogic } from "xstate";
class MyActor extends AbstractActor<AnyActorLogic> { // Required: reactive state signal state = new Signal.State({});
// Required: typed event dispatch send = (event: { type: string }) => { /* dispatch to XState */ };}With a typed event union:
type AuthEvent = { type: "auth.login"; username: string } | { type: "auth.logout" };
class AuthActor extends AbstractActor<AnyActorLogic, AuthEvent> { state = new Signal.State({ isAuthenticated: false, username: null });
send = (event: AuthEvent) => { /* dispatch */ };}typedSpec<TContext>(spec)
Identity helper that constrains a PlaySpec object’s contextProps to keys of a specific machine context type. This enables compile-time validation and IDE autocomplete without any runtime cost.
import { typedSpec } from "@xmachines/play-actor";
interface DashboardCtx { username: string; params: Record<string, string>; query: Record<string, string>;}
// In an XState machine meta block:meta: { view: typedSpec<DashboardCtx>({ root: "root", contextProps: ["username"], // ✓ key of DashboardCtx // contextProps: ["usernaem"], // ✗ compile error elements: { root: { type: "Dashboard", props: {}, children: [] }, }, }),}PlaySpec
Extends @json-render/core’s Spec with an optional contextProps field — an explicit allowlist of machine context fields that are merged into element props at view derivation time.
import type { PlaySpec } from "@xmachines/play-actor";
const spec: PlaySpec = { root: "root", contextProps: ["username"], // only these keys are exposed to components elements: { root: { type: "Profile", props: { username: undefined }, children: [] }, },};Routable
Interface for actors that support routing.
import type { Routable } from "@xmachines/play-actor";import { Signal } from "@xmachines/play-signals";
// Implement in a concrete actor (note: RoutableActor interface is exported from @xmachines/play-router):class MyRoutableActor extends AbstractActor<AnyActorLogic> implements Routable { state = new Signal.State({}); currentRoute = new Signal.Computed(() => this.state.get().path ?? null); initialRoute = "/"; send = (event) => { /* dispatch */ };}Viewable
Interface for actors that expose a renderable view signal.
import type { Viewable } from "@xmachines/play-actor";import type { PlaySpec } from "@xmachines/play-actor";import { Signal } from "@xmachines/play-signals";
// currentView carries PlaySpec | nullconst signal = new Signal.State<PlaySpec | null>(null);const viewable: Viewable = { currentView: signal };BaseActorProviderProps<TRegistry>
Framework-agnostic base props shared by every ActorProvider implementation (React, Vue, Solid, Svelte). Framework renderer packages extend this interface.
import type { BaseActorProviderProps } from "@xmachines/play-actor";import type { DefineRegistryResult } from "@json-render/react";
interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> { fallback?: React.ReactNode; children: React.ReactNode;}BaseViewContextValue<TRegistry>
Framework-agnostic base for every framework’s ViewContextValue. Holds spec, handlers, registry, and store fields that are identical across React, Vue, Solid, and Svelte.
Testing
Run the test suite for this package in isolation:
# From the package directorynpm test
# From the monorepo root (workspace-scoped)npm test -w packages/play-actor
# Watch modenpm run test:watch -w packages/play-actorRequirements
- Node.js
>=22.0.0 - TypeScript
>=5.7(strict mode) - ESM only —
"type": "module"
@xmachines/play-actor - Abstract Actor base class for Play Architecture
This package provides AbstractActor, a minimal base class that extends XState Actor while enforcing the Play Architecture’s signal protocol (RFC section 5.3).
The core protocol is minimal (state + send). Optional capabilities are provided via interfaces:
- Routable: For actors that support routing
- Viewable: For actors that support view rendering
Maintains XState ecosystem compatibility (inspection, devtools) while exposing reactive signals for Infrastructure layer communication.