@xmachines/play-solid 1.0.0-beta.2 → 1.0.0-beta.21

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 (61) hide show
  1. package/README.md +209 -66
  2. package/dist/PlayRenderer.js +55 -29
  3. package/dist/PlayRenderer.js.map +1 -1
  4. package/dist/index.js +4 -5
  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 +27 -17
  55. package/dist/PlayRenderer.d.ts +0 -63
  56. package/dist/PlayRenderer.d.ts.map +0 -1
  57. package/dist/index.d.ts +0 -8
  58. package/dist/index.d.ts.map +0 -1
  59. package/dist/index.js.map +0 -1
  60. package/dist/types.d.ts +0 -28
  61. package/dist/types.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,110 +1,253 @@
1
1
  # @xmachines/play-solid
2
2
 
3
- SolidJS renderer for XMachines Play architecture. Enables catalog-driven view rendering with actor-owned business state.
3
+ **SolidJS renderer for XMachines Play Architecture**
4
+
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
+
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 solid-js
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
 
11
30
  ## Current Exports
12
31
 
13
- - `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`
14
38
  - `PlayRendererProps` (type)
39
+ - `PlayActor` (type)
15
40
 
16
- ## Usage
41
+ ## Quick Start
17
42
 
18
- ```typescript
19
- import { PlayRenderer } from '@xmachines/play-solid';
20
- import { definePlayer } from '@xmachines/play-xstate';
21
- import { defineCatalog } from '@xmachines/play-catalog';
43
+ ```tsx
44
+ import { definePlayer, formatPlayRouteTransitions } from "@xmachines/play-xstate";
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";
50
+ import { z } from "zod";
22
51
 
23
- // Define catalog
52
+ // 1. Define catalog — the contract between machine spec and UI components
24
53
  const catalog = defineCatalog({
25
- Home: { component: 'Home', props: {} },
26
- Login: { component: 'Login', props: { error: { type: 'string' } } }
54
+ elements: {
55
+ Login: { props: z.object({ title: z.string() }), description: "Login form" },
56
+ Dashboard: { props: z.object({ username: z.string() }), description: "Dashboard" },
57
+ },
27
58
  });
28
59
 
29
- // Create player
30
- const createPlayer = definePlayer({
31
- machine: authMachine,
32
- catalog
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>
64
+ <form
65
+ onSubmit={(e) => {
66
+ e.preventDefault();
67
+ emit("submit");
68
+ }}
69
+ >
70
+ <button type="submit">Log In</button>
71
+ </form>
72
+ </div>
73
+ );
74
+
75
+ const Dashboard: ComponentFn<typeof catalog, "Dashboard"> = ({ props }) => (
76
+ <div class="view">Welcome, {props.username}!</div>
77
+ );
78
+
79
+ // 3. Build registry
80
+ const { registry } = defineRegistry(catalog, {
81
+ components: { Login, Dashboard },
82
+ actions: { login: async () => {}, logout: async () => {} },
33
83
  });
34
84
 
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
+ );
150
+
151
+ // 5. Create actor and render
152
+ const createPlayer = definePlayer({ machine });
35
153
  const actor = createPlayer();
36
154
  actor.start();
37
155
 
38
- // Define components
39
- const components = {
40
- Home: (props) => <div>Home</div>,
41
- Login: (props) => (
42
- <form onSubmit={(e) => {
43
- e.preventDefault();
44
- props.send({ type: 'auth.login', payload: {...} });
45
- }}>
46
- {props.error && <p>{props.error}</p>}
47
- <input type="text" name="username" />
48
- <button type="submit">Login</button>
49
- </form>
50
- )
51
- };
52
-
53
- // Render
54
- <PlayRenderer actor={actor} components={components} />
156
+ function App() {
157
+ return (
158
+ <PlayRenderer
159
+ actor={actor}
160
+ registry={registry}
161
+ actions={{ login: "auth.login", logout: "auth.logout" }}
162
+ />
163
+ );
164
+ }
165
+ ```
166
+
167
+ ## API Reference
168
+
169
+ ### `PlayRenderer`
170
+
171
+ Main component. Subscribes to `actor.currentView` and renders the spec.
172
+
173
+ ```tsx
174
+ <PlayRenderer
175
+ actor={actor}
176
+ registry={registry}
177
+ actions={{ login: "auth.login" }}
178
+ store={myStore}
179
+ fallback={<p>Loading…</p>}
180
+ />
55
181
  ```
56
182
 
57
- ## API
183
+ **`actor`** — A `PlayerActor` (or any `AbstractActor & Viewable`). Provides the `currentView` signal.
58
184
 
59
- ### PlayRenderer
185
+ **`registry`** — Built with `defineRegistry(catalog, { components, actions })` from `@xmachines/play-solid`.
60
186
 
61
- Component that observes `actor.currentView` signal and renders the appropriate component from the catalog.
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:
62
188
 
63
- **Props:**
189
+ ```tsx
190
+ <PlayRenderer<typeof machine>
191
+ actor={actor}
192
+ registry={registry}
193
+ actions={{ login: "auth.login", logout: "auth.logout" }}
194
+ />
195
+ ```
64
196
 
65
- - `actor: AbstractActor & Viewable` - Actor instance with currentView signal
66
- - `components: Record<string, Component<any>>` - Map of component names to SolidJS components
67
- - `fallback?: JSX.Element` - Optional fallback to show when currentView is null
197
+ **`store`** (optional) Controls per-view UI state (`$state` bindings, form values):
68
198
 
69
- **Features:**
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.
70
201
 
71
- - Automatically bridges TC39 Signals to SolidJS reactivity
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
202
+ ```tsx
203
+ import { createAtom } from "@xstate/store";
204
+ import { xstateStoreStateStore } from "@json-render/xstate";
205
+ import type { StateStore } from "@json-render/core";
75
206
 
76
- ## Canonical Watcher Lifecycle
207
+ const store: StateStore = xstateStoreStateStore({ atom: createAtom({ username: "" }) });
77
208
 
78
- Use the same watcher flow as React/Vue/router packages:
209
+ <PlayRenderer actor={actor} registry={registry} store={store} actions={{ login: "auth.login" }} />;
210
+ ```
79
211
 
80
- 1. `notify`
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()`
212
+ **`fallback`** — Shown when `actor.currentView.get()` is `null`.
85
213
 
86
- Watcher notifications are one-shot, so re-arm is mandatory.
214
+ ---
87
215
 
88
- ## Cleanup Contract
216
+ ### `useActor`
89
217
 
90
- Solid integrations must perform explicit teardown:
218
+ Solid hook for accessing the actor from inside any component rendered by `PlayRenderer`. No prop drilling needed.
91
219
 
92
- - Use `onCleanup` for lifecycle teardown.
93
- - Call `unwatch(...)` on teardown, not only reference nulling.
94
- - Keep adapters/renderers passive; state validity remains actor-owned.
220
+ ```tsx
221
+ import { useActor } from "@xmachines/play-solid";
95
222
 
96
- ## Architecture
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
+ ```
97
229
 
98
- PlayRenderer follows the XMachines Play architecture:
230
+ Throws `"useActor() must be called inside <PlayRenderer>"` if called outside the tree.
99
231
 
100
- - **Actor Authority**: Actor controls all state transitions via guards
101
- - **Passive Infrastructure**: Renderer observes signals, sends events
102
- - **Signal-Only Reactivity**: Business logic state lives in actor signals
232
+ ---
233
+
234
+ ## Route Parameters in Props
235
+
236
+ When using `formatPlayRouteTransitions`, URL path parameters flow automatically into component props. Declare an `undefined` slot in the spec to opt in:
237
+
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
+ ```
103
243
 
104
- The renderer bridges TC39 Signals (used by XMachines actors) to SolidJS's reactivity system using `Signal.subtle.Watcher` with a one-shot re-watch pattern.
244
+ Priority: **route param fills `undefined` slots; explicit non-`undefined` spec props always win.**
105
245
 
106
- Signals remain observation plumbing, not an alternate mutation channel.
246
+ ---
107
247
 
108
- ## License
248
+ ## Architecture Notes
109
249
 
110
- 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,30 +1,56 @@
1
- import { memo as o, insert as r, createComponent as i, Dynamic as u, mergeProps as s, template as d } from "solid-js/web";
2
- import { createSignal as g, onMount as b } from "solid-js";
3
- import { Signal as w } from "@xmachines/play-signals";
4
- var f = /* @__PURE__ */ d('<div class=play-renderer-error>Component "<!>" not found in catalog. Available: ');
5
- const h = (n) => {
6
- const [e, a] = g(n.actor.currentView.get());
7
- b(() => {
8
- const t = new w.subtle.Watcher(() => {
9
- queueMicrotask(() => {
10
- t.getPending(), a(n.actor.currentView.get()), t.watch(n.actor.currentView);
11
- });
12
- });
13
- t.watch(n.actor.currentView);
14
- });
15
- const l = n.actor.send.bind(n.actor);
16
- return [o(() => o(() => !e())() && (n.fallback || null)), o(() => o(() => !!(e() && !n.components))() && (console.error(`Components catalog is ${n.components === null ? "null" : "undefined"}. Cannot render component "${e().component}".`), n.fallback || null)), o(() => o(() => !!(e() && n.components && !n.components[e().component]))() && (console.error(`Component "${e().component}" not found in catalog. Available components: ${Object.keys(n.components).join(", ")}`), (() => {
17
- var t = f(), m = t.firstChild, c = m.nextSibling;
18
- return c.nextSibling, r(t, () => e().component, c), r(t, () => Object.keys(n.components).join(", "), null), t;
19
- })())), o(() => o(() => !!(e() && n.components && n.components[e().component]))() && i(u, s({
20
- get component() {
21
- return n.components[e().component];
22
- }
23
- }, () => e().props, {
24
- send: l
25
- })))];
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 } from "solid-js";
7
+ import { watchSignal as u } from "@xmachines/play-signals";
8
+ //#region src/PlayRenderer.tsx
9
+ var d = (d) => {
10
+ let [f, p] = c(d.actor.currentView.get()), m = null, h = null, g = u(d.actor.currentView, (e) => {
11
+ p(e);
12
+ });
13
+ return l(() => {
14
+ g();
15
+ }), s(o, {
16
+ get value() {
17
+ return d.actor;
18
+ },
19
+ get children() {
20
+ return (() => {
21
+ let o = f();
22
+ if (!o) return d.fallback ?? null;
23
+ let c;
24
+ d.store ? c = d.store : ((m === null || h !== o) && (m = a({ atom: i(o.spec.state ?? {}) }), h = o), c = m);
25
+ let l = Object.fromEntries(Object.entries(d.actions ?? {}).map(([e, t]) => [e, async (e = {}) => d.actor.send({
26
+ type: t,
27
+ ...e
28
+ })]));
29
+ return s(n, {
30
+ store: c,
31
+ get children() {
32
+ return s(e, {
33
+ handlers: l,
34
+ get children() {
35
+ return s(r, { get children() {
36
+ return s(t, {
37
+ get spec() {
38
+ return o.spec;
39
+ },
40
+ get registry() {
41
+ return d.registry;
42
+ }
43
+ });
44
+ } });
45
+ }
46
+ });
47
+ }
48
+ });
49
+ })();
50
+ }
51
+ });
26
52
  };
27
- export {
28
- h as PlayRenderer
29
- };
30
- //# sourceMappingURL=PlayRenderer.js.map
53
+ //#endregion
54
+ export { d as PlayRenderer };
55
+
56
+ //# sourceMappingURL=PlayRenderer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlayRenderer.js","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"],"names":["PlayRenderer","props","view","setView","createSignal","actor","currentView","get","onMount","watcher","Signal","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","_$insert","_$createComponent","Dynamic","_$mergeProps"],"mappings":";;;;AAiEO,MAAMA,IAA8CC,CAAAA,MAAU;AAGpE,QAAM,CAACC,GAAMC,CAAO,IAAIC,EAAwBH,EAAMI,MAAMC,YAAYC,KAAkB;AAI1FC,EAAAA,EAAQ,MAAM;AACb,UAAMC,IAAU,IAAIC,EAAOC,OAAOC,QAAQ,MAAM;AAC/CC,qBAAe,MAAM;AAEpBJ,QAAAA,EAAQK,WAAAA,GAGRX,EAAQF,EAAMI,MAAMC,YAAYC,IAAAA,CAAkB,GAIlDE,EAAQM,MAAMd,EAAMI,MAAMC,WAAW;AAAA,MACtC,CAAC;AAAA,IACF,CAAC;AAGDG,IAAAA,EAAQM,MAAMd,EAAMI,MAAMC,WAAW;AAAA,EAItC,CAAC;AAGD,QAAMU,IAAYf,EAAMI,MAAMY,KAAKC,KAAKjB,EAAMI,KAAK;AAEnD,SAAA,CAAAc,EAAA,MAGGA,EAAA,MAAA,CAACjB,EAAAA,CAAM,EAAA,MAAKD,EAAMmB,YAAY,KAAK,GAAAD,EAAA,MAGnCA,EAAA,MAAA,CAAA,EAAAjB,EAAAA,KACA,CAACD,EAAMoB,WAAU,EAAA,MAEhBC,QAAQC,MACP,yBAAyBtB,EAAMoB,eAAe,OAAO,SAAS,WAAW,8BAC5CnB,EAAAA,EAAQsB,SAAS,IAC/C,GACOvB,EAAMmB,YAAY,KACtB,GAAAD,EAAA,MAGJA,EAAA,MAAA,CAAA,EAAAjB,EAAAA,KACAD,EAAMoB,cACN,CAACpB,EAAMoB,WAAWnB,EAAAA,EAAQsB,SAAS,EAAC,EAAA,MAEnCF,QAAQC,MACP,cAAcrB,EAAAA,EAAQsB,SAAS,iDACLC,OAAOC,KAAKzB,EAAMoB,UAAU,EAAEM,KAAK,IAAI,CAAC,EACnE,IACA,MAAA;AAAA,QAAAC,IAAAC,KAAAC,IAAAF,EAAAG,YAAAC,IAAAF,EAAAG;AAAAD,WAAAA,EAAAC,aAAAC,EAAAN,GAAA,MAEc1B,EAAAA,EAAQsB,WAASQ,CAAA,GAAAE,EAAAN,GAAA,MAC5BH,OAAOC,KAAKzB,EAAMoB,UAAU,EAAEM,KAAK,IAAI,GAAC,IAAA,GAAAC;AAAAA,EAAA,GAAA,EAGxC,GAAAT,EAAA,MAGJA,EAAA,MAAA,CAAA,EAAAjB,EAAAA,KAAUD,EAAMoB,cAAcpB,EAAMoB,WAAWnB,EAAAA,EAAQsB,SAAS,EAAC,OAAAW,EAChEC,GAAOC,EAAA;AAAA,IAAA,IACPb,YAAS;AAAA,aAAEvB,EAAMoB,WAAWnB,EAAAA,EAAQsB,SAAS;AAAA,IAAC;AAAA,EAAA,GAAA,MAC1CtB,EAAAA,EAAQD,OAAK;AAAA,IACjBgB,MAAMD;AAAAA,EAAAA,CAAS,CAAA,CAEhB,CAAA;AAGJ;"}
1
+ {"version":3,"file":"PlayRenderer.js","names":["createSignal","onCleanup","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, 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 — subscribe synchronously during setup\n\t// (matching React/Vue behavior; onMount would defer until after DOM insertion,\n\t// silently dropping signal changes between createSignal and mount)\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{(() => {\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,IAAagB,KAA8CC,MAAU;CAGpE,IAAM,CAACC,GAAMC,KAAWnB,EACvBiB,EAAMG,MAAMC,YAAYC,KAAK,CAC7B,EAGGC,IAAmC,MACnCC,IAAgC,MAK9BC,IAAUd,EAAYM,EAAMG,MAAMC,cAAcK,MAAkC;AACvFP,IAAQO,EAAS;GAChB;AAKF,QAJAzB,QAAgB;AACfwB,KAAS;GACR,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,5 +1,4 @@
1
- import { PlayRenderer as o } from "./PlayRenderer.js";
2
- export {
3
- o as PlayRenderer
4
- };
5
- //# sourceMappingURL=index.js.map
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 };
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-AFLK3Q4T.js","names":["z.union","z.string","z.number","z.boolean","z.null","z.object"],"sources":["../../../../../../../node_modules/@json-render/core/dist/chunk-AFLK3Q4T.mjs"],"sourcesContent":["// src/types.ts\nimport { z } from \"zod\";\nvar DynamicValueSchema = z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.object({ $state: z.string() })\n]);\nvar DynamicStringSchema = z.union([\n z.string(),\n z.object({ $state: z.string() })\n]);\nvar DynamicNumberSchema = z.union([\n z.number(),\n z.object({ $state: z.string() })\n]);\nvar DynamicBooleanSchema = z.union([\n z.boolean(),\n z.object({ $state: z.string() })\n]);\nfunction resolveDynamicValue(value, stateModel) {\n if (value === null || value === void 0) {\n return void 0;\n }\n if (typeof value === \"object\" && \"$state\" in value) {\n return getByPath(stateModel, value.$state);\n }\n return value;\n}\nfunction unescapeJsonPointer(token) {\n return token.replace(/~1/g, \"/\").replace(/~0/g, \"~\");\n}\nfunction parseJsonPointer(path) {\n const raw = path.startsWith(\"/\") ? path.slice(1).split(\"/\") : path.split(\"/\");\n return raw.map(unescapeJsonPointer);\n}\nfunction getByPath(obj, path) {\n if (!path || path === \"/\") {\n return obj;\n }\n const segments = parseJsonPointer(path);\n let current = obj;\n for (const segment of segments) {\n if (current === null || current === void 0) {\n return void 0;\n }\n if (Array.isArray(current)) {\n const index = parseInt(segment, 10);\n current = current[index];\n } else if (typeof current === \"object\") {\n current = current[segment];\n } else {\n return void 0;\n }\n }\n return current;\n}\nfunction isNumericIndex(str) {\n return /^\\d+$/.test(str);\n}\nfunction setByPath(obj, path, value) {\n const segments = parseJsonPointer(path);\n if (segments.length === 0) return;\n let current = obj;\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i];\n const nextSegment = segments[i + 1];\n const nextIsNumeric = nextSegment !== void 0 && (isNumericIndex(nextSegment) || nextSegment === \"-\");\n if (Array.isArray(current)) {\n const index = parseInt(segment, 10);\n if (current[index] === void 0 || typeof current[index] !== \"object\") {\n current[index] = nextIsNumeric ? [] : {};\n }\n current = current[index];\n } else {\n if (!(segment in current) || typeof current[segment] !== \"object\") {\n current[segment] = nextIsNumeric ? [] : {};\n }\n current = current[segment];\n }\n }\n const lastSegment = segments[segments.length - 1];\n if (Array.isArray(current)) {\n if (lastSegment === \"-\") {\n current.push(value);\n } else {\n const index = parseInt(lastSegment, 10);\n current[index] = value;\n }\n } else {\n current[lastSegment] = value;\n }\n}\nfunction addByPath(obj, path, value) {\n const segments = parseJsonPointer(path);\n if (segments.length === 0) return;\n let current = obj;\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i];\n const nextSegment = segments[i + 1];\n const nextIsNumeric = nextSegment !== void 0 && (isNumericIndex(nextSegment) || nextSegment === \"-\");\n if (Array.isArray(current)) {\n const index = parseInt(segment, 10);\n if (current[index] === void 0 || typeof current[index] !== \"object\") {\n current[index] = nextIsNumeric ? [] : {};\n }\n current = current[index];\n } else {\n if (!(segment in current) || typeof current[segment] !== \"object\") {\n current[segment] = nextIsNumeric ? [] : {};\n }\n current = current[segment];\n }\n }\n const lastSegment = segments[segments.length - 1];\n if (Array.isArray(current)) {\n if (lastSegment === \"-\") {\n current.push(value);\n } else {\n const index = parseInt(lastSegment, 10);\n current.splice(index, 0, value);\n }\n } else {\n current[lastSegment] = value;\n }\n}\nfunction removeByPath(obj, path) {\n const segments = parseJsonPointer(path);\n if (segments.length === 0) return;\n let current = obj;\n for (let i = 0; i < segments.length - 1; i++) {\n const segment = segments[i];\n if (Array.isArray(current)) {\n const index = parseInt(segment, 10);\n if (current[index] === void 0 || typeof current[index] !== \"object\") {\n return;\n }\n current = current[index];\n } else {\n if (!(segment in current) || typeof current[segment] !== \"object\") {\n return;\n }\n current = current[segment];\n }\n }\n const lastSegment = segments[segments.length - 1];\n if (Array.isArray(current)) {\n const index = parseInt(lastSegment, 10);\n if (index >= 0 && index < current.length) {\n current.splice(index, 1);\n }\n } else {\n delete current[lastSegment];\n }\n}\nfunction deepEqual(a, b) {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== typeof b) return false;\n if (typeof a !== \"object\") return false;\n if (Array.isArray(a)) {\n if (!Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n return a.every((item, i) => deepEqual(item, b[i]));\n }\n const aObj = a;\n const bObj = b;\n const aKeys = Object.keys(aObj);\n const bKeys = Object.keys(bObj);\n if (aKeys.length !== bKeys.length) return false;\n return aKeys.every((key) => deepEqual(aObj[key], bObj[key]));\n}\nfunction findFormValue(fieldName, params, state) {\n if (params?.[fieldName] !== void 0) {\n const val = params[fieldName];\n if (typeof val !== \"string\" || !val.includes(\".\")) {\n return val;\n }\n }\n if (params) {\n for (const key of Object.keys(params)) {\n if (key.endsWith(`.${fieldName}`)) {\n const val = params[key];\n if (typeof val !== \"string\" || !val.includes(\".\")) {\n return val;\n }\n }\n }\n }\n if (state) {\n for (const key of Object.keys(state)) {\n if (key === fieldName || key.endsWith(`.${fieldName}`)) {\n return state[key];\n }\n }\n const val = getByPath(state, fieldName);\n if (val !== void 0) {\n return val;\n }\n }\n return void 0;\n}\nfunction parseSpecStreamLine(line) {\n const trimmed = line.trim();\n if (!trimmed || !trimmed.startsWith(\"{\")) return null;\n try {\n const patch = JSON.parse(trimmed);\n if (patch.op && patch.path !== void 0) {\n return patch;\n }\n return null;\n } catch {\n return null;\n }\n}\nfunction applySpecStreamPatch(obj, patch) {\n switch (patch.op) {\n case \"add\":\n addByPath(obj, patch.path, patch.value);\n break;\n case \"replace\":\n setByPath(obj, patch.path, patch.value);\n break;\n case \"remove\":\n removeByPath(obj, patch.path);\n break;\n case \"move\": {\n if (!patch.from) break;\n const moveValue = getByPath(obj, patch.from);\n removeByPath(obj, patch.from);\n addByPath(obj, patch.path, moveValue);\n break;\n }\n case \"copy\": {\n if (!patch.from) break;\n const copyValue = getByPath(obj, patch.from);\n addByPath(obj, patch.path, copyValue);\n break;\n }\n case \"test\": {\n const actual = getByPath(obj, patch.path);\n if (!deepEqual(actual, patch.value)) {\n throw new Error(\n `Test operation failed: value at \"${patch.path}\" does not match`\n );\n }\n break;\n }\n }\n return obj;\n}\nfunction applySpecPatch(spec, patch) {\n applySpecStreamPatch(spec, patch);\n return spec;\n}\nfunction nestedToFlat(nested) {\n const elements = {};\n let counter = 0;\n function walk(node) {\n const key = `el-${counter++}`;\n const { type, props, children: rawChildren, ...rest } = node;\n const childKeys = [];\n if (Array.isArray(rawChildren)) {\n for (const child of rawChildren) {\n if (child && typeof child === \"object\" && \"type\" in child) {\n childKeys.push(walk(child));\n }\n }\n }\n const element = {\n type: type ?? \"unknown\",\n props: props ?? {},\n children: childKeys\n };\n for (const [k, v] of Object.entries(rest)) {\n if (k !== \"state\" && v !== void 0) {\n element[k] = v;\n }\n }\n elements[key] = element;\n return key;\n }\n const root = walk(nested);\n const spec = { root, elements };\n if (nested.state && typeof nested.state === \"object\" && !Array.isArray(nested.state)) {\n spec.state = nested.state;\n }\n return spec;\n}\nfunction compileSpecStream(stream, initial = {}) {\n const lines = stream.split(\"\\n\");\n const result = { ...initial };\n for (const line of lines) {\n const patch = parseSpecStreamLine(line);\n if (patch) {\n applySpecStreamPatch(result, patch);\n }\n }\n return result;\n}\nfunction createSpecStreamCompiler(initial = {}) {\n let result = { ...initial };\n let buffer = \"\";\n const appliedPatches = [];\n const processedLines = /* @__PURE__ */ new Set();\n return {\n push(chunk) {\n buffer += chunk;\n const newPatches = [];\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || processedLines.has(trimmed)) continue;\n processedLines.add(trimmed);\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n applySpecStreamPatch(result, patch);\n appliedPatches.push(patch);\n newPatches.push(patch);\n }\n }\n if (newPatches.length > 0) {\n result = { ...result };\n }\n return { result, newPatches };\n },\n getResult() {\n if (buffer.trim()) {\n const patch = parseSpecStreamLine(buffer);\n if (patch && !processedLines.has(buffer.trim())) {\n processedLines.add(buffer.trim());\n applySpecStreamPatch(result, patch);\n appliedPatches.push(patch);\n result = { ...result };\n }\n buffer = \"\";\n }\n return result;\n },\n getPatches() {\n return [...appliedPatches];\n },\n reset(newInitial = {}) {\n result = { ...newInitial };\n buffer = \"\";\n appliedPatches.length = 0;\n processedLines.clear();\n }\n };\n}\nfunction createMixedStreamParser(callbacks) {\n let buffer = \"\";\n let inSpecFence = false;\n function processLine(line) {\n const trimmed = line.trim();\n if (!inSpecFence && trimmed.startsWith(\"```spec\")) {\n inSpecFence = true;\n return;\n }\n if (inSpecFence && trimmed === \"```\") {\n inSpecFence = false;\n return;\n }\n if (!trimmed) return;\n if (inSpecFence) {\n const patch2 = parseSpecStreamLine(trimmed);\n if (patch2) {\n callbacks.onPatch(patch2);\n }\n return;\n }\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n callbacks.onPatch(patch);\n } else {\n callbacks.onText(line);\n }\n }\n return {\n push(chunk) {\n buffer += chunk;\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() || \"\";\n for (const line of lines) {\n processLine(line);\n }\n },\n flush() {\n if (buffer.trim()) {\n processLine(buffer);\n }\n buffer = \"\";\n }\n };\n}\nvar SPEC_FENCE_OPEN = \"```spec\";\nvar SPEC_FENCE_CLOSE = \"```\";\nfunction createJsonRenderTransform() {\n let lineBuffer = \"\";\n let currentTextId = \"\";\n let buffering = false;\n let inSpecFence = false;\n let inTextBlock = false;\n let textIdCounter = 0;\n function closeTextBlock(controller) {\n if (inTextBlock) {\n controller.enqueue({ type: \"text-end\", id: currentTextId });\n inTextBlock = false;\n }\n }\n function ensureTextBlock(controller) {\n if (!inTextBlock) {\n textIdCounter++;\n currentTextId = String(textIdCounter);\n controller.enqueue({ type: \"text-start\", id: currentTextId });\n inTextBlock = true;\n }\n }\n function emitTextDelta(delta, controller) {\n ensureTextBlock(controller);\n controller.enqueue({ type: \"text-delta\", id: currentTextId, delta });\n }\n function emitPatch(patch, controller) {\n closeTextBlock(controller);\n controller.enqueue({\n type: SPEC_DATA_PART_TYPE,\n data: { type: \"patch\", patch }\n });\n }\n function flushBuffer(controller) {\n if (!lineBuffer) return;\n const trimmed = lineBuffer.trim();\n if (inSpecFence) {\n if (trimmed) {\n const patch = parseSpecStreamLine(trimmed);\n if (patch) emitPatch(patch, controller);\n }\n lineBuffer = \"\";\n buffering = false;\n return;\n }\n if (trimmed) {\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n emitPatch(patch, controller);\n } else {\n emitTextDelta(lineBuffer, controller);\n }\n } else {\n emitTextDelta(lineBuffer, controller);\n }\n lineBuffer = \"\";\n buffering = false;\n }\n function processCompleteLine(line, controller) {\n const trimmed = line.trim();\n if (!inSpecFence && trimmed.startsWith(SPEC_FENCE_OPEN)) {\n inSpecFence = true;\n return;\n }\n if (inSpecFence && trimmed === SPEC_FENCE_CLOSE) {\n inSpecFence = false;\n return;\n }\n if (inSpecFence) {\n if (trimmed) {\n const patch2 = parseSpecStreamLine(trimmed);\n if (patch2) emitPatch(patch2, controller);\n }\n return;\n }\n if (!trimmed) {\n emitTextDelta(\"\\n\", controller);\n return;\n }\n const patch = parseSpecStreamLine(trimmed);\n if (patch) {\n emitPatch(patch, controller);\n } else {\n emitTextDelta(line + \"\\n\", controller);\n }\n }\n return new TransformStream({\n transform(chunk, controller) {\n switch (chunk.type) {\n case \"text-start\": {\n const id = chunk.id;\n const idNum = parseInt(id, 10);\n if (!isNaN(idNum) && idNum >= textIdCounter) {\n textIdCounter = idNum;\n }\n currentTextId = id;\n inTextBlock = true;\n controller.enqueue(chunk);\n break;\n }\n case \"text-delta\": {\n const delta = chunk;\n const text = delta.delta;\n for (let i = 0; i < text.length; i++) {\n const ch = text.charAt(i);\n if (ch === \"\\n\") {\n if (buffering) {\n processCompleteLine(lineBuffer, controller);\n lineBuffer = \"\";\n buffering = false;\n } else {\n if (!inSpecFence) {\n emitTextDelta(\"\\n\", controller);\n }\n }\n } else if (lineBuffer.length === 0 && !buffering) {\n if (inSpecFence || ch === \"{\" || ch === \"`\") {\n buffering = true;\n lineBuffer += ch;\n } else {\n emitTextDelta(ch, controller);\n }\n } else if (buffering) {\n lineBuffer += ch;\n } else {\n emitTextDelta(ch, controller);\n }\n }\n break;\n }\n case \"text-end\": {\n flushBuffer(controller);\n if (inTextBlock) {\n controller.enqueue({ type: \"text-end\", id: currentTextId });\n inTextBlock = false;\n }\n break;\n }\n default: {\n controller.enqueue(chunk);\n break;\n }\n }\n },\n flush(controller) {\n flushBuffer(controller);\n closeTextBlock(controller);\n }\n });\n}\nvar SPEC_DATA_PART = \"spec\";\nvar SPEC_DATA_PART_TYPE = `data-${SPEC_DATA_PART}`;\nfunction pipeJsonRender(stream) {\n return stream.pipeThrough(\n createJsonRenderTransform()\n );\n}\n\n// src/state-store.ts\nfunction immutableSetByPath(root, path, value) {\n const segments = parseJsonPointer(path);\n if (segments.length === 0) return root;\n const result = { ...root };\n let current = result;\n for (let i = 0; i < segments.length - 1; i++) {\n const seg = segments[i];\n const child = current[seg];\n if (Array.isArray(child)) {\n current[seg] = [...child];\n } else if (child !== null && typeof child === \"object\") {\n current[seg] = { ...child };\n } else {\n const nextSeg = segments[i + 1];\n current[seg] = nextSeg !== void 0 && /^\\d+$/.test(nextSeg) ? [] : {};\n }\n current = current[seg];\n }\n const lastSeg = segments[segments.length - 1];\n if (Array.isArray(current)) {\n if (lastSeg === \"-\") {\n current.push(value);\n } else {\n current[parseInt(lastSeg, 10)] = value;\n }\n } else {\n current[lastSeg] = value;\n }\n return result;\n}\nfunction createStateStore(initialState = {}) {\n let state = { ...initialState };\n const listeners = /* @__PURE__ */ new Set();\n function notify() {\n for (const listener of listeners) {\n listener();\n }\n }\n return {\n get(path) {\n return getByPath(state, path);\n },\n set(path, value) {\n if (getByPath(state, path) === value) return;\n state = immutableSetByPath(state, path, value);\n notify();\n },\n update(updates) {\n let changed = false;\n let next = state;\n for (const [path, value] of Object.entries(updates)) {\n if (getByPath(next, path) !== value) {\n next = immutableSetByPath(next, path, value);\n changed = true;\n }\n }\n if (!changed) return;\n state = next;\n notify();\n },\n getSnapshot() {\n return state;\n },\n getServerSnapshot() {\n return state;\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }\n };\n}\nfunction createStoreAdapter(config) {\n return {\n get(path) {\n return getByPath(config.getSnapshot(), path);\n },\n set(path, value) {\n const current = config.getSnapshot();\n if (getByPath(current, path) === value) return;\n config.setSnapshot(immutableSetByPath(current, path, value));\n },\n update(updates) {\n let next = config.getSnapshot();\n let changed = false;\n for (const [path, value] of Object.entries(updates)) {\n if (getByPath(next, path) !== value) {\n next = immutableSetByPath(next, path, value);\n changed = true;\n }\n }\n if (!changed) return;\n config.setSnapshot(next);\n },\n getSnapshot: config.getSnapshot,\n getServerSnapshot: config.getSnapshot,\n subscribe: config.subscribe\n };\n}\nvar MAX_FLATTEN_DEPTH = 20;\nfunction flattenToPointers(obj, prefix = \"\", _depth = 0, _seen, _warned) {\n const seen = _seen ?? /* @__PURE__ */ new Set();\n const warned = _warned ?? { current: false };\n const result = {};\n for (const [key, value] of Object.entries(obj)) {\n const pointer = `${prefix}/${key}`;\n if (_depth < MAX_FLATTEN_DEPTH && value !== null && typeof value === \"object\" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype && !seen.has(value)) {\n seen.add(value);\n Object.assign(\n result,\n flattenToPointers(\n value,\n pointer,\n _depth + 1,\n seen,\n warned\n )\n );\n } else {\n if (process.env.NODE_ENV !== \"production\" && !warned.current && _depth >= MAX_FLATTEN_DEPTH && value !== null && typeof value === \"object\" && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype && !seen.has(value)) {\n warned.current = true;\n console.warn(\n `flattenToPointers: depth limit (${MAX_FLATTEN_DEPTH}) reached. Nested state beyond this depth will be treated as a leaf value.`\n );\n }\n result[pointer] = value;\n }\n }\n return result;\n}\n\nexport {\n DynamicValueSchema,\n DynamicStringSchema,\n DynamicNumberSchema,\n DynamicBooleanSchema,\n resolveDynamicValue,\n getByPath,\n setByPath,\n addByPath,\n removeByPath,\n findFormValue,\n parseSpecStreamLine,\n applySpecStreamPatch,\n applySpecPatch,\n nestedToFlat,\n compileSpecStream,\n createSpecStreamCompiler,\n createMixedStreamParser,\n createJsonRenderTransform,\n SPEC_DATA_PART,\n SPEC_DATA_PART_TYPE,\n pipeJsonRender,\n immutableSetByPath,\n createStateStore,\n createStoreAdapter,\n flattenToPointers\n};\n//# sourceMappingURL=chunk-AFLK3Q4T.mjs.map"],"x_google_ignoreList":[0],"mappings":"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"}