@xmachines/play-vue
v2.1.0
Published
Vue renderer for XMachines Play architecture
Downloads
616
Maintainers
Readme
@xmachines/play-vue
Vue 3 renderer for the XMachines Play Architecture. It observes the actor signals and renders the UI through
@xmachines/json-render-vue.
Overview
@xmachines/play-vue is the Vue 3 rendering layer of XMachines Play. It connects the TC39 Signals (the actor state) to the Vue reactivity, and it renders the components through @xmachines/json-render-vue.
The architecture invariants that this package keeps:
- Passive Infrastructure — the components observe the actor signals. They never decide a state transition.
- Signal-Only Reactivity — the TC39 Signals are the source of truth. Vue reactivity only triggers the re-render.
- Actor Authority — the actor controls the view selection. The renderer reflects it.
Installation
pnpm add @xmachines/play-vuePeer dependencies. Install them with the package:
pnpm add vue@^3.5.0 xstate@^5.31.0 @xstate/store@^3.17.0 @xmachines/json-render-vue@^0.20.0-xm.2 @xmachines/json-render-core@^0.20.0-xm.2 @xmachines/json-render-xstate@^0.20.0-xm.2Quick Start
<!-- App.vue -->
<template>
<PlayUIProvider :actor="actor" :registryResult="registryResult">
<PlayRenderer />
</PlayUIProvider>
</template>
<script setup lang="ts">
import { defineRegistry, PlayUIProvider, PlayRenderer } from "@xmachines/play-vue";
import { definePlayer } from "@xmachines/play-xstate";
import { myMachine } from "./machine.js";
import { myCatalog } from "./catalog.js";
import HomeSFC from "./views/Home.vue";
import LoginSFC from "./views/Login.vue";
const createPlayer = definePlayer({ machine: myMachine });
const actor = createPlayer();
actor.start();
const registryResult = defineRegistry(myCatalog, {
components: {
Home: HomeSFC, // .vue SFCs are auto-wrapped
Login: LoginSFC,
},
actions: {
login: async (args) => actor.send({ type: "auth.login", ...args }),
logout: async () => actor.send({ type: "auth.logout" }),
},
});
</script>API Summary
Components
<PlayUIProvider>
The composite provider. It wraps <ActorProvider> and JSONUIProvider in one component. Use it in most applications.
| Prop | Type | Required | Description |
| --------------------- | -------------------------- | -------- | ------------------------------------------- |
| actor | AbstractActor & Viewable | ✅ | The XMachines actor instance |
| registryResult | DefineRegistryResult | ✅ | Result of defineRegistry() |
| store | StateStore | — | External controlled state store (optional) |
| onRenderError | RenderErrorHandler | — | Error handler for render failures |
| navigate | (path: string) => void | — | Link navigation function |
| validationFunctions | Record<string, Function> | — | Custom validation functions |
| functions | Record<string, Function> | — | Named functions for $computed expressions |
Slots: default (the rendered content), fallback (the content while the actor view is null)
<PlayRenderer>
The leaf component without props. It reads the current spec and registry from the nearest <ActorProvider> or <PlayUIProvider> context, then renders them with <Renderer>. Put it inside one of those providers.
<PlayUIProvider :actor="actor" :registryResult="registryResult">
<PlayRenderer />
</PlayUIProvider><ActorProvider>
The low-level provider for a custom provider composition. It owns the complete actor lifecycle: the signal subscription, the state store of each view, the handler resolution, and the Vue context. Use <PlayUIProvider> when you do not need this control.
| Prop | Type | Required | Description |
| ---------------- | -------------------------- | -------- | ------------------------------- |
| actor | AbstractActor & Viewable | ✅ | The XMachines actor instance |
| registryResult | DefineRegistryResult | ✅ | Result of defineRegistry() |
| store | StateStore | — | External controlled state store |
| onRenderError | RenderErrorHandler | — | Override render error handler |
Functions
defineRegistry(catalog, options)
This function is the drop-in replacement for defineRegistry from @xmachines/json-render-vue. Always import it from @xmachines/play-vue when you work with a Vue SFC, not from @xmachines/json-render-vue. The wrapper finds each .vue SFC in the components map, and wraps it with h(). A Vue composable, and also a composable that uses inject, then works correctly inside <script setup>.
import { defineRegistry } from "@xmachines/play-vue";
// NOT: import { defineRegistry } from "@xmachines/json-render-vue"
import { myCatalog } from "./catalog.js"; // as in the Quick Start
import LoginSFC from "./views/Login.vue";
import DashboardSFC from "./views/Dashboard.vue";
const registryResult = defineRegistry(myCatalog, {
components: {
Login: LoginSFC, // .vue SFC — auto-wrapped via h()
Dashboard: DashboardSFC,
},
actions: {
login: async (args, setState, state) => {
/* ... */
},
},
});A plain ComponentFn function also works, and the wrapper passes it through without a change. One registry can hold both SFCs and plain functions.
useActor()
The Vue composable that gives the raw actor inside a PlayRenderer tree. A deeply nested component then does not need the actor as a prop.
import { useActor } from "@xmachines/play-vue";
// Inside a component rendered by PlayRenderer:
const actor = useActor();
actor.send({ type: "SUBMIT" });It throws when the caller is outside an <ActorProvider> or a <PlayUIProvider> tree.
usePlayView()
Access the current ViewContextValue — { spec, handlers, registry, store } — from inside an <ActorProvider> tree.
import { usePlayView } from "@xmachines/play-vue";
// Inside setup() of a component within an ActorProvider tree:
const view = usePlayView();
// view.spec, view.handlers, view.registry, view.storeNote:
usePlayViewwas previously namedgetPlayViewContext. The old name is still exported as a deprecated alias and will be removed in the next major.
Re-exported from @xmachines/json-render-vue
This package re-exports the following, so that a consumer imports everything from @xmachines/play-vue:
Components: JSONUIProvider, StateProvider, ActionProvider, VisibilityProvider, ValidationProvider, Renderer
Composables: useBoundProp
Types: JSONUIProviderProps, StateProviderProps, ActionProviderProps, ValidationProviderProps, RendererProps, ComponentFn, ComponentContext, DefineRegistryResult
Testing
Run tests for this package in isolation:
# From the monorepo root
pnpm --filter @xmachines/play-vue test
# Watch mode
pnpm --filter @xmachines/play-vue run test:watch
# With coverage (80% threshold enforced on lines, functions, branches, statements)
pnpm exec vitest run --coverage --config packages/play-vue/vitest.config.tsThe tests use Vitest in a jsdom environment. They mount the components with @vue/test-utils.
License
MIT — see LICENSE.
