@xmachines/play-solid 1.0.0-beta.30 → 1.0.0-beta.32
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/README.md +26 -27
- package/dist/PlayRenderer.js +48 -40
- package/dist/PlayRenderer.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/node_modules/@json-render/core/dist/index.js +131 -78
- package/dist/node_modules/@json-render/core/dist/index.js.map +1 -1
- package/dist/node_modules/@json-render/solid/dist/index.js +251 -208
- package/dist/node_modules/@json-render/solid/dist/index.js.map +1 -1
- package/dist/node_modules/zod/v4/core/util.js +1 -1
- package/dist/node_modules/zod/v4/core/util.js.map +1 -1
- package/package.json +19 -19
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ without muting console output.
|
|
|
36
36
|
- `PlayRenderer` — main renderer component
|
|
37
37
|
- `useActor` — hook for accessing the actor inside a `PlayRenderer` tree
|
|
38
38
|
- `defineRegistry` — re-exported from `@json-render/solid`
|
|
39
|
-
- `
|
|
39
|
+
- `useBoundProp` — re-exported from `@json-render/solid`
|
|
40
40
|
- `ComponentFn` (type) — re-exported from `@json-render/solid`
|
|
41
41
|
- `ComponentContext` (type) — re-exported from `@json-render/solid`
|
|
42
42
|
- `PlayRendererProps` (type)
|
|
@@ -81,9 +81,17 @@ const Dashboard: ComponentFn<typeof catalog, "Dashboard"> = ({ props }) => (
|
|
|
81
81
|
);
|
|
82
82
|
|
|
83
83
|
// 3. Build registry
|
|
84
|
-
const
|
|
84
|
+
const registryResult = defineRegistry(catalog, {
|
|
85
85
|
components: { Login, Dashboard },
|
|
86
|
-
actions: {
|
|
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
|
+
},
|
|
87
95
|
});
|
|
88
96
|
|
|
89
97
|
// 4. Define machine with view metadata
|
|
@@ -158,13 +166,7 @@ const actor = createPlayer();
|
|
|
158
166
|
actor.start();
|
|
159
167
|
|
|
160
168
|
function App() {
|
|
161
|
-
return
|
|
162
|
-
<PlayRenderer
|
|
163
|
-
actor={actor}
|
|
164
|
-
registry={registry}
|
|
165
|
-
actions={{ login: "auth.login", logout: "auth.logout" }}
|
|
166
|
-
/>
|
|
167
|
-
);
|
|
169
|
+
return <PlayRenderer actor={actor} registryResult={registryResult} />;
|
|
168
170
|
}
|
|
169
171
|
```
|
|
170
172
|
|
|
@@ -177,8 +179,7 @@ Main component. Subscribes to `actor.currentView` and renders the spec.
|
|
|
177
179
|
```tsx
|
|
178
180
|
<PlayRenderer
|
|
179
181
|
actor={actor}
|
|
180
|
-
|
|
181
|
-
actions={{ login: "auth.login" }}
|
|
182
|
+
registryResult={registryResult}
|
|
182
183
|
store={myStore}
|
|
183
184
|
fallback={<p>Loading…</p>}
|
|
184
185
|
/>
|
|
@@ -186,21 +187,11 @@ Main component. Subscribes to `actor.currentView` and renders the spec.
|
|
|
186
187
|
|
|
187
188
|
**`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
|
|
188
189
|
|
|
189
|
-
**`
|
|
190
|
+
**`registryResult`** — The full `DefineRegistryResult` returned by `defineRegistry(catalog, { components, actions })` from `@xmachines/play-solid`.
|
|
190
191
|
|
|
191
192
|
`defineRegistry` also accepts `onRenderError(error, elementType)`, which receives errors
|
|
192
193
|
caught by `@json-render/solid`'s inner element boundary before the default logger is used.
|
|
193
194
|
|
|
194
|
-
**`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:
|
|
195
|
-
|
|
196
|
-
```tsx
|
|
197
|
-
<PlayRenderer<typeof machine>
|
|
198
|
-
actor={actor}
|
|
199
|
-
registry={registry}
|
|
200
|
-
actions={{ login: "auth.login", logout: "auth.logout" }}
|
|
201
|
-
/>
|
|
202
|
-
```
|
|
203
|
-
|
|
204
195
|
**`store`** (optional) — Controls per-view UI state (`$state` bindings, form values):
|
|
205
196
|
|
|
206
197
|
- **Omitted (uncontrolled, default):** A fresh `@xstate/store` atom is created per view transition, seeded from `view.spec.state`.
|
|
@@ -213,16 +204,24 @@ import type { StateStore } from "@json-render/core";
|
|
|
213
204
|
|
|
214
205
|
const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
|
|
215
206
|
|
|
216
|
-
<PlayRenderer actor={actor}
|
|
207
|
+
<PlayRenderer actor={actor} registryResult={registryResult} store={store} />;
|
|
217
208
|
```
|
|
218
209
|
|
|
219
210
|
**Inner render errors** — You can intercept catalog component render failures without
|
|
220
211
|
overriding the outer Solid error boundary:
|
|
221
212
|
|
|
222
213
|
```tsx
|
|
223
|
-
const
|
|
214
|
+
const registryResult = defineRegistry(catalog, {
|
|
224
215
|
components: { Login, Dashboard },
|
|
225
|
-
actions: {
|
|
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
|
+
},
|
|
226
225
|
onRenderError(error, elementType) {
|
|
227
226
|
reportExpectedRenderError(error, elementType);
|
|
228
227
|
},
|
|
@@ -270,4 +269,4 @@ Priority: **route param fills `undefined` slots; explicit non-`undefined` spec p
|
|
|
270
269
|
- SolidJS signals are only used to trigger re-renders — not for business logic
|
|
271
270
|
- `actor.currentView` (TC39 Signal) is bridged into a SolidJS `createSignal` inside `PlayRenderer`
|
|
272
271
|
- Per-view UI state lives in an `@xstate/store` atom, not in SolidJS reactive state
|
|
273
|
-
- `@json-render/solid` drives rendering; `PlayRenderer` is the signal bridge — import `defineRegistry`, `ComponentFn`, `ComponentContext`, and `
|
|
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,50 +1,58 @@
|
|
|
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
|
|
3
|
-
import { xstateStoreStateStore as
|
|
4
|
-
import { ActorProvider as
|
|
5
|
-
import { createComponent as
|
|
6
|
-
import { ErrorBoundary as
|
|
7
|
-
import { watchSignal as
|
|
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";
|
|
8
8
|
//#region src/PlayRenderer.tsx
|
|
9
|
-
var
|
|
10
|
-
let
|
|
11
|
-
|
|
9
|
+
var p = (n) => {
|
|
10
|
+
let a = i(), o = (e) => {
|
|
11
|
+
let t = a.getSnapshot();
|
|
12
|
+
a.update(e(t));
|
|
13
|
+
};
|
|
14
|
+
return c(e, {
|
|
15
|
+
handlers: n.registryResult.handlers(() => o, () => a.getSnapshot()),
|
|
16
|
+
get children() {
|
|
17
|
+
return c(r, { get children() {
|
|
18
|
+
return c(t, {
|
|
19
|
+
get spec() {
|
|
20
|
+
return n.spec;
|
|
21
|
+
},
|
|
22
|
+
get registry() {
|
|
23
|
+
return n.registryResult.registry;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
} });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}, m = (e) => {
|
|
30
|
+
let [t, r] = u(e.actor.currentView.get()), i = null, m = null, h = f(e.actor.currentView, (e) => {
|
|
31
|
+
r(e);
|
|
12
32
|
});
|
|
13
|
-
return
|
|
14
|
-
|
|
15
|
-
}), s
|
|
33
|
+
return d(() => {
|
|
34
|
+
h();
|
|
35
|
+
}), c(s, {
|
|
16
36
|
get value() {
|
|
17
|
-
return
|
|
37
|
+
return e.actor;
|
|
18
38
|
},
|
|
19
39
|
get children() {
|
|
20
|
-
return
|
|
21
|
-
fallback: (
|
|
40
|
+
return c(l, {
|
|
41
|
+
fallback: (t) => (e.onError?.(t), e.fallback ?? null),
|
|
22
42
|
get children() {
|
|
23
43
|
return (() => {
|
|
24
|
-
let
|
|
25
|
-
if (!
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
type: t,
|
|
30
|
-
...e
|
|
31
|
-
})]));
|
|
32
|
-
return s(n, {
|
|
33
|
-
store: c,
|
|
44
|
+
let r = t();
|
|
45
|
+
if (!r) return e.fallback ?? null;
|
|
46
|
+
let s;
|
|
47
|
+
return e.store ? s = e.store : ((i === null || m !== r) && (i = o({ atom: a(r.spec?.state ?? {}) }), m = r), s = i), c(n, {
|
|
48
|
+
store: s,
|
|
34
49
|
get children() {
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return o.spec;
|
|
42
|
-
},
|
|
43
|
-
get registry() {
|
|
44
|
-
return f.registry;
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
} });
|
|
50
|
+
return c(p, {
|
|
51
|
+
get registryResult() {
|
|
52
|
+
return e.registryResult;
|
|
53
|
+
},
|
|
54
|
+
get spec() {
|
|
55
|
+
return r.spec;
|
|
48
56
|
}
|
|
49
57
|
});
|
|
50
58
|
}
|
|
@@ -56,6 +64,6 @@ var f = (f) => {
|
|
|
56
64
|
});
|
|
57
65
|
};
|
|
58
66
|
//#endregion
|
|
59
|
-
export {
|
|
67
|
+
export { m as PlayRenderer };
|
|
60
68
|
|
|
61
69
|
//# sourceMappingURL=PlayRenderer.js.map
|
package/dist/PlayRenderer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayRenderer.js","names":["createSignal","onCleanup","ErrorBoundary","Component","Renderer","StateProvider","ActionProvider","VisibilityProvider","
|
|
1
|
+
{"version":3,"file":"PlayRenderer.js","names":["createSignal","onCleanup","ErrorBoundary","Component","Renderer","StateProvider","ActionProvider","VisibilityProvider","useStateStore","StateStore","DefineRegistryResult","SetState","createAtom","xstateStoreStateStore","watchSignal","PlayRendererProps","ViewMetadata","ActorProvider","PlayActor","PlayRendererInner","registryResult","spec","Spec","innerProps","stateCtx","setStateAdapter","updater","prev","getSnapshot","update","handlers","_$createComponent","children","registry","PlayRenderer","props","view","setView","actor","currentView","get","internalStore","lastView","unwatch","nextView","value","fallback","err","onError","store","initialState","state","Record","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 { 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 = innerProps.registryResult.handlers(\n\t\t() => setStateAdapter,\n\t\t() => stateCtx.getSnapshot(),\n\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,IAAMmB,KAGAI,MAAe;CACpB,IAAMC,IAAWhB,GAAe,EAK1BiB,KAA6BC,MAAY;EAC9C,IAAMC,IAAOH,EAASI,aAAa;AACnCJ,IAASK,OAAOH,EAAQC,EAAK,CAAC;;AAQ/B,QAAAI,EACEzB,GAAc;EAAWwB,UANVP,EAAWH,eAAeU,eACpCL,SACAD,EAASI,aAChB,CAAC;EAGkC,IAAAI,WAAA;AAAA,UAAAD,EAChCxB,GAAkB,EAAA,IAAAyB,WAAA;AAAA,WAAAD,EACjB3B,GAAQ;KAAA,IAACiB,OAAI;AAAA,aAAEE,EAAWF;;KAAI,IAAEY,WAAQ;AAAA,aAAEV,EAAWH,eAAea;;KAAQ,CAAA;MAAA,CAAA;;EAAA,CAAA;GAwBpEC,KAA8CC,MAAU;CAEpE,IAAM,CAACC,GAAMC,KAAWrC,EACvBmC,EAAMG,MAAMC,YAAYC,KAAK,CAC7B,EAGGC,IAAmC,MACnCC,IAAgC,MAG9BC,IAAU7B,EAAYqB,EAAMG,MAAMC,cAAcK,MAAkC;AACvFP,IAAQO,EAAS;GAChB;AAKF,QAJA3C,QAAgB;AACf0C,KAAS;GACR,EAEFZ,EACEd,GAAa;EAAA,IAAC4B,QAAK;AAAA,UAAEV,EAAMG;;EAAkB,IAAAN,WAAA;AAAA,UAAAD,EAC5C7B,GAAa;IACb4C,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,IAAgB5B,EAAsB,EACrCwC,MAAMzC,EAFL2B,EAAYlB,MAAM8B,SAAqC,EAAE,CAE7B,EAC7B,CAAC,EACFT,IAAWH,IAEZU,IAAQR,IAITV,EACE1B,GAAa;OAAQ4C;OAAK,IAAAjB,WAAA;AAAA,eAAAD,EACzBZ,GAAiB;SAAA,IACjBC,iBAAc;AAAA,iBAAEe,EAAMf;;SAAc,IACpCC,OAAI;AAAA,iBAAEkB,EAAYlB;;SAAI,CAAA;;OAAA,CAAA;SAItB;;IAAA,CAAA;;EAAA,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineRegistry as e,
|
|
1
|
+
import { defineRegistry as e, useBoundProp as t } from "./node_modules/@json-render/solid/dist/index.js";
|
|
2
2
|
import { useActor as n } from "./useActor.js";
|
|
3
3
|
import { PlayRenderer as r } from "./PlayRenderer.js";
|
|
4
|
-
export { r as PlayRenderer, e as defineRegistry, n as useActor, t as
|
|
4
|
+
export { r as PlayRenderer, e as defineRegistry, n as useActor, t as useBoundProp };
|