Skip to content

@xmachines/play-view

API / @xmachines/play-view

The shared view half of every renderer of the XMachines Play Architecture.

License: MIT Version

Installation

Terminal window
pnpm add @xmachines/play-view

Peer dependencies. Install them with the package:

Terminal window
pnpm add @xmachines/json-render-core

A renderer of a framework depends on this package already. Install it directly when you write a renderer of your own.

Overview

A renderer of a framework is two things: the view lifecycle, and the code that binds that lifecycle to the effects of the framework. This package holds the first one. @xmachines/play-react, @xmachines/play-vue, @xmachines/play-solid, @xmachines/play-svelte and @xmachines/play-dom hold the second one.

It also holds Viewable and PlaySpec. Every type here names a type of @xmachines/json-render-core, and that is the reason they are here and not in @xmachines/play-actor: an application that routes and renders no view installs this package never, and json-render never.

ExportDescription
ViewableThe capability of an actor that publishes a currentView signal
PlaySpec, typedSpecThe spec that an actor publishes, and its identity helper
BaseActorProviderProps, BaseViewContextValueThe framework-free half of the props and of the context of a provider
createViewStoreLifecycleThe lifecycle of the state store of one view
createFailureLatch, createReportGuardThe report of a render failure, one time for each view
toAtomState, attachRenderErrorHandlerThe guards that a provider puts around a registry
composePlayState, refreshContextSubtree, …The projection of the machine context into the store

API Summary

typedSpec(spec)

This identity helper gives a view-spec literal the type PlaySpec at the definition site. The XState meta field has the type Record<string, unknown>. Therefore this helper is the place where the spec shape receives the compile-time check and the IDE autocomplete. The helper has no cost at run time.

import { typedSpec } from "@xmachines/play-view";
// In an XState machine meta block:
meta: {
view: typedSpec({
root: "root",
elements: {
root: {
type: "Dashboard",
props: { username: { $state: "/context/username" } },
children: [],
},
},
}),
}

PlaySpec

This type extends the Spec type of @xmachines/json-render-core. Each derived view receives the complete machine context in its state store, under the read-only /context subtree. A spec therefore reads the context through the ordinary { $state: "/context/…" } grammar: in a prop, in a visible condition, and in repeat.statePath.

/context is read-only by design. Nothing can write to it. The machine context changes through an event only. A $bindState write or a setState write under /context throws an error, and the error names the event to send. This is the model: the bindable ephemeral state is at the root of the store, from spec.state; the domain state is in the machine, and it changes through events that are meaningful and easy to inspect.

The path shows the origin of each value. /context/params/username comes from the URL. /context/username belongs to the machine. One value can never hide the other.

The model projects everything, and this has two consequences. The first consequence is exposure. The complete context is visible to the client in the view store, which includes a debug panel, an inspector, and a validator. The context is a client-side value in each case, so keep a secret out of it.

The second consequence is emission granularity. The emit gate compares the context field by field, at the top level only. Therefore an event that changes any field emits the view again with the same viewKey. A provider refreshes /context in the live store, and it does not seed the store again. The component does not remount, and the ephemeral view state and the focus stay. However, a new emission is still a render pass in the framework layer. Keep high-frequency ephemeral data, such as a draft for each keystroke or a timer, in the view store (spec.state with $bindState) or in a child actor. The domain state belongs in the context. A keystroke does not.

import type { PlaySpec } from "@xmachines/play-view";
const spec: PlaySpec = {
root: "root",
elements: {
root: {
type: "Profile",
props: { username: { $state: "/context/username" } },
children: [],
},
},
};

Historical note: an earlier version had a contextProps field. At first the field drove an implicit prop-enrichment pass. That pass merged the allowlisted context fields and the URL params into the props of every element. We removed it, because it put values into components that never asked for them, and it let URL data from the user hide machine-owned state. The field was then a projection filter for a short time. We removed that filter too, because a limit on what a view can read added machinery without a real problem to solve. Always validate the derived view (actor.currentView.get()), not the raw meta.view. The state of the derived spec carries the projection, so a tool such as validateSpec sees a spec that is consistent with itself.

Viewable

Interface for actors that expose a renderable view signal.

import type { Viewable } from "@xmachines/play-view";
import type { PlaySpec } from "@xmachines/play-view";
import { Signal } from "@xmachines/play-signals";
// currentView carries PlaySpec | null
const signal = new Signal.State<PlaySpec | null>(null);
const viewable: Viewable = { currentView: signal };

BaseActorProviderProps<TRegistry>

The framework-agnostic base props. Every ActorProvider implementation shares them: React, Vue, Solid, and Svelte. Each framework renderer package extends this interface.

import type { BaseActorProviderProps } from "@xmachines/play-view";
import type { DefineRegistryResult } from "@xmachines/json-render-react";
interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {
fallback?: React.ReactNode;
children: React.ReactNode;
}

BaseViewContextValue<TRegistry>

The framework-agnostic base of the ViewContextValue type in each framework. It holds the spec, handlers, registry, and store fields. These fields are identical in React, Vue, Solid, and Svelte.

Documentation

License

MIT

Modules