@xmachines/docs 1.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/api/@xmachines/play/README.md +130 -0
- package/api/@xmachines/play/type-aliases/PlayEvent.md +81 -0
- package/api/@xmachines/play-actor/README.md +247 -0
- package/api/@xmachines/play-actor/classes/AbstractActor.md +520 -0
- package/api/@xmachines/play-actor/interfaces/Routable.md +29 -0
- package/api/@xmachines/play-actor/interfaces/ViewMetadata.md +17 -0
- package/api/@xmachines/play-actor/interfaces/Viewable.md +12 -0
- package/api/@xmachines/play-catalog/README.md +331 -0
- package/api/@xmachines/play-catalog/functions/defineCatalog.md +98 -0
- package/api/@xmachines/play-catalog/functions/defineComponents.md +134 -0
- package/api/@xmachines/play-catalog/type-aliases/Catalog.md +48 -0
- package/api/@xmachines/play-catalog/type-aliases/ComponentsFor.md +20 -0
- package/api/@xmachines/play-catalog/type-aliases/InferComponentProps.md +65 -0
- package/api/@xmachines/play-catalog/type-aliases/NoExtraKeys.md +17 -0
- package/api/@xmachines/play-react/README.md +423 -0
- package/api/@xmachines/play-react/classes/PlayErrorBoundary.md +613 -0
- package/api/@xmachines/play-react/functions/useSignalEffect.md +68 -0
- package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryProps.md +15 -0
- package/api/@xmachines/play-react/interfaces/PlayErrorBoundaryState.md +14 -0
- package/api/@xmachines/play-react/interfaces/PlayRendererProps.md +15 -0
- package/api/@xmachines/play-react/variables/PlayRenderer.md +64 -0
- package/api/@xmachines/play-react-router/README.md +198 -0
- package/api/@xmachines/play-react-router/classes/ReactRouterBridge.md +321 -0
- package/api/@xmachines/play-react-router/classes/RouteMap.md +137 -0
- package/api/@xmachines/play-react-router/functions/PlayRouterProvider.md +19 -0
- package/api/@xmachines/play-react-router/functions/createRouteMapFromTree.md +35 -0
- package/api/@xmachines/play-react-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-react-router/interfaces/PlayRouterProviderProps.md +14 -0
- package/api/@xmachines/play-react-router/interfaces/RouteMapping.md +17 -0
- package/api/@xmachines/play-react-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-react-router-demo/README.md +137 -0
- package/api/@xmachines/play-router/README.md +502 -0
- package/api/@xmachines/play-router/classes/BaseRouteMap.md +142 -0
- package/api/@xmachines/play-router/classes/RouterBridgeBase.md +300 -0
- package/api/@xmachines/play-router/functions/buildRouteTree.md +27 -0
- package/api/@xmachines/play-router/functions/connectRouter.md +67 -0
- package/api/@xmachines/play-router/functions/crawlMachine.md +92 -0
- package/api/@xmachines/play-router/functions/createBrowserHistory.md +47 -0
- package/api/@xmachines/play-router/functions/createRouteMap.md +53 -0
- package/api/@xmachines/play-router/functions/createRouter.md +76 -0
- package/api/@xmachines/play-router/functions/detectDuplicateRoutes.md +32 -0
- package/api/@xmachines/play-router/functions/extractMachineRoutes.md +64 -0
- package/api/@xmachines/play-router/functions/extractRoute.md +45 -0
- package/api/@xmachines/play-router/functions/findRouteById.md +37 -0
- package/api/@xmachines/play-router/functions/findRouteByPath.md +39 -0
- package/api/@xmachines/play-router/functions/getNavigableRoutes.md +35 -0
- package/api/@xmachines/play-router/functions/getRoutableRoutes.md +39 -0
- package/api/@xmachines/play-router/functions/routeExists.md +26 -0
- package/api/@xmachines/play-router/functions/validateRouteFormat.md +29 -0
- package/api/@xmachines/play-router/functions/validateStateExists.md +29 -0
- package/api/@xmachines/play-router/interfaces/BaseRouteMapping.md +27 -0
- package/api/@xmachines/play-router/interfaces/BrowserHistory.md +172 -0
- package/api/@xmachines/play-router/interfaces/BrowserWindow.md +69 -0
- package/api/@xmachines/play-router/interfaces/ConnectRouterOptions.md +13 -0
- package/api/@xmachines/play-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-router/interfaces/RouteInfo.md +19 -0
- package/api/@xmachines/play-router/interfaces/RouteMap.md +56 -0
- package/api/@xmachines/play-router/interfaces/RouteNode.md +21 -0
- package/api/@xmachines/play-router/interfaces/RouteObject.md +21 -0
- package/api/@xmachines/play-router/interfaces/RouteTree.md +20 -0
- package/api/@xmachines/play-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-router/interfaces/StateVisit.md +15 -0
- package/api/@xmachines/play-router/interfaces/VanillaRouter.md +28 -0
- package/api/@xmachines/play-router/type-aliases/RouteMetadata.md +11 -0
- package/api/@xmachines/play-router-demo/README.md +137 -0
- package/api/@xmachines/play-signals/README.md +176 -0
- package/api/@xmachines/play-signals/interfaces/ComputedOptions.md +34 -0
- package/api/@xmachines/play-signals/interfaces/SignalComputed.md +49 -0
- package/api/@xmachines/play-signals/interfaces/SignalOptions.md +35 -0
- package/api/@xmachines/play-signals/interfaces/SignalState.md +68 -0
- package/api/@xmachines/play-signals/interfaces/SignalWatcher.md +97 -0
- package/api/@xmachines/play-signals/namespaces/Signal/README.md +22 -0
- package/api/@xmachines/play-signals/namespaces/Signal/classes/Computed.md +52 -0
- package/api/@xmachines/play-signals/namespaces/Signal/classes/State.md +72 -0
- package/api/@xmachines/play-signals/namespaces/Signal/interfaces/Options.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/README.md +21 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/classes/Watcher.md +85 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/currentComputed.md +13 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/hasSinks.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/hasSources.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/introspectSinks.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/introspectSources.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/functions/untrack.md +25 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/variables/unwatched.md +9 -0
- package/api/@xmachines/play-signals/namespaces/Signal/namespaces/subtle/variables/watched.md +9 -0
- package/api/@xmachines/play-signals/namespaces/Signal/variables/isComputed.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/variables/isState.md +19 -0
- package/api/@xmachines/play-signals/namespaces/Signal/variables/isWatcher.md +19 -0
- package/api/@xmachines/play-signals/type-aliases/WatcherNotify.md +32 -0
- package/api/@xmachines/play-solid/README.md +311 -0
- package/api/@xmachines/play-solid/interfaces/PlayRendererProps.md +15 -0
- package/api/@xmachines/play-solid/variables/PlayRenderer.md +70 -0
- package/api/@xmachines/play-solid-router/README.md +666 -0
- package/api/@xmachines/play-solid-router/classes/RouteMap.md +150 -0
- package/api/@xmachines/play-solid-router/classes/SolidRouterBridge.md +347 -0
- package/api/@xmachines/play-solid-router/functions/PlayRouterProvider.md +19 -0
- package/api/@xmachines/play-solid-router/functions/createRouteMap.md +32 -0
- package/api/@xmachines/play-solid-router/interfaces/AbstractActor.md +486 -0
- package/api/@xmachines/play-solid-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-solid-router/interfaces/PlayRouterProviderProps.md +14 -0
- package/api/@xmachines/play-solid-router/interfaces/RouteMapping.md +14 -0
- package/api/@xmachines/play-solid-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-solid-router/type-aliases/RoutableActor.md +9 -0
- package/api/@xmachines/play-solid-router/type-aliases/SolidRouterHooks.md +51 -0
- package/api/@xmachines/play-solid-router-demo/README.md +127 -0
- package/api/@xmachines/play-tanstack-react-router/README.md +226 -0
- package/api/@xmachines/play-tanstack-react-router/classes/RouteMap.md +137 -0
- package/api/@xmachines/play-tanstack-react-router/classes/TanStackReactRouterBridge.md +348 -0
- package/api/@xmachines/play-tanstack-react-router/functions/PlayRouterProvider.md +19 -0
- package/api/@xmachines/play-tanstack-react-router/functions/createRouteMap.md +53 -0
- package/api/@xmachines/play-tanstack-react-router/functions/createRouteMapFromTree.md +35 -0
- package/api/@xmachines/play-tanstack-react-router/functions/extractParams.md +38 -0
- package/api/@xmachines/play-tanstack-react-router/functions/extractQueryParams.md +33 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/PlayRouterProviderProps.md +14 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/RouteMapping.md +17 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/RouteNavigateEvent.md +26 -0
- package/api/@xmachines/play-tanstack-react-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterInstance.md +9 -0
- package/api/@xmachines/play-tanstack-react-router/type-aliases/TanStackRouterLike.md +78 -0
- package/api/@xmachines/play-tanstack-react-router/variables/extractMachineRoutes.md +64 -0
- package/api/@xmachines/play-tanstack-react-router-demo/README.md +126 -0
- package/api/@xmachines/play-tanstack-solid-router/README.md +285 -0
- package/api/@xmachines/play-tanstack-solid-router/classes/RouteMap.md +150 -0
- package/api/@xmachines/play-tanstack-solid-router/classes/SolidRouterBridge.md +343 -0
- package/api/@xmachines/play-tanstack-solid-router/functions/PlayRouterProvider.md +19 -0
- package/api/@xmachines/play-tanstack-solid-router/functions/createRouteMap.md +32 -0
- package/api/@xmachines/play-tanstack-solid-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-tanstack-solid-router/interfaces/PlayRouterProviderProps.md +14 -0
- package/api/@xmachines/play-tanstack-solid-router/interfaces/RouteMapping.md +23 -0
- package/api/@xmachines/play-tanstack-solid-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/RoutableActor.md +9 -0
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterInstance.md +9 -0
- package/api/@xmachines/play-tanstack-solid-router/type-aliases/TanStackRouterLike.md +78 -0
- package/api/@xmachines/play-tanstack-solid-router-demo/README.md +126 -0
- package/api/@xmachines/play-vue/README.md +292 -0
- package/api/@xmachines/play-vue/interfaces/PlayRendererProps.md +14 -0
- package/api/@xmachines/play-vue/variables/PlayRenderer.md +9 -0
- package/api/@xmachines/play-vue-router/README.md +604 -0
- package/api/@xmachines/play-vue-router/classes/RouteMap.md +209 -0
- package/api/@xmachines/play-vue-router/classes/VueBaseRouteMap.md +201 -0
- package/api/@xmachines/play-vue-router/classes/VueRouterBridge.md +360 -0
- package/api/@xmachines/play-vue-router/functions/createRouteMap.md +19 -0
- package/api/@xmachines/play-vue-router/interfaces/PlayRouteEvent.md +119 -0
- package/api/@xmachines/play-vue-router/interfaces/RouteMapping.md +15 -0
- package/api/@xmachines/play-vue-router/interfaces/RouterBridge.md +104 -0
- package/api/@xmachines/play-vue-router/type-aliases/RoutableActor.md +9 -0
- package/api/@xmachines/play-vue-router/variables/PlayRouterProvider.md +67 -0
- package/api/@xmachines/play-vue-router-demo/README.md +133 -0
- package/api/@xmachines/play-xstate/README.md +512 -0
- package/api/@xmachines/play-xstate/classes/PlayerActor.md +527 -0
- package/api/@xmachines/play-xstate/functions/buildRouteUrl.md +43 -0
- package/api/@xmachines/play-xstate/functions/composeGuards.md +79 -0
- package/api/@xmachines/play-xstate/functions/composeGuardsOr.md +67 -0
- package/api/@xmachines/play-xstate/functions/definePlayer.md +127 -0
- package/api/@xmachines/play-xstate/functions/deriveRoute.md +109 -0
- package/api/@xmachines/play-xstate/functions/eventMatches.md +40 -0
- package/api/@xmachines/play-xstate/functions/formatPlayRouteTransitions.md +54 -0
- package/api/@xmachines/play-xstate/functions/hasContext.md +42 -0
- package/api/@xmachines/play-xstate/functions/isAbsoluteRoute.md +41 -0
- package/api/@xmachines/play-xstate/functions/mergeViewProps.md +26 -0
- package/api/@xmachines/play-xstate/functions/negateGuard.md +61 -0
- package/api/@xmachines/play-xstate/functions/stateMatches.md +25 -0
- package/api/@xmachines/play-xstate/functions/validateComponentBinding.md +39 -0
- package/api/@xmachines/play-xstate/functions/validateViewProps.md +80 -0
- package/api/@xmachines/play-xstate/interfaces/CatalogEntry.md +16 -0
- package/api/@xmachines/play-xstate/interfaces/PlayerConfig.md +24 -0
- package/api/@xmachines/play-xstate/interfaces/PlayerOptions.md +26 -0
- package/api/@xmachines/play-xstate/interfaces/RouteContext.md +22 -0
- package/api/@xmachines/play-xstate/type-aliases/Catalog.md +21 -0
- package/api/@xmachines/play-xstate/type-aliases/ComposedGuard.md +14 -0
- package/api/@xmachines/play-xstate/type-aliases/Guard.md +34 -0
- package/api/@xmachines/play-xstate/type-aliases/GuardArray.md +20 -0
- package/api/@xmachines/play-xstate/type-aliases/PlayerFactory.md +29 -0
- package/api/@xmachines/play-xstate/type-aliases/RouteMachineConfig.md +45 -0
- package/api/@xmachines/play-xstate/type-aliases/RouteStateNode.md +51 -0
- package/api/@xmachines/play-xstate/type-aliases/ValidationResult.md +17 -0
- package/api/@xmachines/play-xstate/type-aliases/ViewMergeContext.md +35 -0
- package/api/@xmachines/shared/README.md +379 -0
- package/api/@xmachines/shared/functions/defineXmVitestConfig.md +29 -0
- package/api/@xmachines/shared/functions/xmAliases.md +24 -0
- package/api/README.md +25 -0
- package/api/llms.txt +26 -0
- package/examples/README.md +63 -0
- package/examples/basic-state-machine.md +70 -0
- package/examples/form-validation.md +167 -0
- package/examples/multi-router-integration.md +277 -0
- package/examples/routing-patterns.md +260 -0
- package/examples/traffic-light.md +99 -0
- package/guides/README.md +29 -0
- package/guides/getting-started.md +223 -0
- package/guides/installation.md +323 -0
- package/index.d.ts +3 -0
- package/index.js +4 -0
- package/package.json +54 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[Documentation](../../../README.md) / [@xmachines/play-catalog](../README.md) / NoExtraKeys
|
|
2
|
+
|
|
3
|
+
# Type Alias: NoExtraKeys\<TComponents, TCatalog\>
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
type NoExtraKeys<TComponents, TCatalog> = TComponents &
|
|
7
|
+
Record<Exclude<keyof TComponents, keyof TCatalog>, never>;
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Defined in: [define-components.ts:14](https://gitlab.com/xmachin-es/xmachines-js/-/blob/00a28432ed57807112288436d1ae4387d9f06919/packages/play-catalog/src/define-components.ts#L14)
|
|
11
|
+
|
|
12
|
+
## Type Parameters
|
|
13
|
+
|
|
14
|
+
| Type Parameter |
|
|
15
|
+
| ------------------------------------------------------ |
|
|
16
|
+
| `TComponents` |
|
|
17
|
+
| `TCatalog` _extends_ `Record`\<`string`, `z.ZodType`\> |
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
[Documentation](../../README.md) / @xmachines/play-react
|
|
2
|
+
|
|
3
|
+
# @xmachines/play-react
|
|
4
|
+
|
|
5
|
+
**React renderer consuming signals and UI schema with provider pattern**
|
|
6
|
+
|
|
7
|
+
Signal-driven React rendering layer observing actor state with zero React state for business logic.
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
`@xmachines/play-react` provides `PlayRenderer` and `useSignalEffect` for building React UIs that passively observe actor signals. This package enables framework-swappable architecture where React is just a rendering target subscribing to signal changes — business logic lives entirely in the actor.
|
|
12
|
+
|
|
13
|
+
Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
|
|
14
|
+
|
|
15
|
+
- **Signal-Only Reactivity (INV-05):** No useState/useReducer for business logic, signals only
|
|
16
|
+
- **Passive Infrastructure (INV-04):** Components observe signals, send events to actor
|
|
17
|
+
|
|
18
|
+
**Key Principle:** React state is never used for business logic. Signals are the source of truth.
|
|
19
|
+
|
|
20
|
+
Renderer receives actor via props (provider pattern), not children.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install react@^18.0.0 react-dom@^18.0.0
|
|
26
|
+
npm install @xmachines/play-react
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Current Exports
|
|
30
|
+
|
|
31
|
+
- `PlayRenderer`
|
|
32
|
+
- `useSignalEffect`
|
|
33
|
+
- `PlayErrorBoundary`
|
|
34
|
+
- `PlayRendererProps` (type)
|
|
35
|
+
- `PlayErrorBoundaryProps` (type)
|
|
36
|
+
|
|
37
|
+
**Peer dependencies:**
|
|
38
|
+
|
|
39
|
+
- `react` ^18.0.0 || ^19.0.0 — React runtime
|
|
40
|
+
- `react-dom` ^18.0.0 || ^19.0.0 — React DOM renderer
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createRoot } from "react-dom/client";
|
|
46
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
47
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
48
|
+
import { PlayRenderer } from "@xmachines/play-react";
|
|
49
|
+
import { z } from "zod";
|
|
50
|
+
|
|
51
|
+
// 1. Define catalog (business logic layer)
|
|
52
|
+
const catalog = defineCatalog({
|
|
53
|
+
LoginForm: z.object({ error: z.string().optional() }),
|
|
54
|
+
Dashboard: z.object({
|
|
55
|
+
userId: z.string(),
|
|
56
|
+
username: z.string(),
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 2. Create React components (view layer)
|
|
61
|
+
const components = {
|
|
62
|
+
LoginForm: ({ error, send }) => (
|
|
63
|
+
<form
|
|
64
|
+
onSubmit={(e) => {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
const data = new FormData(e.currentTarget);
|
|
67
|
+
send({
|
|
68
|
+
type: "auth.login",
|
|
69
|
+
username: data.get("username"),
|
|
70
|
+
});
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
{error && <p style={{ color: "red" }}>{error}</p>}
|
|
74
|
+
<input name="username" required placeholder="Username" />
|
|
75
|
+
<button type="submit">Log In</button>
|
|
76
|
+
</form>
|
|
77
|
+
),
|
|
78
|
+
Dashboard: ({ userId, username, send }) => (
|
|
79
|
+
<div>
|
|
80
|
+
<h1>Welcome, {username}!</h1>
|
|
81
|
+
<p>User ID: {userId}</p>
|
|
82
|
+
<button onClick={() => send({ type: "auth.logout" })}>Log Out</button>
|
|
83
|
+
</div>
|
|
84
|
+
),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// 3. Create player actor (business logic runtime)
|
|
88
|
+
const createPlayer = definePlayer({ machine: authMachine, catalog });
|
|
89
|
+
const actor = createPlayer();
|
|
90
|
+
actor.start();
|
|
91
|
+
|
|
92
|
+
// 4. Render UI (actor via props)
|
|
93
|
+
const root = createRoot(document.getElementById("app")!);
|
|
94
|
+
root.render(<PlayRenderer actor={actor} components={components} />);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## API Reference
|
|
98
|
+
|
|
99
|
+
### PlayRenderer
|
|
100
|
+
|
|
101
|
+
Main renderer component subscribing to actor signals and dynamically rendering catalog components:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
interface PlayRendererProps {
|
|
105
|
+
actor: AbstractActor<AnyActorLogic> & Viewable;
|
|
106
|
+
components: Record<string, React.ElementType>;
|
|
107
|
+
fallback?: React.ReactNode;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Props:**
|
|
112
|
+
|
|
113
|
+
- `actor` - Actor instance with `currentView` signal
|
|
114
|
+
- `components` - Map of component names to React components
|
|
115
|
+
- `fallback` - Component shown when `currentView` is null (default: `null`)
|
|
116
|
+
|
|
117
|
+
**Behavior:**
|
|
118
|
+
|
|
119
|
+
1. Subscribes to `actor.currentView` signal via `useSignalEffect`
|
|
120
|
+
2. Looks up component from `components` map using `view.component` string
|
|
121
|
+
3. Renders component with props from `view.props` + `send` function
|
|
122
|
+
|
|
123
|
+
**Example:**
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
<PlayRenderer
|
|
127
|
+
actor={actor}
|
|
128
|
+
components={{
|
|
129
|
+
HomePage: ({ send }) => <div>Home</div>,
|
|
130
|
+
AboutPage: ({ send }) => <div>About</div>,
|
|
131
|
+
}}
|
|
132
|
+
fallback={<div>Loading...</div>}
|
|
133
|
+
/>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### useSignalEffect()
|
|
137
|
+
|
|
138
|
+
Hook for subscribing to signal changes with automatic cleanup:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
useSignalEffect(() => {
|
|
142
|
+
const value = signal.get();
|
|
143
|
+
// React re-renders when signal changes
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Behavior:**
|
|
148
|
+
|
|
149
|
+
- Tracks signal dependencies automatically via `Signal.Computed` wrapper
|
|
150
|
+
- Uses `Signal.subtle.Watcher` with microtask batching
|
|
151
|
+
- Triggers React state update to force re-render
|
|
152
|
+
- Cleans up watcher on unmount with explicit `unwatch`
|
|
153
|
+
|
|
154
|
+
**Canonical watcher lifecycle:**
|
|
155
|
+
|
|
156
|
+
1. `notify`
|
|
157
|
+
2. `queueMicrotask`
|
|
158
|
+
3. drain pending work (`getPending` and/or `Computed.get`)
|
|
159
|
+
4. run effect + trigger render
|
|
160
|
+
5. re-arm watcher via `watch()`
|
|
161
|
+
|
|
162
|
+
Watcher notification is one-shot, so re-arm and explicit cleanup are both required.
|
|
163
|
+
|
|
164
|
+
**Example:**
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { useSignalEffect } from "@xmachines/play-react";
|
|
168
|
+
import { useState } from "react";
|
|
169
|
+
|
|
170
|
+
function RouteDisplay({ actor }: { actor: AbstractActor<any> }) {
|
|
171
|
+
const [route, setRoute] = useState<string | null>(null);
|
|
172
|
+
|
|
173
|
+
useSignalEffect(() => {
|
|
174
|
+
const currentRoute = actor.currentRoute.get();
|
|
175
|
+
setRoute(currentRoute);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return <div>Current Route: {route ?? "None"}</div>;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### PlayErrorBoundary
|
|
183
|
+
|
|
184
|
+
React class component error boundary for catching catalog component render errors.
|
|
185
|
+
|
|
186
|
+
`PlayRenderer` wraps its render output in `PlayErrorBoundary` automatically. You can also use it directly to wrap any component that may throw during render.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
interface PlayErrorBoundaryProps {
|
|
190
|
+
fallback?: React.ReactNode; // UI shown when a child throws (default: null)
|
|
191
|
+
children: React.ReactNode;
|
|
192
|
+
onError?: (error: Error, info: React.ErrorInfo) => void; // Forward to Sentry, Datadog, etc.
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Props:**
|
|
197
|
+
|
|
198
|
+
- `fallback` — ReactNode rendered when a child component throws. Defaults to `null`.
|
|
199
|
+
- `onError` — Optional callback forwarded on every caught error. Use for production observability (Sentry, Datadog, custom logging).
|
|
200
|
+
|
|
201
|
+
**Example:**
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
import { PlayErrorBoundary } from "@xmachines/play-react";
|
|
205
|
+
|
|
206
|
+
<PlayErrorBoundary
|
|
207
|
+
fallback={<div className="error">Something went wrong.</div>}
|
|
208
|
+
onError={(error) => Sentry.captureException(error)}
|
|
209
|
+
>
|
|
210
|
+
<YourCatalogComponent />
|
|
211
|
+
</PlayErrorBoundary>;
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Works with React 18 and React 19. Uses the standard class component `componentDidCatch` + `getDerivedStateFromError` pattern.
|
|
215
|
+
|
|
216
|
+
## Examples
|
|
217
|
+
|
|
218
|
+
### Component Receiving Props from Catalog
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { PlayRenderer } from "@xmachines/play-react";
|
|
222
|
+
import { defineCatalog } from "@xmachines/play-catalog";
|
|
223
|
+
import { z } from "zod";
|
|
224
|
+
|
|
225
|
+
// Define schema in catalog
|
|
226
|
+
const catalog = defineCatalog({
|
|
227
|
+
UserProfile: z.object({
|
|
228
|
+
userId: z.string(),
|
|
229
|
+
name: z.string(),
|
|
230
|
+
avatar: z.string().url().optional(),
|
|
231
|
+
stats: z.object({
|
|
232
|
+
posts: z.number(),
|
|
233
|
+
followers: z.number(),
|
|
234
|
+
}),
|
|
235
|
+
}),
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Component receives type-safe props + send
|
|
239
|
+
const components = {
|
|
240
|
+
UserProfile: ({ userId, name, avatar, stats, send }) => (
|
|
241
|
+
<div>
|
|
242
|
+
{avatar && <img src={avatar} alt={name} />}
|
|
243
|
+
<h1>{name}</h1>
|
|
244
|
+
<p>ID: {userId}</p>
|
|
245
|
+
<div>
|
|
246
|
+
<span>{stats.posts} posts</span>
|
|
247
|
+
<span>{stats.followers} followers</span>
|
|
248
|
+
</div>
|
|
249
|
+
<button
|
|
250
|
+
onClick={() =>
|
|
251
|
+
send({
|
|
252
|
+
type: "profile.edit",
|
|
253
|
+
userId,
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
>
|
|
257
|
+
Edit Profile
|
|
258
|
+
</button>
|
|
259
|
+
</div>
|
|
260
|
+
),
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
<PlayRenderer actor={actor} components={components} />;
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### useSignalEffect for Custom Rendering
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
import { useSignalEffect } from "@xmachines/play-react";
|
|
270
|
+
import { AbstractActor } from "@xmachines/play-actor";
|
|
271
|
+
|
|
272
|
+
function CustomRenderer({ actor }: { actor: AbstractActor<any> }) {
|
|
273
|
+
const [view, setView] = useState(null);
|
|
274
|
+
|
|
275
|
+
// Subscribe to currentView signal
|
|
276
|
+
useSignalEffect(() => {
|
|
277
|
+
const currentView = actor.currentView.get();
|
|
278
|
+
setView(currentView);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (!view) return <div>No view</div>;
|
|
282
|
+
|
|
283
|
+
// Custom rendering logic
|
|
284
|
+
if (view.component === "SpecialCase") {
|
|
285
|
+
return <SpecialCaseComponent {...view.props} actor={actor} />;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Fallback to standard rendering
|
|
289
|
+
return <DefaultComponent view={view} actor={actor} />;
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Provider Pattern
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { PlayTanStackRouterProvider } from "@xmachines/play-tanstack-react-router";
|
|
297
|
+
import { PlayRenderer } from "@xmachines/play-react";
|
|
298
|
+
|
|
299
|
+
// Renderer receives actor via props (not children)
|
|
300
|
+
function App() {
|
|
301
|
+
return (
|
|
302
|
+
<PlayTanStackRouterProvider
|
|
303
|
+
actor={actor}
|
|
304
|
+
router={router}
|
|
305
|
+
renderer={(currentActor, currentRouter) => {
|
|
306
|
+
void currentRouter;
|
|
307
|
+
return (
|
|
308
|
+
<div>
|
|
309
|
+
<Header actor={currentActor} />
|
|
310
|
+
<PlayRenderer actor={currentActor} components={components} />
|
|
311
|
+
<Footer />
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
}}
|
|
315
|
+
/>
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Header component also receives actor
|
|
320
|
+
function Header({ actor }: { actor: AbstractActor<any> }) {
|
|
321
|
+
const [route, setRoute] = useState<string | null>(null);
|
|
322
|
+
|
|
323
|
+
useSignalEffect(() => {
|
|
324
|
+
setRoute(actor.currentRoute.get());
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<header>
|
|
329
|
+
<nav>Current: {route}</nav>
|
|
330
|
+
</header>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Architecture
|
|
336
|
+
|
|
337
|
+
This package implements **Signal-Only Reactivity (INV-05)** and **Passive Infrastructure (INV-04)**:
|
|
338
|
+
|
|
339
|
+
1. **No Business Logic in React:**
|
|
340
|
+
- No useState/useReducer for business state
|
|
341
|
+
- No useEffect for side effects
|
|
342
|
+
- React only triggers renders, doesn't control state
|
|
343
|
+
|
|
344
|
+
2. **Signals as Source of Truth:**
|
|
345
|
+
- `actor.currentView.get()` provides UI structure
|
|
346
|
+
- `actor.currentRoute.get()` provides navigation state
|
|
347
|
+
- Components observe signals via `useSignalEffect`
|
|
348
|
+
|
|
349
|
+
3. **Event Forwarding:**
|
|
350
|
+
- Components receive `send` function via props
|
|
351
|
+
- User actions send events to actor (e.g., `{ type: "auth.login" }`)
|
|
352
|
+
- Actor guards validate and process events
|
|
353
|
+
|
|
354
|
+
4. **Microtask Batching:**
|
|
355
|
+
- `Signal.subtle.Watcher` coalesces rapid signal changes
|
|
356
|
+
- Prevents React thrashing from multiple signal updates
|
|
357
|
+
- Single React render per microtask batch
|
|
358
|
+
|
|
359
|
+
5. **Explicit Disposal Contract:**
|
|
360
|
+
- Component teardown must call watcher `unwatch` in cleanup
|
|
361
|
+
- Do not rely on GC-only cleanup
|
|
362
|
+
|
|
363
|
+
**Pattern:**
|
|
364
|
+
|
|
365
|
+
- Renderer receives actor via props (provider pattern)
|
|
366
|
+
- Enables composition with navigation, headers, footers
|
|
367
|
+
- Supports multiple renderers in same app
|
|
368
|
+
|
|
369
|
+
**Architectural Invariants:**
|
|
370
|
+
|
|
371
|
+
- **Signal-Only Reactivity (INV-05):** No React state for business logic
|
|
372
|
+
- **Passive Infrastructure (INV-04):** Components reflect, never decide
|
|
373
|
+
|
|
374
|
+
## Benefits
|
|
375
|
+
|
|
376
|
+
- **Framework Swappable:** Business logic has zero React imports
|
|
377
|
+
- **Type Safety:** Props validated against catalog schemas
|
|
378
|
+
- **Simple Testing:** Test actors without React renderer
|
|
379
|
+
- **Performance:** Microtask batching reduces unnecessary renders
|
|
380
|
+
- **Composability:** Renderer prop enables complex layouts ()
|
|
381
|
+
|
|
382
|
+
## Related Packages
|
|
383
|
+
|
|
384
|
+
- **[@xmachines/play-xstate](../play-xstate/README.md)** - XState adapter providing actors
|
|
385
|
+
- **[@xmachines/play-catalog](../play-catalog/README.md)** - UI schema validation
|
|
386
|
+
- **[@xmachines/play-tanstack-react-router](../play-tanstack-react-router/README.md)** - TanStack Router integration
|
|
387
|
+
- **[@xmachines/play-actor](../play-actor/README.md)** - Actor base
|
|
388
|
+
- **[@xmachines/play-signals](../play-signals/README.md)** - TC39 Signals primitives
|
|
389
|
+
|
|
390
|
+
## License
|
|
391
|
+
|
|
392
|
+
Copyright (c) 2016 [Mikael Karon](mailto:mikael@karon.se). All rights reserved.
|
|
393
|
+
|
|
394
|
+
This work is licensed under the terms of the MIT license.
|
|
395
|
+
For a copy, see <https://opensource.org/licenses/MIT>.
|
|
396
|
+
|
|
397
|
+
@xmachines/play-react - React renderer for XMachines Play architecture
|
|
398
|
+
|
|
399
|
+
Provides a thin React rendering layer that passively observes actor signals
|
|
400
|
+
and renders UI components from catalog definitions. This package enables
|
|
401
|
+
framework-swappable architecture where React is just a rendering target
|
|
402
|
+
that subscribes to signal changes.
|
|
403
|
+
|
|
404
|
+
**Key principle:** React state is NEVER used for business logic—only for
|
|
405
|
+
triggering React's render cycle. Signals are the source of truth.
|
|
406
|
+
|
|
407
|
+
## Classes
|
|
408
|
+
|
|
409
|
+
- [PlayErrorBoundary](classes/PlayErrorBoundary.md)
|
|
410
|
+
|
|
411
|
+
## Interfaces
|
|
412
|
+
|
|
413
|
+
- [PlayErrorBoundaryProps](interfaces/PlayErrorBoundaryProps.md)
|
|
414
|
+
- [PlayErrorBoundaryState](interfaces/PlayErrorBoundaryState.md)
|
|
415
|
+
- [PlayRendererProps](interfaces/PlayRendererProps.md)
|
|
416
|
+
|
|
417
|
+
## Variables
|
|
418
|
+
|
|
419
|
+
- [PlayRenderer](variables/PlayRenderer.md)
|
|
420
|
+
|
|
421
|
+
## Functions
|
|
422
|
+
|
|
423
|
+
- [useSignalEffect](functions/useSignalEffect.md)
|