@xmachines/play-solid 1.0.0-beta.3 → 1.0.0-beta.31
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 +228 -66
- package/dist/PlayRenderer.js +64 -24
- package/dist/PlayRenderer.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js +111 -0
- package/dist/node_modules/@json-render/core/dist/chunk-AFLK3Q4T.js.map +1 -0
- package/dist/node_modules/@json-render/core/dist/index.js +863 -0
- package/dist/node_modules/@json-render/core/dist/index.js.map +1 -0
- package/dist/node_modules/@json-render/core/dist/store-utils.js +1 -0
- package/dist/node_modules/@json-render/solid/dist/chunk-XTSB6FIM.js +57 -0
- package/dist/node_modules/@json-render/solid/dist/chunk-XTSB6FIM.js.map +1 -0
- package/dist/node_modules/@json-render/solid/dist/index.js +619 -0
- package/dist/node_modules/@json-render/solid/dist/index.js.map +1 -0
- package/dist/node_modules/@json-render/xstate/dist/index.js +20 -0
- package/dist/node_modules/@json-render/xstate/dist/index.js.map +1 -0
- package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js +227 -0
- package/dist/node_modules/@xstate/store/dist/store-69e7e2d5.esm.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/errors.js +25 -0
- package/dist/node_modules/zod/v4/classic/errors.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/iso.js +33 -0
- package/dist/node_modules/zod/v4/classic/iso.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/parse.js +8 -0
- package/dist/node_modules/zod/v4/classic/parse.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/schemas.js +362 -0
- package/dist/node_modules/zod/v4/classic/schemas.js.map +1 -0
- package/dist/node_modules/zod/v4/core/api.js +530 -0
- package/dist/node_modules/zod/v4/core/api.js.map +1 -0
- package/dist/node_modules/zod/v4/core/checks.js +285 -0
- package/dist/node_modules/zod/v4/core/checks.js.map +1 -0
- package/dist/node_modules/zod/v4/core/core.js +46 -0
- package/dist/node_modules/zod/v4/core/core.js.map +1 -0
- package/dist/node_modules/zod/v4/core/doc.js +25 -0
- package/dist/node_modules/zod/v4/core/doc.js.map +1 -0
- package/dist/node_modules/zod/v4/core/errors.js +43 -0
- package/dist/node_modules/zod/v4/core/errors.js.map +1 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.js +183 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +1 -0
- package/dist/node_modules/zod/v4/core/parse.js +70 -0
- package/dist/node_modules/zod/v4/core/parse.js.map +1 -0
- package/dist/node_modules/zod/v4/core/regexes.js +27 -0
- package/dist/node_modules/zod/v4/core/regexes.js.map +1 -0
- package/dist/node_modules/zod/v4/core/registries.js +42 -0
- package/dist/node_modules/zod/v4/core/registries.js.map +1 -0
- package/dist/node_modules/zod/v4/core/schemas.js +823 -0
- package/dist/node_modules/zod/v4/core/schemas.js.map +1 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.js +224 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.js.map +1 -0
- package/dist/node_modules/zod/v4/core/util.js +268 -0
- package/dist/node_modules/zod/v4/core/util.js.map +1 -0
- package/dist/node_modules/zod/v4/core/versions.js +10 -0
- package/dist/node_modules/zod/v4/core/versions.js.map +1 -0
- package/dist/useActor.js +12 -0
- package/dist/useActor.js.map +1 -0
- package/package.json +28 -12
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikael Karon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,110 +1,272 @@
|
|
|
1
1
|
# @xmachines/play-solid
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Solid renderer for XMachines Play Architecture**
|
|
4
|
+
|
|
5
|
+
Bridges TC39 Signal-driven actors to Solid's fine-grained reactivity. Business logic stays in the actor; Solid is purely a rendering target.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`@xmachines/play-solid` provides `PlayRenderer`, a Solid component that:
|
|
10
|
+
|
|
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)
|
|
15
|
+
|
|
16
|
+
Per [Play RFC](../docs/rfc/play.md):
|
|
17
|
+
|
|
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
|
|
4
21
|
|
|
5
22
|
## Installation
|
|
6
23
|
|
|
7
24
|
```bash
|
|
8
|
-
npm install @xmachines/play-solid
|
|
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
|
|
9
28
|
```
|
|
10
29
|
|
|
30
|
+
In this monorepo, the root install applies a `patch-package` patch to `@json-render/solid`
|
|
31
|
+
so `defineRegistry(..., { onRenderError })` can intercept inner element-boundary errors
|
|
32
|
+
without muting console output.
|
|
33
|
+
|
|
11
34
|
## Current Exports
|
|
12
35
|
|
|
13
|
-
- `PlayRenderer`
|
|
36
|
+
- `PlayRenderer` — main renderer component
|
|
37
|
+
- `useActor` — hook for accessing the actor inside a `PlayRenderer` tree
|
|
38
|
+
- `defineRegistry` — re-exported from `@json-render/solid`
|
|
39
|
+
- `useBoundProp` — re-exported from `@json-render/solid`
|
|
40
|
+
- `ComponentFn` (type) — re-exported from `@json-render/solid`
|
|
41
|
+
- `ComponentContext` (type) — re-exported from `@json-render/solid`
|
|
14
42
|
- `PlayRendererProps` (type)
|
|
43
|
+
- `PlayActor` (type)
|
|
15
44
|
|
|
16
|
-
##
|
|
45
|
+
## Quick Start
|
|
17
46
|
|
|
18
|
-
```
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { defineCatalog } from
|
|
47
|
+
```tsx
|
|
48
|
+
import { definePlayer, formatPlayRouteTransitions } from "@xmachines/play-xstate";
|
|
49
|
+
import { PlayRenderer } from "@xmachines/play-solid";
|
|
50
|
+
import { defineCatalog } from "@json-render/core";
|
|
51
|
+
import { defineRegistry } from "@xmachines/play-solid";
|
|
52
|
+
import type { ComponentFn } from "@xmachines/play-solid";
|
|
53
|
+
import { setup, assign } from "xstate";
|
|
54
|
+
import { z } from "zod";
|
|
22
55
|
|
|
23
|
-
// Define catalog
|
|
56
|
+
// 1. Define catalog — the contract between machine spec and UI components
|
|
24
57
|
const catalog = defineCatalog({
|
|
25
|
-
|
|
26
|
-
|
|
58
|
+
elements: {
|
|
59
|
+
Login: { props: z.object({ title: z.string() }), description: "Login form" },
|
|
60
|
+
Dashboard: { props: z.object({ username: z.string() }), description: "Dashboard" },
|
|
61
|
+
},
|
|
27
62
|
});
|
|
28
63
|
|
|
29
|
-
//
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
64
|
+
// 2. Implement components using ComponentFn — typed against catalog entries
|
|
65
|
+
const Login: ComponentFn<typeof catalog, "Login"> = ({ props, emit }) => (
|
|
66
|
+
<div class="view">
|
|
67
|
+
<h2>{props.title}</h2>
|
|
68
|
+
<form
|
|
69
|
+
onSubmit={(e) => {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
emit("submit");
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
<button type="submit">Log In</button>
|
|
75
|
+
</form>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const Dashboard: ComponentFn<typeof catalog, "Dashboard"> = ({ props }) => (
|
|
80
|
+
<div class="view">Welcome, {props.username}!</div>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// 3. Build registry
|
|
84
|
+
const registryResult = defineRegistry(catalog, {
|
|
85
|
+
components: { Login, Dashboard },
|
|
86
|
+
actions: {
|
|
87
|
+
login: async (params) => {
|
|
88
|
+
if (!params) return;
|
|
89
|
+
actor.send({ type: "auth.login", username: params.username });
|
|
90
|
+
},
|
|
91
|
+
logout: async (params) => {
|
|
92
|
+
actor.send({ type: "auth.logout" });
|
|
93
|
+
},
|
|
94
|
+
},
|
|
33
95
|
});
|
|
34
96
|
|
|
97
|
+
// 4. Define machine with view metadata
|
|
98
|
+
const machine = setup({
|
|
99
|
+
types: {
|
|
100
|
+
context: {} as {
|
|
101
|
+
isAuthenticated: boolean;
|
|
102
|
+
username: string | null;
|
|
103
|
+
params: Record<string, string>;
|
|
104
|
+
query: Record<string, string>;
|
|
105
|
+
},
|
|
106
|
+
events: {} as
|
|
107
|
+
| { type: "auth.login"; username: string }
|
|
108
|
+
| { type: "auth.logout" }
|
|
109
|
+
| { type: "play.route"; to: string; params?: Record<string, string> },
|
|
110
|
+
},
|
|
111
|
+
}).createMachine(
|
|
112
|
+
formatPlayRouteTransitions({
|
|
113
|
+
id: "app",
|
|
114
|
+
initial: "login",
|
|
115
|
+
context: { isAuthenticated: false, username: null, params: {}, query: {} },
|
|
116
|
+
states: {
|
|
117
|
+
login: {
|
|
118
|
+
id: "login",
|
|
119
|
+
meta: {
|
|
120
|
+
route: "/login",
|
|
121
|
+
view: {
|
|
122
|
+
component: "Login",
|
|
123
|
+
spec: {
|
|
124
|
+
root: "root",
|
|
125
|
+
elements: {
|
|
126
|
+
root: { type: "Login", props: { title: "Sign In" }, children: [] },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
dashboard: {
|
|
133
|
+
id: "dashboard",
|
|
134
|
+
meta: {
|
|
135
|
+
route: "/dashboard",
|
|
136
|
+
view: {
|
|
137
|
+
component: "Dashboard",
|
|
138
|
+
spec: {
|
|
139
|
+
root: "root",
|
|
140
|
+
elements: {
|
|
141
|
+
root: { type: "Dashboard", props: { username: "" }, children: [] },
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
on: {
|
|
149
|
+
"auth.login": {
|
|
150
|
+
target: ".dashboard",
|
|
151
|
+
guard: ({ context }) => !context.isAuthenticated,
|
|
152
|
+
actions: assign({ isAuthenticated: true, username: ({ event }) => event.username }),
|
|
153
|
+
},
|
|
154
|
+
"auth.logout": {
|
|
155
|
+
target: ".login",
|
|
156
|
+
guard: ({ context }) => context.isAuthenticated,
|
|
157
|
+
actions: assign({ isAuthenticated: false, username: null }),
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// 5. Create actor and render
|
|
164
|
+
const createPlayer = definePlayer({ machine });
|
|
35
165
|
const actor = createPlayer();
|
|
36
166
|
actor.start();
|
|
37
167
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
168
|
+
function App() {
|
|
169
|
+
return <PlayRenderer actor={actor} registryResult={registryResult} />;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## API Reference
|
|
174
|
+
|
|
175
|
+
### `PlayRenderer`
|
|
176
|
+
|
|
177
|
+
Main component. Subscribes to `actor.currentView` and renders the spec.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
<PlayRenderer
|
|
181
|
+
actor={actor}
|
|
182
|
+
registryResult={registryResult}
|
|
183
|
+
store={myStore}
|
|
184
|
+
fallback={<p>Loading…</p>}
|
|
185
|
+
/>
|
|
55
186
|
```
|
|
56
187
|
|
|
57
|
-
|
|
188
|
+
**`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
|
|
189
|
+
|
|
190
|
+
**`registryResult`** — The full `DefineRegistryResult` returned by `defineRegistry(catalog, { components, actions })` from `@xmachines/play-solid`.
|
|
191
|
+
|
|
192
|
+
`defineRegistry` also accepts `onRenderError(error, elementType)`, which receives errors
|
|
193
|
+
caught by `@json-render/solid`'s inner element boundary before the default logger is used.
|
|
194
|
+
|
|
195
|
+
**`store`** (optional) — Controls per-view UI state (`$state` bindings, form values):
|
|
58
196
|
|
|
59
|
-
|
|
197
|
+
- **Omitted (uncontrolled, default):** A fresh `@xstate/store` atom is created per view transition, seeded from `view.spec.state`.
|
|
198
|
+
- **Provided (controlled):** The caller owns the store; `spec.state` is ignored.
|
|
60
199
|
|
|
61
|
-
|
|
200
|
+
```tsx
|
|
201
|
+
import { createAtom } from "@xstate/store";
|
|
202
|
+
import { xstateStoreStateStore } from "@json-render/xstate";
|
|
203
|
+
import type { StateStore } from "@json-render/core";
|
|
62
204
|
|
|
63
|
-
|
|
205
|
+
const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
|
|
64
206
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- `fallback?: JSX.Element` - Optional fallback to show when currentView is null
|
|
207
|
+
<PlayRenderer actor={actor} registryResult={registryResult} store={store} />;
|
|
208
|
+
```
|
|
68
209
|
|
|
69
|
-
**
|
|
210
|
+
**Inner render errors** — You can intercept catalog component render failures without
|
|
211
|
+
overriding the outer Solid error boundary:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
const registryResult = defineRegistry(catalog, {
|
|
215
|
+
components: { Login, Dashboard },
|
|
216
|
+
actions: {
|
|
217
|
+
login: async (params) => {
|
|
218
|
+
if (!params) return;
|
|
219
|
+
actor.send({ type: "auth.login", username: params.username });
|
|
220
|
+
},
|
|
221
|
+
logout: async (params) => {
|
|
222
|
+
actor.send({ type: "auth.logout" });
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
onRenderError(error, elementType) {
|
|
226
|
+
reportExpectedRenderError(error, elementType);
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
```
|
|
70
230
|
|
|
71
|
-
|
|
72
|
-
- Passes `send` function to components for event forwarding
|
|
73
|
-
- Handles missing components gracefully with error logging
|
|
74
|
-
- Uses one-shot watcher re-watch pattern for proper signal observation
|
|
231
|
+
**`fallback`** — Shown when `actor.currentView.get()` is `null`.
|
|
75
232
|
|
|
76
|
-
|
|
233
|
+
---
|
|
77
234
|
|
|
78
|
-
|
|
235
|
+
### `useActor`
|
|
79
236
|
|
|
80
|
-
|
|
81
|
-
2. `queueMicrotask`
|
|
82
|
-
3. `getPending()`
|
|
83
|
-
4. read actor signals and update framework-local render trigger
|
|
84
|
-
5. re-arm with `watch(...)` or `watch()`
|
|
237
|
+
Solid hook for accessing the actor from inside any component rendered by `PlayRenderer`. No prop drilling needed.
|
|
85
238
|
|
|
86
|
-
|
|
239
|
+
```tsx
|
|
240
|
+
import { useActor } from "@xmachines/play-solid";
|
|
87
241
|
|
|
88
|
-
|
|
242
|
+
// Inside any component rendered inside PlayRenderer:
|
|
243
|
+
function LogoutButton() {
|
|
244
|
+
const actor = useActor();
|
|
245
|
+
return <button onClick={() => actor.send({ type: "auth.logout" })}>Log Out</button>;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
89
248
|
|
|
90
|
-
|
|
249
|
+
Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
|
|
91
250
|
|
|
92
|
-
|
|
93
|
-
- Call `unwatch(...)` on teardown, not only reference nulling.
|
|
94
|
-
- Keep adapters/renderers passive; state validity remains actor-owned.
|
|
251
|
+
---
|
|
95
252
|
|
|
96
|
-
##
|
|
253
|
+
## Route Parameters in Props
|
|
97
254
|
|
|
98
|
-
|
|
255
|
+
When using `formatPlayRouteTransitions`, URL path parameters flow automatically into component props. Declare an `undefined` slot in the spec to opt in:
|
|
99
256
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
257
|
+
```ts
|
|
258
|
+
// spec: { section: undefined, user: "alice" }
|
|
259
|
+
// After play.route to /settings/profile → context.params = { section: "profile" }
|
|
260
|
+
// Component receives: { section: "profile", user: "alice" }
|
|
261
|
+
```
|
|
103
262
|
|
|
104
|
-
|
|
263
|
+
Priority: **route param fills `undefined` slots; explicit non-`undefined` spec props always win.**
|
|
105
264
|
|
|
106
|
-
|
|
265
|
+
---
|
|
107
266
|
|
|
108
|
-
##
|
|
267
|
+
## Architecture Notes
|
|
109
268
|
|
|
110
|
-
|
|
269
|
+
- SolidJS signals are only used to trigger re-renders — not for business logic
|
|
270
|
+
- `actor.currentView` (TC39 Signal) is bridged into a SolidJS `createSignal` inside `PlayRenderer`
|
|
271
|
+
- Per-view UI state lives in an `@xstate/store` atom, not in SolidJS reactive state
|
|
272
|
+
- `@json-render/solid` drives rendering; `PlayRenderer` is the signal bridge — import `defineRegistry`, `ComponentFn`, `ComponentContext`, and `useBoundProp` from `@xmachines/play-solid`
|
package/dist/PlayRenderer.js
CHANGED
|
@@ -1,31 +1,71 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { ActionProvider as e, Renderer as t, StateProvider as n, VisibilityProvider as r, useStateStore as i } from "./node_modules/@json-render/solid/dist/index.js";
|
|
2
|
+
import { createAtom as a } from "./node_modules/@xstate/store/dist/store-69e7e2d5.esm.js";
|
|
3
|
+
import { xstateStoreStateStore as o } from "./node_modules/@json-render/xstate/dist/index.js";
|
|
4
|
+
import { ActorProvider as s } from "./useActor.js";
|
|
5
|
+
import { createComponent as c } from "solid-js/web";
|
|
6
|
+
import { ErrorBoundary as l, createSignal as u, onCleanup as d } from "solid-js";
|
|
7
|
+
import { watchSignal as f } from "@xmachines/play-signals";
|
|
4
8
|
//#region src/PlayRenderer.tsx
|
|
5
|
-
var
|
|
6
|
-
let
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
var p = (n) => {
|
|
10
|
+
let a = i(), o = (e) => {
|
|
11
|
+
let t = a.getSnapshot();
|
|
12
|
+
a.update(e(t));
|
|
13
|
+
}, s = () => n.registryResult.handlers(() => o, () => a.getSnapshot());
|
|
14
|
+
return c(e, {
|
|
15
|
+
get handlers() {
|
|
16
|
+
return s();
|
|
17
|
+
},
|
|
18
|
+
get children() {
|
|
19
|
+
return c(r, { get children() {
|
|
20
|
+
return c(t, {
|
|
21
|
+
get spec() {
|
|
22
|
+
return n.spec;
|
|
23
|
+
},
|
|
24
|
+
get registry() {
|
|
25
|
+
return n.registryResult.registry;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
} });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}, m = (e) => {
|
|
32
|
+
let [t, r] = u(e.actor.currentView.get()), i = null, m = null, h = f(e.actor.currentView, (e) => {
|
|
33
|
+
r(e);
|
|
34
|
+
});
|
|
35
|
+
return d(() => {
|
|
36
|
+
h();
|
|
37
|
+
}), c(s, {
|
|
38
|
+
get value() {
|
|
39
|
+
return e.actor;
|
|
40
|
+
},
|
|
41
|
+
get children() {
|
|
42
|
+
return c(l, {
|
|
43
|
+
fallback: (t) => (e.onError?.(t), e.fallback ?? null),
|
|
44
|
+
get children() {
|
|
45
|
+
return (() => {
|
|
46
|
+
let r = t();
|
|
47
|
+
if (!r) return e.fallback ?? null;
|
|
48
|
+
let s;
|
|
49
|
+
return e.store ? s = e.store : ((i === null || m !== r) && (i = o({ atom: a(r.spec.state ?? {}) }), m = r), s = i), c(n, {
|
|
50
|
+
store: s,
|
|
51
|
+
get children() {
|
|
52
|
+
return c(p, {
|
|
53
|
+
get registryResult() {
|
|
54
|
+
return e.registryResult;
|
|
55
|
+
},
|
|
56
|
+
get spec() {
|
|
57
|
+
return r.spec;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
})();
|
|
63
|
+
}
|
|
11
64
|
});
|
|
12
|
-
}
|
|
13
|
-
e.watch(a.actor.currentView);
|
|
65
|
+
}
|
|
14
66
|
});
|
|
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
67
|
};
|
|
28
68
|
//#endregion
|
|
29
|
-
export {
|
|
69
|
+
export { m as PlayRenderer };
|
|
30
70
|
|
|
31
71
|
//# sourceMappingURL=PlayRenderer.js.map
|
package/dist/PlayRenderer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayRenderer.js","names":["createSignal","
|
|
1
|
+
{"version":3,"file":"PlayRenderer.js","names":["createSignal","onCleanup","ErrorBoundary","Component","Renderer","StateProvider","ActionProvider","VisibilityProvider","useStateStore","ActionHandler","StateStore","DefineRegistryResult","SetState","createAtom","xstateStoreStateStore","watchSignal","PlayRendererProps","ViewMetadata","ActorProvider","PlayActor","PlayRendererInner","registryResult","spec","Spec","innerProps","stateCtx","setStateAdapter","updater","prev","getSnapshot","update","handlers","Record","_$createComponent","children","registry","PlayRenderer","props","view","setView","actor","currentView","get","internalStore","lastView","unwatch","nextView","value","fallback","err","onError","store","initialState","state","atom"],"sources":["../src/PlayRenderer.tsx"],"sourcesContent":["/**\n * PlayRenderer - Main SolidJS renderer component for XMachines Play architecture\n *\n * @packageDocumentation\n */\n\nimport { createSignal, onCleanup, ErrorBoundary, type Component } from \"solid-js\";\nimport {\n\tRenderer,\n\tStateProvider,\n\tActionProvider,\n\tVisibilityProvider,\n\tuseStateStore,\n} from \"@json-render/solid\";\nimport type { ActionHandler, StateStore } from \"@json-render/core\";\nimport type { DefineRegistryResult, SetState } from \"@json-render/solid\";\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 * Inner component that renders inside StateProvider so it can call useStateStore()\n * to get the live set/getSnapshot functions needed by registryResult.handlers().\n */\nconst PlayRendererInner: Component<{\n\tregistryResult: DefineRegistryResult;\n\tspec: import(\"@json-render/core\").Spec | null;\n}> = (innerProps) => {\n\tconst stateCtx = useStateStore();\n\n\t// Build a SetState adapter: the handlers factory expects an updater-function pattern\n\t// ((prev) => next), while stateCtx provides path-based set/update. This adapter\n\t// bridges the two so action functions can use setState if needed.\n\tconst setStateAdapter: SetState = (updater) => {\n\t\tconst prev = stateCtx.getSnapshot();\n\t\tstateCtx.update(updater(prev));\n\t};\n\n\tconst handlers = (): Record<string, ActionHandler> =>\n\t\tinnerProps.registryResult.handlers(\n\t\t\t() => setStateAdapter,\n\t\t\t() => stateCtx.getSnapshot(),\n\t\t);\n\n\treturn (\n\t\t<ActionProvider handlers={handlers()}>\n\t\t\t<VisibilityProvider>\n\t\t\t\t<Renderer spec={innerProps.spec} registry={innerProps.registryResult.registry} />\n\t\t\t</VisibilityProvider>\n\t\t</ActionProvider>\n\t);\n};\n\n/**\n * Main renderer component that subscribes to actor signals and renders UI\n *\n * Architecture:\n * - Subscribes to actor.currentView signal via TC39 Signal.subtle.Watcher\n * - Renders view.spec via StateProvider → PlayRendererInner (with ActionProvider + handlers)\n * - Routes actions to actor.send() via registryResult.handlers() — real async functions\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 * - Wraps the render path in a SolidJS `ErrorBoundary` to contain catalog component\n * render failures. The `fallback` prop is shown on error; `onError` is called for\n * observability forwarding (Sentry, Datadog, etc.).\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 */\nexport const PlayRenderer: Component<PlayRendererProps> = (props) => {\n\t// Create SolidJS signal for view\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 — subscribe synchronously during setup\n\tconst unwatch = watchSignal(props.actor.currentView, (nextView: ViewMetadata | null) => {\n\t\tsetView(nextView);\n\t});\n\tonCleanup(() => {\n\t\tunwatch();\n\t});\n\n\treturn (\n\t\t<ActorProvider value={props.actor as PlayActor}>\n\t\t\t<ErrorBoundary\n\t\t\t\tfallback={(err: unknown) => {\n\t\t\t\t\tprops.onError?.(err);\n\t\t\t\t\treturn props.fallback ?? null;\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t{(() => {\n\t\t\t\t\tconst currentView = view();\n\t\t\t\t\tif (!currentView) return props.fallback ?? null;\n\n\t\t\t\t\t// Resolve the store: external (controlled) or internal per-view atom\n\t\t\t\t\tlet store: StateStore;\n\t\t\t\t\tif (props.store) {\n\t\t\t\t\t\tstore = props.store;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (internalStore === null || lastView !== currentView) {\n\t\t\t\t\t\t\tconst initialState =\n\t\t\t\t\t\t\t\t(currentView.spec.state as Record<string, unknown>) ?? {};\n\t\t\t\t\t\t\tinternalStore = xstateStoreStateStore({\n\t\t\t\t\t\t\t\tatom: createAtom(initialState),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tlastView = currentView;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstore = internalStore;\n\t\t\t\t\t}\n\n\t\t\t\t\t// PlayRendererInner renders inside StateProvider so useStateStore() works\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<StateProvider store={store}>\n\t\t\t\t\t\t\t<PlayRendererInner\n\t\t\t\t\t\t\t\tregistryResult={props.registryResult}\n\t\t\t\t\t\t\t\tspec={currentView.spec}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t</StateProvider>\n\t\t\t\t\t);\n\t\t\t\t})()}\n\t\t\t</ErrorBoundary>\n\t\t</ActorProvider>\n\t);\n};\n"],"mappings":";;;;;;;;AA2BA,IAAMoB,KAGAI,MAAe;CACpB,IAAMC,IAAWjB,GAAe,EAK1BkB,KAA6BC,MAAY;EAC9C,IAAMC,IAAOH,EAASI,aAAa;AACnCJ,IAASK,OAAOH,EAAQC,EAAK,CAAC;IAGzBG,UACLP,EAAWH,eAAeU,eACnBL,SACAD,EAASI,aAChB,CAAC;AAEF,QAAAI,EACE3B,GAAc;EAAA,IAACyB,WAAQ;AAAA,UAAEA,GAAU;;EAAA,IAAAG,WAAA;AAAA,UAAAD,EAClC1B,GAAkB,EAAA,IAAA2B,WAAA;AAAA,WAAAD,EACjB7B,GAAQ;KAAA,IAACkB,OAAI;AAAA,aAAEE,EAAWF;;KAAI,IAAEa,WAAQ;AAAA,aAAEX,EAAWH,eAAec;;KAAQ,CAAA;MAAA,CAAA;;EAAA,CAAA;GAwBpEC,KAA8CC,MAAU;CAEpE,IAAM,CAACC,GAAMC,KAAWvC,EACvBqC,EAAMG,MAAMC,YAAYC,KAAK,CAC7B,EAGGC,IAAmC,MACnCC,IAAgC,MAG9BC,IAAU9B,EAAYsB,EAAMG,MAAMC,cAAcK,MAAkC;AACvFP,IAAQO,EAAS;GAChB;AAKF,QAJA7C,QAAgB;AACf4C,KAAS;GACR,EAEFZ,EACEf,GAAa;EAAA,IAAC6B,QAAK;AAAA,UAAEV,EAAMG;;EAAkB,IAAAN,WAAA;AAAA,UAAAD,EAC5C/B,GAAa;IACb8C,WAAWC,OACVZ,EAAMa,UAAUD,EAAI,EACbZ,EAAMW,YAAY;IACzB,IAAAd,WAAA;AAAA,mBAEO;MACP,IAAMO,IAAcH,GAAM;AAC1B,UAAI,CAACG,EAAa,QAAOJ,EAAMW,YAAY;MAG3C,IAAIG;AAgBJ,aAfId,EAAMc,QACTA,IAAQd,EAAMc,UAEVR,MAAkB,QAAQC,MAAaH,OAG1CE,IAAgB7B,EAAsB,EACrCwC,MAAMzC,EAFL4B,EAAYnB,KAAK+B,SAAqC,EAAE,CAE5B,EAC7B,CAAC,EACFT,IAAWH,IAEZU,IAAQR,IAITV,EACE5B,GAAa;OAAQ8C;OAAK,IAAAjB,WAAA;AAAA,eAAAD,EACzBb,GAAiB;SAAA,IACjBC,iBAAc;AAAA,iBAAEgB,EAAMhB;;SAAc,IACpCC,OAAI;AAAA,iBAAEmB,EAAYnB;;SAAI,CAAA;;OAAA,CAAA;SAItB;;IAAA,CAAA;;EAAA,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { defineRegistry as e, useBoundProp 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 useBoundProp };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { _null as e, boolean as t, number as n, object as r, string as i, union as a } from "../../../zod/v4/classic/schemas.js";
|
|
2
|
+
//#region ../../node_modules/@json-render/core/dist/chunk-AFLK3Q4T.mjs
|
|
3
|
+
var o = a([
|
|
4
|
+
i(),
|
|
5
|
+
n(),
|
|
6
|
+
t(),
|
|
7
|
+
e(),
|
|
8
|
+
r({ $state: i() })
|
|
9
|
+
]);
|
|
10
|
+
a([i(), r({ $state: i() })]), a([n(), r({ $state: i() })]), a([t(), r({ $state: i() })]);
|
|
11
|
+
function s(e, t) {
|
|
12
|
+
if (e != null) return typeof e == "object" && "$state" in e ? u(t, e.$state) : e;
|
|
13
|
+
}
|
|
14
|
+
function c(e) {
|
|
15
|
+
return e.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
16
|
+
}
|
|
17
|
+
function l(e) {
|
|
18
|
+
return (e.startsWith("/") ? e.slice(1).split("/") : e.split("/")).map(c);
|
|
19
|
+
}
|
|
20
|
+
function u(e, t) {
|
|
21
|
+
if (!t || t === "/") return e;
|
|
22
|
+
let n = l(t), r = e;
|
|
23
|
+
for (let e of n) {
|
|
24
|
+
if (r == null) return;
|
|
25
|
+
if (Array.isArray(r)) r = r[parseInt(e, 10)];
|
|
26
|
+
else if (typeof r == "object") r = r[e];
|
|
27
|
+
else return;
|
|
28
|
+
}
|
|
29
|
+
return r;
|
|
30
|
+
}
|
|
31
|
+
var d = "spec";
|
|
32
|
+
`${d}`;
|
|
33
|
+
function f(e, t, n) {
|
|
34
|
+
let r = l(t);
|
|
35
|
+
if (r.length === 0) return e;
|
|
36
|
+
let i = { ...e }, a = i;
|
|
37
|
+
for (let e = 0; e < r.length - 1; e++) {
|
|
38
|
+
let t = r[e], n = a[t];
|
|
39
|
+
if (Array.isArray(n)) a[t] = [...n];
|
|
40
|
+
else if (typeof n == "object" && n) a[t] = { ...n };
|
|
41
|
+
else {
|
|
42
|
+
let n = r[e + 1];
|
|
43
|
+
a[t] = n !== void 0 && /^\d+$/.test(n) ? [] : {};
|
|
44
|
+
}
|
|
45
|
+
a = a[t];
|
|
46
|
+
}
|
|
47
|
+
let o = r[r.length - 1];
|
|
48
|
+
return Array.isArray(a) ? o === "-" ? a.push(n) : a[parseInt(o, 10)] = n : a[o] = n, i;
|
|
49
|
+
}
|
|
50
|
+
function p(e = {}) {
|
|
51
|
+
let t = { ...e }, n = /* @__PURE__ */ new Set();
|
|
52
|
+
function r() {
|
|
53
|
+
for (let e of n) e();
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
get(e) {
|
|
57
|
+
return u(t, e);
|
|
58
|
+
},
|
|
59
|
+
set(e, n) {
|
|
60
|
+
u(t, e) !== n && (t = f(t, e, n), r());
|
|
61
|
+
},
|
|
62
|
+
update(e) {
|
|
63
|
+
let n = !1, i = t;
|
|
64
|
+
for (let [t, r] of Object.entries(e)) u(i, t) !== r && (i = f(i, t, r), n = !0);
|
|
65
|
+
n && (t = i, r());
|
|
66
|
+
},
|
|
67
|
+
getSnapshot() {
|
|
68
|
+
return t;
|
|
69
|
+
},
|
|
70
|
+
getServerSnapshot() {
|
|
71
|
+
return t;
|
|
72
|
+
},
|
|
73
|
+
subscribe(e) {
|
|
74
|
+
return n.add(e), () => {
|
|
75
|
+
n.delete(e);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function m(e) {
|
|
81
|
+
return {
|
|
82
|
+
get(t) {
|
|
83
|
+
return u(e.getSnapshot(), t);
|
|
84
|
+
},
|
|
85
|
+
set(t, n) {
|
|
86
|
+
let r = e.getSnapshot();
|
|
87
|
+
u(r, t) !== n && e.setSnapshot(f(r, t, n));
|
|
88
|
+
},
|
|
89
|
+
update(t) {
|
|
90
|
+
let n = e.getSnapshot(), r = !1;
|
|
91
|
+
for (let [e, i] of Object.entries(t)) u(n, e) !== i && (n = f(n, e, i), r = !0);
|
|
92
|
+
r && e.setSnapshot(n);
|
|
93
|
+
},
|
|
94
|
+
getSnapshot: e.getSnapshot,
|
|
95
|
+
getServerSnapshot: e.getSnapshot,
|
|
96
|
+
subscribe: e.subscribe
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
var h = 20;
|
|
100
|
+
function g(e, t = "", n = 0, r, i) {
|
|
101
|
+
let a = r ?? /* @__PURE__ */ new Set(), o = i ?? { current: !1 }, s = {};
|
|
102
|
+
for (let [r, i] of Object.entries(e)) {
|
|
103
|
+
let e = `${t}/${r}`;
|
|
104
|
+
n < h && typeof i == "object" && i && !Array.isArray(i) && Object.getPrototypeOf(i) === Object.prototype && !a.has(i) ? (a.add(i), Object.assign(s, g(i, e, n + 1, a, o))) : (process.env.NODE_ENV !== "production" && !o.current && n >= h && typeof i == "object" && i && !Array.isArray(i) && Object.getPrototypeOf(i) === Object.prototype && !a.has(i) && (o.current = !0, console.warn(`flattenToPointers: depth limit (${h}) reached. Nested state beyond this depth will be treated as a leaf value.`)), s[e] = i);
|
|
105
|
+
}
|
|
106
|
+
return s;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { o as DynamicValueSchema, d as SPEC_DATA_PART, p as createStateStore, m as createStoreAdapter, g as flattenToPointers, u as getByPath, f as immutableSetByPath, s as resolveDynamicValue };
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=chunk-AFLK3Q4T.js.map
|