@xmachines/play-solid 1.0.0-beta.16 → 1.0.0-beta.17

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.
Files changed (54) hide show
  1. package/README.md +174 -220
  2. package/dist/PlayRenderer.js +52 -24
  3. package/dist/PlayRenderer.js.map +1 -1
  4. package/dist/index.js +4 -2
  5. package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js +111 -0
  6. package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js.map +1 -0
  7. package/dist/node_modules/@json-render/core/dist/index.js +863 -0
  8. package/dist/node_modules/@json-render/core/dist/index.js.map +1 -0
  9. package/dist/node_modules/@json-render/core/dist/store-utils.js +1 -0
  10. package/dist/node_modules/@json-render/solid/dist/chunk-XTSB6FIM.js +57 -0
  11. package/dist/node_modules/@json-render/solid/dist/chunk-XTSB6FIM.js.map +1 -0
  12. package/dist/node_modules/@json-render/solid/dist/index.js +617 -0
  13. package/dist/node_modules/@json-render/solid/dist/index.js.map +1 -0
  14. package/dist/node_modules/@json-render/xstate/dist/index.js +20 -0
  15. package/dist/node_modules/@json-render/xstate/dist/index.js.map +1 -0
  16. package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js +227 -0
  17. package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js.map +1 -0
  18. package/dist/node_modules/zod/v4/classic/errors.js +25 -0
  19. package/dist/node_modules/zod/v4/classic/errors.js.map +1 -0
  20. package/dist/node_modules/zod/v4/classic/iso.js +33 -0
  21. package/dist/node_modules/zod/v4/classic/iso.js.map +1 -0
  22. package/dist/node_modules/zod/v4/classic/parse.js +8 -0
  23. package/dist/node_modules/zod/v4/classic/parse.js.map +1 -0
  24. package/dist/node_modules/zod/v4/classic/schemas.js +362 -0
  25. package/dist/node_modules/zod/v4/classic/schemas.js.map +1 -0
  26. package/dist/node_modules/zod/v4/core/api.js +530 -0
  27. package/dist/node_modules/zod/v4/core/api.js.map +1 -0
  28. package/dist/node_modules/zod/v4/core/checks.js +285 -0
  29. package/dist/node_modules/zod/v4/core/checks.js.map +1 -0
  30. package/dist/node_modules/zod/v4/core/core.js +46 -0
  31. package/dist/node_modules/zod/v4/core/core.js.map +1 -0
  32. package/dist/node_modules/zod/v4/core/doc.js +25 -0
  33. package/dist/node_modules/zod/v4/core/doc.js.map +1 -0
  34. package/dist/node_modules/zod/v4/core/errors.js +43 -0
  35. package/dist/node_modules/zod/v4/core/errors.js.map +1 -0
  36. package/dist/node_modules/zod/v4/core/json-schema-processors.js +183 -0
  37. package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +1 -0
  38. package/dist/node_modules/zod/v4/core/parse.js +70 -0
  39. package/dist/node_modules/zod/v4/core/parse.js.map +1 -0
  40. package/dist/node_modules/zod/v4/core/regexes.js +27 -0
  41. package/dist/node_modules/zod/v4/core/regexes.js.map +1 -0
  42. package/dist/node_modules/zod/v4/core/registries.js +42 -0
  43. package/dist/node_modules/zod/v4/core/registries.js.map +1 -0
  44. package/dist/node_modules/zod/v4/core/schemas.js +823 -0
  45. package/dist/node_modules/zod/v4/core/schemas.js.map +1 -0
  46. package/dist/node_modules/zod/v4/core/to-json-schema.js +224 -0
  47. package/dist/node_modules/zod/v4/core/to-json-schema.js.map +1 -0
  48. package/dist/node_modules/zod/v4/core/util.js +268 -0
  49. package/dist/node_modules/zod/v4/core/util.js.map +1 -0
  50. package/dist/node_modules/zod/v4/core/versions.js +10 -0
  51. package/dist/node_modules/zod/v4/core/versions.js.map +1 -0
  52. package/dist/useActor.js +12 -0
  53. package/dist/useActor.js.map +1 -0
  54. package/package.json +14 -5
package/README.md CHANGED
@@ -1,299 +1,253 @@
1
1
  # @xmachines/play-solid
2
2
 
3
- **SolidJS renderer consuming signals and UI schema with provider pattern**
3
+ **SolidJS renderer for XMachines Play Architecture**
4
4
 
5
- Signal-driven SolidJS rendering layer observing actor state with zero SolidJS state for business logic.
5
+ Bridges TC39 Signal-driven actors to SolidJS's fine-grained reactivity. Business logic stays in the actor; Solid is purely a rendering target.
6
6
 
7
7
  ## Overview
8
8
 
9
- `@xmachines/play-solid` provides `PlayRenderer` for building SolidJS UIs that passively observe actor signals. This package enables framework-swappable architecture where SolidJS is just a rendering target subscribing to signal changes — business logic lives entirely in the actor.
9
+ `@xmachines/play-solid` provides `PlayRenderer`, a Solid component that:
10
10
 
11
- Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
11
+ - Subscribes to `actor.currentView` (TC39 Signal) and re-renders on every state transition
12
+ - Renders the current view's JSON spec via `@json-render/solid`
13
+ - Routes action names from spec elements to `actor.send()` via the `actions` prop
14
+ - Manages per-view UI state in an `@xstate/store` atom (automatic or caller-supplied)
12
15
 
13
- - **Signal-Only Reactivity (INV-05):** No createSignal/createStore for business logic, TC39 signals only
14
- - **Passive Infrastructure (INV-04):** Components observe signals, send events to actor
16
+ Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md):
15
17
 
16
- **Key Principle:** SolidJS state is never used for business logic. Signals are the source of truth.
17
-
18
- Renderer receives actor via props (provider pattern), not children.
18
+ - **Actor Authority (INV-01):** Guards in the machine decide all state transitions
19
+ - **Passive Infrastructure (INV-04):** Solid observes signals and dispatches events — never decides
20
+ - **Signal-Only Reactivity (INV-05):** `actor.currentView` signal is the sole render trigger
19
21
 
20
22
  ## Installation
21
23
 
22
24
  ```bash
23
- npm install solid-js@^1.8.0
24
25
  npm install @xmachines/play-solid
26
+ npm install @json-render/solid @json-render/core # peer deps
27
+ npm install @json-render/xstate @xstate/store # store integration
25
28
  ```
26
29
 
27
30
  ## Current Exports
28
31
 
29
- - `PlayRenderer`
32
+ - `PlayRenderer` — main renderer component
33
+ - `useActor` — hook for accessing the actor inside a `PlayRenderer` tree
34
+ - `defineRegistry` — re-exported from `@json-render/solid`
35
+ - `useStateBinding` — re-exported from `@json-render/solid`
36
+ - `ComponentFn` (type) — re-exported from `@json-render/solid`
37
+ - `ComponentContext` (type) — re-exported from `@json-render/solid`
30
38
  - `PlayRendererProps` (type)
31
-
32
- **Peer dependencies:**
33
-
34
- - `solid-js` ^1.8.0 - SolidJS runtime
39
+ - `PlayActor` (type)
35
40
 
36
41
  ## Quick Start
37
42
 
38
43
  ```tsx
39
- import { render } from "solid-js/web";
40
- import { definePlayer } from "@xmachines/play-xstate";
41
- import { defineCatalog } from "@xmachines/play-catalog";
44
+ import { definePlayer, formatPlayRouteTransitions } from "@xmachines/play-xstate";
42
45
  import { PlayRenderer } from "@xmachines/play-solid";
46
+ import { defineCatalog } from "@json-render/core";
47
+ import { defineRegistry } from "@xmachines/play-solid";
48
+ import type { ComponentFn } from "@xmachines/play-solid";
49
+ import { setup, assign } from "xstate";
43
50
  import { z } from "zod";
44
51
 
45
- // 1. Define catalog (business logic layer)
52
+ // 1. Define catalog the contract between machine spec and UI components
46
53
  const catalog = defineCatalog({
47
- LoginForm: z.object({ error: z.string().optional() }),
48
- Dashboard: z.object({
49
- userId: z.string(),
50
- username: z.string(),
51
- }),
54
+ elements: {
55
+ Login: { props: z.object({ title: z.string() }), description: "Login form" },
56
+ Dashboard: { props: z.object({ username: z.string() }), description: "Dashboard" },
57
+ },
52
58
  });
53
59
 
54
- // 2. Create SolidJS components (view layer)
55
- const components = {
56
- LoginForm: (props) => (
60
+ // 2. Implement components using ComponentFn — typed against catalog entries
61
+ const Login: ComponentFn<typeof catalog, "Login"> = ({ props, emit }) => (
62
+ <div class="view">
63
+ <h2>{props.title}</h2>
57
64
  <form
58
65
  onSubmit={(e) => {
59
66
  e.preventDefault();
60
- const data = new FormData(e.currentTarget);
61
- props.send({
62
- type: "auth.login",
63
- username: data.get("username"),
64
- });
67
+ emit("submit");
65
68
  }}
66
69
  >
67
- {props.error && <p style={{ color: "red" }}>{props.error}</p>}
68
- <input name="username" required placeholder="Username" />
69
70
  <button type="submit">Log In</button>
70
71
  </form>
71
- ),
72
- Dashboard: (props) => (
73
- <div>
74
- <h1>Welcome, {props.username}!</h1>
75
- <p>User ID: {props.userId}</p>
76
- <button onClick={() => props.send({ type: "auth.logout" })}>Log Out</button>
77
- </div>
78
- ),
79
- };
80
-
81
- // 3. Create player actor (business logic runtime)
82
- const createPlayer = definePlayer({ machine: authMachine, catalog });
83
- const actor = createPlayer();
84
- actor.start();
72
+ </div>
73
+ );
85
74
 
86
- // 4. Render UI (actor via props)
87
- render(
88
- () => <PlayRenderer actor={actor} components={components} />,
89
- document.getElementById("app")!,
75
+ const Dashboard: ComponentFn<typeof catalog, "Dashboard"> = ({ props }) => (
76
+ <div class="view">Welcome, {props.username}!</div>
90
77
  );
91
- ```
92
78
 
93
- ## API Reference
79
+ // 3. Build registry
80
+ const { registry } = defineRegistry(catalog, {
81
+ components: { Login, Dashboard },
82
+ actions: { login: async () => {}, logout: async () => {} },
83
+ });
94
84
 
95
- ### PlayRenderer
85
+ // 4. Define machine with view metadata
86
+ const machine = setup({
87
+ types: {
88
+ context: {} as {
89
+ isAuthenticated: boolean;
90
+ username: string | null;
91
+ routeParams: Record<string, string>;
92
+ queryParams: Record<string, string>;
93
+ },
94
+ events: {} as
95
+ | { type: "auth.login"; username: string }
96
+ | { type: "auth.logout" }
97
+ | { type: "play.route"; to: string; params?: Record<string, string> },
98
+ },
99
+ }).createMachine(
100
+ formatPlayRouteTransitions({
101
+ id: "app",
102
+ initial: "login",
103
+ context: { isAuthenticated: false, username: null, routeParams: {}, queryParams: {} },
104
+ states: {
105
+ login: {
106
+ id: "login",
107
+ meta: {
108
+ route: "/login",
109
+ view: {
110
+ component: "Login",
111
+ spec: {
112
+ root: "root",
113
+ elements: {
114
+ root: { type: "Login", props: { title: "Sign In" }, children: [] },
115
+ },
116
+ },
117
+ },
118
+ },
119
+ },
120
+ dashboard: {
121
+ id: "dashboard",
122
+ meta: {
123
+ route: "/dashboard",
124
+ view: {
125
+ component: "Dashboard",
126
+ spec: {
127
+ root: "root",
128
+ elements: {
129
+ root: { type: "Dashboard", props: { username: "" }, children: [] },
130
+ },
131
+ },
132
+ },
133
+ },
134
+ },
135
+ },
136
+ on: {
137
+ "auth.login": {
138
+ target: ".dashboard",
139
+ guard: ({ context }) => !context.isAuthenticated,
140
+ actions: assign({ isAuthenticated: true, username: ({ event }) => event.username }),
141
+ },
142
+ "auth.logout": {
143
+ target: ".login",
144
+ guard: ({ context }) => context.isAuthenticated,
145
+ actions: assign({ isAuthenticated: false, username: null }),
146
+ },
147
+ },
148
+ }),
149
+ );
96
150
 
97
- Main renderer component subscribing to actor signals and dynamically rendering catalog components:
151
+ // 5. Create actor and render
152
+ const createPlayer = definePlayer({ machine });
153
+ const actor = createPlayer();
154
+ actor.start();
98
155
 
99
- ```typescript
100
- interface PlayRendererProps {
101
- actor: AbstractActor<any>;
102
- components: Record<string, Component<any>>;
103
- fallback?: JSX.Element;
156
+ function App() {
157
+ return (
158
+ <PlayRenderer
159
+ actor={actor}
160
+ registry={registry}
161
+ actions={{ login: "auth.login", logout: "auth.logout" }}
162
+ />
163
+ );
104
164
  }
105
165
  ```
106
166
 
107
- **Props:**
108
-
109
- - `actor` - Actor instance with `currentView` signal
110
- - `components` - Map of component names to SolidJS components
111
- - `fallback` - Component shown when `currentView` is null
112
-
113
- **Behavior:**
167
+ ## API Reference
114
168
 
115
- 1. Subscribes to `actor.currentView` signal using a `Signal.subtle.Watcher` inside the component
116
- 2. Looks up component from `components` map using `view.component` string
117
- 3. Renders component with props from `view.props` + `send` function using Solid's `<Dynamic />`
169
+ ### `PlayRenderer`
118
170
 
119
- **Example:**
171
+ Main component. Subscribes to `actor.currentView` and renders the spec.
120
172
 
121
173
  ```tsx
122
174
  <PlayRenderer
123
175
  actor={actor}
124
- components={{
125
- HomePage: (props) => <div>Home</div>,
126
- AboutPage: (props) => <div>About</div>,
127
- }}
128
- fallback={<div>Loading...</div>}
176
+ registry={registry}
177
+ actions={{ login: "auth.login" }}
178
+ store={myStore}
179
+ fallback={<p>Loading…</p>}
129
180
  />
130
181
  ```
131
182
 
132
- ## Examples
133
-
134
- ### Component Receiving Props from Catalog
135
-
136
- ```tsx
137
- import { PlayRenderer } from "@xmachines/play-solid";
138
- import { defineCatalog } from "@xmachines/play-catalog";
139
- import { z } from "zod";
140
-
141
- // Define schema in catalog
142
- const catalog = defineCatalog({
143
- UserProfile: z.object({
144
- userId: z.string(),
145
- name: z.string(),
146
- avatar: z.string().url().optional(),
147
- stats: z.object({
148
- posts: z.number(),
149
- followers: z.number(),
150
- }),
151
- }),
152
- });
183
+ **`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
153
184
 
154
- // Component receives type-safe props + send
155
- const components = {
156
- UserProfile: (props) => (
157
- <div>
158
- {props.avatar && <img src={props.avatar} alt={props.name} />}
159
- <h1>{props.name}</h1>
160
- <p>ID: {props.userId}</p>
161
- <div>
162
- <span>{props.stats.posts} posts</span>
163
- <span>{props.stats.followers} followers</span>
164
- </div>
165
- <button
166
- onClick={() =>
167
- props.send({
168
- type: "profile.edit",
169
- userId: props.userId,
170
- })
171
- }
172
- >
173
- Edit Profile
174
- </button>
175
- </div>
176
- ),
177
- };
178
-
179
- <PlayRenderer actor={actor} components={components} />;
180
- ```
185
+ **`registry`** Built with `defineRegistry(catalog, { components, actions })` from `@xmachines/play-solid`.
181
186
 
182
- ### Provider Pattern
187
+ **`actions`** Maps json-render action names (from spec `on` bindings) to XState event type strings. Type-checked against `EventFromLogic<TLogic>["type"]` when `TLogic` is specified:
183
188
 
184
189
  ```tsx
185
- import { PlayTanStackRouterProvider } from "@xmachines/play-tanstack-solid-router";
186
- import { PlayRenderer } from "@xmachines/play-solid";
187
- import { createSignal, onCleanup } from "solid-js";
188
-
189
- // Renderer receives actor via props (not children)
190
- function App() {
191
- return (
192
- <PlayTanStackRouterProvider
193
- actor={actor}
194
- router={router}
195
- routeMap={routeMap}
196
- renderer={(currentActor, currentRouter) => {
197
- return (
198
- <div>
199
- <Header actor={currentActor} />
200
- <PlayRenderer actor={currentActor} components={components} />
201
- <Footer />
202
- </div>
203
- );
204
- }}
205
- />
206
- );
207
- }
208
-
209
- // Header component also receives actor
210
- function Header(props) {
211
- const [route, setRoute] = createSignal<string | null>(null);
212
-
213
- // Manual watcher setup for custom component reading signals
214
- let watcher: Signal.subtle.Watcher;
215
-
216
- // ... setup watcher to update route signal ...
217
-
218
- return (
219
- <header>
220
- <nav>Current: {route()}</nav>
221
- </header>
222
- );
223
- }
190
+ <PlayRenderer<typeof machine>
191
+ actor={actor}
192
+ registry={registry}
193
+ actions={{ login: "auth.login", logout: "auth.logout" }}
194
+ />
224
195
  ```
225
196
 
226
- ## Architecture
227
-
228
- This package implements **Signal-Only Reactivity (INV-05)** and **Passive Infrastructure (INV-04)**:
197
+ **`store`** (optional) — Controls per-view UI state (`$state` bindings, form values):
229
198
 
230
- 1. **No Business Logic in SolidJS:**
231
- - No createSignal/createStore for business state
232
- - No createEffect for business side effects
233
- - SolidJS only triggers renders, doesn't control state
199
+ - **Omitted (uncontrolled, default):** A fresh `@xstate/store` atom is created per view transition, seeded from `view.spec.state`.
200
+ - **Provided (controlled):** The caller owns the store; `spec.state` is ignored.
234
201
 
235
- 2. **Signals as Source of Truth:**
236
- - `actor.currentView.get()` provides UI structure
237
- - `actor.currentRoute.get()` provides navigation state
238
- - Components observe signals via explicit watcher patterns
239
-
240
- 3. **Event Forwarding:**
241
- - Components receive `send` function via props
242
- - User actions send events to actor (e.g., `{ type: "auth.login" }`)
243
- - Actor guards validate and process events
202
+ ```tsx
203
+ import { createAtom } from "@xstate/store";
204
+ import { xstateStoreStateStore } from "@json-render/xstate";
205
+ import type { StateStore } from "@json-render/core";
244
206
 
245
- 4. **Microtask Batching:**
246
- - `Signal.subtle.Watcher` coalesces rapid signal changes
247
- - Prevents SolidJS thrashing from multiple signal updates
248
- - Single SolidJS render per microtask batch
207
+ const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
249
208
 
250
- 5. **Explicit Disposal Contract:**
251
- - Component teardown calls watcher `unwatch` in `onCleanup`
252
- - Do not rely on GC-only cleanup
209
+ <PlayRenderer actor={actor} registry={registry} store={store} actions={{ login: "auth.login" }} />;
210
+ ```
253
211
 
254
- **Pattern:**
212
+ **`fallback`** — Shown when `actor.currentView.get()` is `null`.
255
213
 
256
- - Renderer receives actor via props (provider pattern)
257
- - Enables composition with navigation, headers, footers
258
- - Supports multiple renderers in same app
214
+ ---
259
215
 
260
- **Architectural Invariants:**
216
+ ### `useActor`
261
217
 
262
- - **Signal-Only Reactivity (INV-05):** No SolidJS state for business logic
263
- - **Passive Infrastructure (INV-04):** Components reflect, never decide
218
+ Solid hook for accessing the actor from inside any component rendered by `PlayRenderer`. No prop drilling needed.
264
219
 
265
- ## Canonical Watcher Lifecycle
220
+ ```tsx
221
+ import { useActor } from "@xmachines/play-solid";
266
222
 
267
- If you write your own custom integration, use the same watcher flow as `PlayRenderer`:
223
+ // Inside any component rendered inside PlayRenderer:
224
+ function LogoutButton() {
225
+ const actor = useActor();
226
+ return <button onClick={() => actor.send({ type: "auth.logout" })}>Log Out</button>;
227
+ }
228
+ ```
268
229
 
269
- 1. `notify` callback runs
270
- 2. Schedule work with `queueMicrotask`
271
- 3. Drain `watcher.getPending()`
272
- 4. Read actor signals and update SolidJS-local signal state (`setSignal()`)
273
- 5. Re-arm with `watch(...)` or `watch()`
230
+ Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
274
231
 
275
- Watcher notify is one-shot. Re-arm is required for continuous observation.
232
+ ---
276
233
 
277
- ## Benefits
234
+ ## Route Parameters in Props
278
235
 
279
- - **Framework Swappable:** Business logic has zero SolidJS imports
280
- - **Type Safety:** Props validated against catalog schemas
281
- - **Simple Testing:** Test actors without SolidJS renderer
282
- - **Performance:** Microtask batching reduces unnecessary renders
283
- - **Composability:** Renderer prop enables complex layouts
236
+ When using `formatPlayRouteTransitions`, URL path parameters flow automatically into component props. Declare an `undefined` slot in the spec to opt in:
284
237
 
285
- ## Related Packages
238
+ ```ts
239
+ // spec: { section: undefined, user: "alice" }
240
+ // After play.route to /settings/profile → context.routeParams = { section: "profile" }
241
+ // Component receives: { section: "profile", user: "alice" }
242
+ ```
286
243
 
287
- - **[@xmachines/play-xstate](../play-xstate)** - XState adapter providing actors
288
- - **[@xmachines/play-catalog](../play-catalog)** - UI schema validation
289
- - **[@xmachines/play-solid-router](../play-solid-router)** - Solid Router integration
290
- - **[@xmachines/play-tanstack-solid-router](../play-tanstack-solid-router)** - TanStack Solid Router integration
291
- - **[@xmachines/play-actor](../play-actor)** - Actor base
292
- - **[@xmachines/play-signals](../play-signals)** - TC39 Signals primitives
244
+ Priority: **route param fills `undefined` slots; explicit non-`undefined` spec props always win.**
293
245
 
294
- ## License
246
+ ---
295
247
 
296
- Copyright (c) 2016 [Mikael Karon](mailto:mikael@karon.se). All rights reserved.
248
+ ## Architecture Notes
297
249
 
298
- This work is licensed under the terms of the MIT license.
299
- For a copy, see <https://opensource.org/licenses/MIT>.
250
+ - SolidJS signals are only used to trigger re-renders not for business logic
251
+ - `actor.currentView` (TC39 Signal) is bridged into a SolidJS `createSignal` inside `PlayRenderer`
252
+ - Per-view UI state lives in an `@xstate/store` atom, not in SolidJS reactive state
253
+ - `@json-render/solid` drives rendering; `PlayRenderer` is the signal bridge — import `defineRegistry`, `ComponentFn`, `ComponentContext`, and `useStateBinding` from `@xmachines/play-solid`
@@ -1,31 +1,59 @@
1
- import { Dynamic as e, createComponent as t, insert as n, memo as r, mergeProps as i, template as a } from "solid-js/web";
2
- import { createSignal as o, onMount as s } from "solid-js";
3
- import { Signal as c } from "@xmachines/play-signals";
1
+ import { ActionProvider as e, Renderer as t, StateProvider as n, VisibilityProvider as r } from "./node_modules/@json-render/solid/dist/index.js";
2
+ import { createAtom as i } from "./node_modules/@xstate/store/dist/store-69e7e2d5.esm.js";
3
+ import { xstateStoreStateStore as a } from "./node_modules/@json-render/xstate/dist/index.js";
4
+ import { ActorProvider as o } from "./useActor.js";
5
+ import { createComponent as s } from "solid-js/web";
6
+ import { createSignal as c, onCleanup as l, onMount as u } from "solid-js";
7
+ import { watchSignal as d } from "@xmachines/play-signals";
4
8
  //#region src/PlayRenderer.tsx
5
- var l = /* @__PURE__ */ a("<div class=play-renderer-error>Component \"<!>\" not found in catalog. Available: "), u = (a) => {
6
- let [u, d] = o(a.actor.currentView.get());
7
- s(() => {
8
- let e = new c.subtle.Watcher(() => {
9
- queueMicrotask(() => {
10
- e.getPending(), d(a.actor.currentView.get()), e.watch(a.actor.currentView);
11
- });
9
+ var f = (f) => {
10
+ let [p, m] = c(f.actor.currentView.get()), h = null, g = null;
11
+ return u(() => {
12
+ let e = d(f.actor.currentView, (e) => {
13
+ m(e);
12
14
  });
13
- e.watch(a.actor.currentView);
15
+ l(() => {
16
+ e();
17
+ });
18
+ }), s(o, {
19
+ get value() {
20
+ return f.actor;
21
+ },
22
+ get children() {
23
+ return (() => {
24
+ let o = p();
25
+ if (!o) return f.fallback ?? null;
26
+ let c;
27
+ f.store ? c = f.store : ((h === null || g !== o) && (h = a({ atom: i(o.spec.state ?? {}) }), g = o), c = h);
28
+ let l = Object.fromEntries(Object.entries(f.actions ?? {}).map(([e, t]) => [e, async (e = {}) => f.actor.send({
29
+ type: t,
30
+ ...e
31
+ })]));
32
+ return s(n, {
33
+ store: c,
34
+ get children() {
35
+ return s(e, {
36
+ handlers: l,
37
+ get children() {
38
+ return s(r, { get children() {
39
+ return s(t, {
40
+ get spec() {
41
+ return o.spec;
42
+ },
43
+ get registry() {
44
+ return f.registry;
45
+ }
46
+ });
47
+ } });
48
+ }
49
+ });
50
+ }
51
+ });
52
+ })();
53
+ }
14
54
  });
15
- let f = a.actor.send.bind(a.actor);
16
- return [
17
- r(() => r(() => !u())() && (a.fallback || null)),
18
- r(() => r(() => !!(u() && !a.components))() && (console.error(`Components catalog is ${a.components === null ? "null" : "undefined"}. Cannot render component "${u().component}".`), a.fallback || null)),
19
- r(() => r(() => !!(u() && a.components && !a.components[u().component]))() && (console.error(`Component "${u().component}" not found in catalog. Available components: ${Object.keys(a.components).join(", ")}`), (() => {
20
- var e = l(), t = e.firstChild.nextSibling;
21
- return t.nextSibling, n(e, () => u().component, t), n(e, () => Object.keys(a.components).join(", "), null), e;
22
- })())),
23
- r(() => r(() => !!(u() && a.components && a.components[u().component]))() && t(e, i({ get component() {
24
- return a.components[u().component];
25
- } }, () => u().props, { send: f })))
26
- ];
27
55
  };
28
56
  //#endregion
29
- export { u as PlayRenderer };
57
+ export { f as PlayRenderer };
30
58
 
31
59
  //# sourceMappingURL=PlayRenderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlayRenderer.js","names":["createSignal","onMount","Component","Dynamic","Signal","PlayRendererProps","SolidView","PlayRenderer","props","view","setView","actor","currentView","get","watcher","subtle","Watcher","queueMicrotask","getPending","watch","sendBound","send","bind","_$memo","fallback","components","console","error","component","Object","keys","join","_el$","_tmpl$","_el$2","firstChild","_el$5","nextSibling","_el$3","_$insert","_$createComponent","_$mergeProps"],"sources":["../src/PlayRenderer.tsx"],"sourcesContent":["/**\n * PlayRenderer - Main SolidJS renderer component for XMachines Play architecture\n *\n * @packageDocumentation\n */\n\nimport { createSignal, onMount, type Component } from \"solid-js\";\nimport { Dynamic } from \"solid-js/web\";\nimport { Signal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps, SolidView } from \"./types.js\";\n\n/**\n * Main renderer component that subscribes to actor signals and renders UI\n *\n * Architecture (per XMachines Play patterns):\n * - Subscribes to actor.currentView signal via TC39 Signal.subtle.Watcher\n * - Dynamically renders catalog components based on view.component string\n * - Forwards user events to actor via actor.send()\n * - SolidJS signal only for triggering renders, NOT business logic\n *\n * Invariant: Actor Authority - Actor decides all state transitions via guards.\n * Invariant: Passive Infrastructure - Component observes signals and sends events.\n * Invariant: Signal-Only Reactivity - Business logic state lives in actor signals.\n *\n * @example\n * ```typescript\n * import { PlayRenderer } from \"@xmachines/play-solidjs\";\n * import { definePlayer } from \"@xmachines/play-xstate\";\n *\n * const actor = definePlayer({ machine, catalog })();\n * actor.start();\n *\n * const components = {\n * Dashboard: (props) => <div>User: {props.userId}</div>,\n * LoginForm: (props) => (\n * <form onSubmit={(e) => {\n * e.preventDefault();\n * props.send({ type: \"auth.login\", payload: {...} });\n * }}>...</form>\n * )\n * };\n *\n * <PlayRenderer actor={actor} components={components} />\n * ```\n *\n * @param props - Component props\n * @returns SolidJS element rendering current view from actor\n *\n * @remarks\n * **Component lookup:** Dynamically looks up component from `components` map\n * using `view.component` string from actor.currentView signal.\n *\n * **Event forwarding:** Injects `send` function as prop to components. Components\n * call `send(event)` to forward intents to actor. Actor guards decide validity.\n *\n * **Error handling:** If component not found in catalog, logs error and shows\n * fallback. This indicates missing component registration, not runtime error.\n *\n * **Signal bridge:** Uses one-shot re-watch pattern. TC39 Signal watchers stop\n * watching after notification, so watcher.watch() must be called in microtask\n * after getPending() to re-arm for next notification.\n *\n * **CRITICAL:** Never call actor.send() during render - only in event handlers.\n * Calling send during render causes infinite render loops.\n */\nexport const PlayRenderer: Component<PlayRendererProps> = (props) => {\n\t// Create SolidJS signal for view\n\t// Signal is NOT business logic state - it's just SolidJS's render trigger\n\tconst [view, setView] = createSignal<SolidView>(props.actor.currentView.get() as SolidView);\n\n\t// Bridge TC39 Signal to SolidJS signal\n\t// Uses one-shot re-watch pattern (must re-watch after each notification)\n\tonMount(() => {\n\t\tconst watcher = new Signal.subtle.Watcher(() => {\n\t\t\tqueueMicrotask(() => {\n\t\t\t\t// Acknowledge the notification\n\t\t\t\twatcher.getPending();\n\n\t\t\t\t// Update SolidJS signal (triggers SolidJS reactivity)\n\t\t\t\tsetView(props.actor.currentView.get() as SolidView);\n\n\t\t\t\t// Re-watch for next notification (one-shot pattern)\n\t\t\t\t// TC39 Signal watchers stop watching after notification\n\t\t\t\twatcher.watch(props.actor.currentView);\n\t\t\t});\n\t\t});\n\n\t\t// Watch actor.currentView for changes\n\t\twatcher.watch(props.actor.currentView);\n\n\t\t// Note: TC39 Signal watchers don't have explicit disposal\n\t\t// The watcher will be garbage collected when the component unmounts\n\t});\n\n\t// Bind send function (ensures correct 'this' context)\n\tconst sendBound = props.actor.send.bind(props.actor);\n\n\treturn (\n\t\t<>\n\t\t\t{/* No view - show fallback */}\n\t\t\t{!view() && (props.fallback || null)}\n\n\t\t\t{/* Handle null/undefined components catalog gracefully */}\n\t\t\t{view() &&\n\t\t\t\t!props.components &&\n\t\t\t\t(() => {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`Components catalog is ${props.components === null ? \"null\" : \"undefined\"}. ` +\n\t\t\t\t\t\t\t`Cannot render component \"${view()!.component}\".`,\n\t\t\t\t\t);\n\t\t\t\t\treturn props.fallback || null;\n\t\t\t\t})()}\n\n\t\t\t{/* View exists but component not found */}\n\t\t\t{view() &&\n\t\t\t\tprops.components &&\n\t\t\t\t!props.components[view()!.component] &&\n\t\t\t\t(() => {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`Component \"${view()!.component}\" not found in catalog. ` +\n\t\t\t\t\t\t\t`Available components: ${Object.keys(props.components).join(\", \")}`,\n\t\t\t\t\t);\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div class=\"play-renderer-error\">\n\t\t\t\t\t\t\tComponent \"{view()!.component}\" not found in catalog. Available:{\" \"}\n\t\t\t\t\t\t\t{Object.keys(props.components).join(\", \")}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})()}\n\n\t\t\t{/* Render matched component dynamically */}\n\t\t\t{view() && props.components && props.components[view()!.component] && (\n\t\t\t\t<Dynamic\n\t\t\t\t\tcomponent={props.components[view()!.component]}\n\t\t\t\t\t{...view()!.props}\n\t\t\t\t\tsend={sendBound}\n\t\t\t\t/>\n\t\t\t)}\n\t\t</>\n\t);\n};\n"],"mappings":";;;;iHAiEaO,KAA8CC,MAAU;CAGpE,IAAM,CAACC,GAAMC,KAAWV,EAAwBQ,EAAMG,MAAMC,YAAYC,KAAK,CAAc;AAI3FZ,SAAc;EACb,IAAMa,IAAU,IAAIV,EAAOW,OAAOC,cAAc;AAC/CC,wBAAqB;AASpBH,IAPAA,EAAQI,YAAY,EAGpBR,EAAQF,EAAMG,MAAMC,YAAYC,KAAK,CAAc,EAInDC,EAAQK,MAAMX,EAAMG,MAAMC,YAAY;KACrC;IACD;AAGFE,IAAQK,MAAMX,EAAMG,MAAMC,YAAY;GAIrC;CAGF,IAAMQ,IAAYZ,EAAMG,MAAMU,KAAKC,KAAKd,EAAMG,MAAM;AAEpD,QAAA;EAAAY,QAGGA,QAAA,CAACd,GAAM,CAAA,EAAA,KAAKD,EAAMgB,YAAY,MAAK;EAAAD,QAGnCA,QAAA,CAAA,EAAAd,GAAM,IACN,CAACD,EAAMiB,YAAU,EAAA,KAEhBC,QAAQC,MACP,yBAAyBnB,EAAMiB,eAAe,OAAO,SAAS,YAAW,6BAC5ChB,GAAM,CAAEmB,UAAS,IAC9C,EACMpB,EAAMgB,YAAY,MACtB;EAAAD,QAGJA,QAAA,CAAA,EAAAd,GAAM,IACND,EAAMiB,cACN,CAACjB,EAAMiB,WAAWhB,GAAM,CAAEmB,YAAU,EAAA,KAEnCF,QAAQC,MACP,cAAclB,GAAM,CAAEmB,UAAS,gDACLC,OAAOC,KAAKtB,EAAMiB,WAAW,CAACM,KAAK,KAAK,GAClE,SACD;GAAA,IAAAC,IAAAC,GAAA,EAAAG,IAAAJ,EAAAG,WAAAE;AAG2C,UAH3CD,EAAAC,aAAAE,EAAAP,SAEcvB,GAAM,CAAEmB,WAASQ,EAAA,EAAAG,EAAAP,SAC5BH,OAAOC,KAAKtB,EAAMiB,WAAW,CAACM,KAAK,KAAK,EAAA,KAAA,EAAAC;MAAA,EAGxC;EAAAT,QAGJA,QAAA,CAAA,EAAAd,GAAM,IAAID,EAAMiB,cAAcjB,EAAMiB,WAAWhB,GAAM,CAAEmB,YAAU,EAAA,IAAAY,EAChErC,GAAOsC,EAAA,EAAA,IACPb,YAAS;AAAA,UAAEpB,EAAMiB,WAAWhB,GAAM,CAAEmB;KAAU,QAC1CnB,GAAM,CAAED,OAAK,EACjBa,MAAMD,GAAS,CAAA,CAEhB,CAAA;EAAA"}
1
+ {"version":3,"file":"PlayRenderer.js","names":["createSignal","onCleanup","onMount","Component","Renderer","StateProvider","ActionProvider","VisibilityProvider","ActionHandler","StateStore","createAtom","xstateStoreStateStore","watchSignal","PlayRendererProps","ViewMetadata","ActorProvider","PlayActor","PlayRenderer","props","view","setView","actor","currentView","get","internalStore","lastView","unwatch","nextView","_$createComponent","value","children","fallback","store","initialState","spec","state","Record","atom","handlers","Object","fromEntries","entries","actions","map","actionName","eventType","params","send","type","registry"],"sources":["../src/PlayRenderer.tsx"],"sourcesContent":["/**\n * PlayRenderer - Main SolidJS renderer component for XMachines Play architecture\n *\n * @packageDocumentation\n */\n\nimport { createSignal, onCleanup, onMount, type Component } from \"solid-js\";\nimport { Renderer, StateProvider, ActionProvider, VisibilityProvider } from \"@json-render/solid\";\nimport type { ActionHandler, StateStore } from \"@json-render/core\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@json-render/xstate\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport type { PlayRendererProps } from \"./types.js\";\nimport type { ViewMetadata } from \"@xmachines/play-actor\";\nimport { ActorProvider, type PlayActor } from \"./useActor.js\";\n\n/**\n * Main renderer component that subscribes to actor signals and renders UI\n *\n * Architecture (per XMachines Play patterns):\n * - Subscribes to actor.currentView signal via TC39 Signal.subtle.Watcher\n * - Renders view.spec via @json-render/solid Renderer backed by the registry\n * - Routes actions to actor.send() via ActionProvider handlers\n * - SolidJS signal only for triggering renders, NOT business logic\n * - State store: uses external `store` prop if provided (controlled mode); otherwise\n * creates a fresh @xstate/store atom per view transition seeded from spec.state.\n *\n * Invariant: Actor Authority - Actor decides all state transitions via guards.\n * Invariant: Passive Infrastructure - Component observes signals and sends events.\n * Invariant: Signal-Only Reactivity - Business logic state lives in actor signals.\n *\n * @example\n * ```typescript\n * import { PlayRenderer } from \"@xmachines/play-solid\";\n * import { defineRegistry } from \"@json-render/solid\";\n *\n * const { registry } = defineRegistry(catalog, { components: { ... } });\n *\n * // Uncontrolled fresh atom per view, seeded from spec.state:\n * <PlayRenderer actor={actor} registry={registry} actions={{ login: \"auth.login\" }} />\n *\n * // Controlled caller provides and owns the store:\n * import { createAtom } from \"@xstate/store\";\n * import { xstateStoreStateStore } from \"@json-render/xstate\";\n * const store = xstateStoreStateStore({ atom: createAtom({ username: \"\" }) });\n * <PlayRenderer actor={actor} registry={registry} store={store} actions={{ login: \"auth.login\" }} />\n * ```\n */\nexport const PlayRenderer: Component<PlayRendererProps> = (props) => {\n\t// Create SolidJS signal for view\n\t// Signal is NOT business logic state - it's just SolidJS's render trigger\n\tconst [view, setView] = createSignal<ViewMetadata | null>(\n\t\tprops.actor.currentView.get() as ViewMetadata | null,\n\t);\n\n\t// Internal per-view store recreated on each view transition when no external store.\n\tlet internalStore: StateStore | null = null;\n\tlet lastView: ViewMetadata | null = null;\n\n\t// Bridge TC39 Signal to SolidJS signal\n\tonMount(() => {\n\t\tconst unwatch = watchSignal(props.actor.currentView, (nextView: ViewMetadata | null) => {\n\t\t\tsetView(nextView);\n\t\t});\n\t\tonCleanup(() => {\n\t\t\tunwatch();\n\t\t});\n\t});\n\n\treturn (\n\t\t<ActorProvider value={props.actor as PlayActor}>\n\t\t\t{(() => {\n\t\t\t\tconst currentView = view();\n\t\t\t\tif (!currentView) return props.fallback ?? null;\n\n\t\t\t\t// Resolve the store: external (controlled) or internal per-view atom\n\t\t\t\tlet store: StateStore;\n\t\t\t\tif (props.store) {\n\t\t\t\t\tstore = props.store;\n\t\t\t\t} else {\n\t\t\t\t\tif (internalStore === null || lastView !== currentView) {\n\t\t\t\t\t\tconst initialState =\n\t\t\t\t\t\t\t(currentView.spec.state as Record<string, unknown>) ?? {};\n\t\t\t\t\t\tinternalStore = xstateStoreStateStore({\n\t\t\t\t\t\t\tatom: createAtom(initialState),\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlastView = currentView;\n\t\t\t\t\t}\n\t\t\t\t\tstore = internalStore;\n\t\t\t\t}\n\n\t\t\t\tconst handlers: Record<string, ActionHandler> = Object.fromEntries(\n\t\t\t\t\tObject.entries(props.actions ?? {}).map(([actionName, eventType]) => [\n\t\t\t\t\t\tactionName,\n\t\t\t\t\t\tasync (params: Record<string, unknown> = {}) =>\n\t\t\t\t\t\t\tprops.actor.send({ type: eventType, ...params }),\n\t\t\t\t\t]),\n\t\t\t\t);\n\n\t\t\t\treturn (\n\t\t\t\t\t<StateProvider store={store}>\n\t\t\t\t\t\t<ActionProvider handlers={handlers}>\n\t\t\t\t\t\t\t<VisibilityProvider>\n\t\t\t\t\t\t\t\t<Renderer spec={currentView.spec} registry={props.registry} />\n\t\t\t\t\t\t\t</VisibilityProvider>\n\t\t\t\t\t\t</ActionProvider>\n\t\t\t\t\t</StateProvider>\n\t\t\t\t);\n\t\t\t})()}\n\t\t</ActorProvider>\n\t);\n};\n"],"mappings":";;;;;;;;AAgDA,IAAaiB,KAA8CC,MAAU;CAGpE,IAAM,CAACC,GAAMC,KAAWpB,EACvBkB,EAAMG,MAAMC,YAAYC,KAAK,CAC7B,EAGGC,IAAmC,MACnCC,IAAgC;AAYpC,QATAvB,QAAc;EACb,IAAMwB,IAAUd,EAAYM,EAAMG,MAAMC,cAAcK,MAAkC;AACvFP,KAAQO,EAAS;IAChB;AACF1B,UAAgB;AACfyB,MAAS;IACR;GACD,EAEFE,EACEb,GAAa;EAAA,IAACc,QAAK;AAAA,UAAEX,EAAMG;;EAAkB,IAAAS,WAAA;AAAA,iBACrC;IACP,IAAMR,IAAcH,GAAM;AAC1B,QAAI,CAACG,EAAa,QAAOJ,EAAMa,YAAY;IAG3C,IAAIC;AACJ,IAAId,EAAMc,QACTA,IAAQd,EAAMc,UAEVR,MAAkB,QAAQC,MAAaH,OAG1CE,IAAgBb,EAAsB,EACrC0B,MAAM3B,EAFLY,EAAYY,KAAKC,SAAqC,EAAE,CAE5B,EAC7B,CAAC,EACFV,IAAWH,IAEZU,IAAQR;IAGT,IAAMc,IAA0CC,OAAOC,YACtDD,OAAOE,QAAQvB,EAAMwB,WAAW,EAAE,CAAC,CAACC,KAAK,CAACC,GAAYC,OAAe,CACpED,GACA,OAAOE,IAAkC,EAAE,KAC1C5B,EAAMG,MAAM0B,KAAK;KAAEC,MAAMH;KAAW,GAAGC;KAAQ,CAAC,CACjD,CACF,CAAC;AAED,WAAAlB,EACEvB,GAAa;KAAQ2B;KAAK,IAAAF,WAAA;AAAA,aAAAF,EACzBtB,GAAc;OAAWgC;OAAQ,IAAAR,WAAA;AAAA,eAAAF,EAChCrB,GAAkB,EAAA,IAAAuB,WAAA;AAAA,gBAAAF,EACjBxB,GAAQ;UAAA,IAAC8B,OAAI;AAAA,kBAAEZ,EAAYY;;UAAI,IAAEe,WAAQ;AAAA,kBAAE/B,EAAM+B;;UAAQ,CAAA;WAAA,CAAA;;OAAA,CAAA;;KAAA,CAAA;OAK3D;;EAAA,CAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,4 @@
1
- import { PlayRenderer as e } from "./PlayRenderer.js";
2
- export { e as PlayRenderer };
1
+ import { defineRegistry as e, useStateBinding as t } from "./node_modules/@json-render/solid/dist/index.js";
2
+ import { useActor as n } from "./useActor.js";
3
+ import { PlayRenderer as r } from "./PlayRenderer.js";
4
+ export { r as PlayRenderer, e as defineRegistry, n as useActor, t as useStateBinding };