@xmachines/play-vue 1.0.0-beta.8 → 1.0.0

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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -208
  3. package/dist/ActorProvider.js +8 -0
  4. package/dist/ActorProvider.js.map +1 -0
  5. package/dist/ActorProvider.vue.d.ts +51 -0
  6. package/dist/ActorProvider.vue.d.ts.map +1 -0
  7. package/dist/ActorProvider.vue_vue_type_script_lang.js +125 -0
  8. package/dist/ActorProvider.vue_vue_type_script_lang.js.map +1 -0
  9. package/dist/PlayRenderer.js +2 -4
  10. package/dist/PlayRenderer.js.map +1 -1
  11. package/dist/PlayRenderer.vue.d.ts +5 -0
  12. package/dist/PlayRenderer.vue.d.ts.map +1 -0
  13. package/dist/PlayRenderer.vue_vue_type_script_lang.js +10 -27
  14. package/dist/PlayRenderer.vue_vue_type_script_lang.js.map +1 -1
  15. package/dist/PlayUIProvider.js +7 -0
  16. package/dist/PlayUIProvider.js.map +1 -0
  17. package/dist/PlayUIProvider.vue.d.ts +76 -0
  18. package/dist/PlayUIProvider.vue.d.ts.map +1 -0
  19. package/dist/PlayUIProvider.vue_vue_type_script_lang.js +86 -0
  20. package/dist/PlayUIProvider.vue_vue_type_script_lang.js.map +1 -0
  21. package/dist/actor-provider-context.d.ts +40 -0
  22. package/dist/actor-provider-context.d.ts.map +1 -0
  23. package/dist/actor-provider-context.js +11 -0
  24. package/dist/actor-provider-context.js.map +1 -0
  25. package/dist/define-registry.d.ts +79 -0
  26. package/dist/define-registry.d.ts.map +1 -0
  27. package/dist/define-registry.js +27 -0
  28. package/dist/define-registry.js.map +1 -0
  29. package/dist/index.d.ts +21 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +8 -2
  32. package/dist/types.d.ts +19 -12
  33. package/dist/types.d.ts.map +1 -1
  34. package/dist/useActor.d.ts +50 -0
  35. package/dist/useActor.d.ts.map +1 -0
  36. package/dist/useActor.js +14 -0
  37. package/dist/useActor.js.map +1 -0
  38. package/package.json +37 -15
  39. package/dist/_virtual/_plugin-vue_export-helper.js +0 -8
  40. package/dist/index.css +0 -2
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,280 +1,209 @@
1
- # @xmachines/play-vue
1
+ <!-- generated-by: gsd-doc-writer -->
2
2
 
3
- **Vue renderer consuming signals and UI schema with provider pattern**
3
+ # `@xmachines/play-vue`
4
4
 
5
- Signal-driven Vue rendering layer observing actor state with zero Vue state for business logic.
5
+ > Vue 3 renderer for the XMachines Play Architecture passively observes actor signals and renders UI via `@xmachines/json-render-vue`.
6
6
 
7
- ## Overview
7
+ Part of the [XMachines Play monorepo](../../README.md).
8
+
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
10
+ [![Version](https://img.shields.io/badge/version-1.0.0--beta.51-blue.svg)](package.json)
8
11
 
9
- `@xmachines/play-vue` provides `PlayRenderer` for building Vue 3 UIs that passively observe actor signals. This package enables framework-swappable architecture where Vue is just a rendering target subscribing to signal changes — business logic lives entirely in the actor.
12
+ ---
13
+
14
+ ## Overview
10
15
 
11
- Per [RFC Play v1](https://gitlab.com/xmachin-es/rfc/-/blob/main/src/play-v1.md), this package implements:
16
+ `@xmachines/play-vue` is the Vue 3 rendering layer for XMachines Play. It bridges TC39 Signals (actor state) to Vue reactivity and drives component rendering through `@xmachines/json-render-vue`.
12
17
 
13
- - **Signal-Only Reactivity (INV-05):** No refs/reactive for business logic, TC39 signals only
14
- - **Passive Infrastructure (INV-04):** Components observe signals, send events to actor
18
+ **Architecture invariants this package upholds:**
15
19
 
16
- **Key Principle:** Vue state is never used for business logic. Signals are the source of truth.
20
+ - **Passive Infrastructure** Components observe actor signals; they never decide state transitions.
21
+ - **Signal-Only Reactivity** — TC39 Signals are the source of truth; Vue reactivity is used only to trigger re-renders.
22
+ - **Actor Authority** — The actor controls view selection; the renderer reflects it.
17
23
 
18
- Renderer receives actor via props (provider pattern), not children.
24
+ ---
19
25
 
20
26
  ## Installation
21
27
 
22
28
  ```bash
23
- npm install vue@^3.0.0
24
- npm install @xmachines/play-vue
29
+ pnpm add @xmachines/play-vue
25
30
  ```
26
31
 
27
- ## Current Exports
32
+ **Peer dependencies** (install alongside):
28
33
 
29
- - `PlayRenderer` (Vue component)
30
- - `PlayRendererProps` (TypeScript interface)
31
-
32
- **Peer dependencies:**
34
+ ```bash
35
+ pnpm add vue@^3.5.0 xstate@^5.31.0 @xstate/store@^3.17.0 @xmachines/json-render-vue@^0.18.0 @xmachines/json-render-core@^0.18.0 @xmachines/json-render-xstate@^0.18.0
36
+ ```
33
37
 
34
- - `vue` ^3.0.0 - Vue 3 runtime
38
+ ---
35
39
 
36
40
  ## Quick Start
37
41
 
38
42
  ```vue
39
43
  <!-- App.vue -->
44
+ <template>
45
+ <PlayUIProvider :actor="actor" :registryResult="registryResult">
46
+ <PlayRenderer />
47
+ </PlayUIProvider>
48
+ </template>
49
+
40
50
  <script setup lang="ts">
51
+ import { defineRegistry, PlayUIProvider, PlayRenderer } from "@xmachines/play-vue";
41
52
  import { definePlayer } from "@xmachines/play-xstate";
42
- import { defineCatalog } from "@xmachines/play-catalog";
43
- import { PlayRenderer } from "@xmachines/play-vue";
44
- import { z } from "zod";
45
- import LoginForm from "./components/LoginForm.vue";
46
- import Dashboard from "./components/Dashboard.vue";
47
-
48
- // 1. Define catalog (business logic layer)
49
- const catalog = defineCatalog({
50
- LoginForm: z.object({ error: z.string().optional() }),
51
- Dashboard: z.object({
52
- userId: z.string(),
53
- username: z.string(),
54
- }),
55
- });
56
-
57
- // 2. Define component map (view layer)
58
- const components = {
59
- LoginForm,
60
- Dashboard,
61
- };
53
+ import { myMachine } from "./machine.js";
54
+ import { myCatalog } from "./catalog.js";
55
+ import HomeSFC from "./views/Home.vue";
56
+ import LoginSFC from "./views/Login.vue";
62
57
 
63
- // 3. Create player actor (business logic runtime)
64
- const createPlayer = definePlayer({ machine: authMachine, catalog });
58
+ const createPlayer = definePlayer({ machine: myMachine });
65
59
  const actor = createPlayer();
66
60
  actor.start();
67
- </script>
68
61
 
69
- <template>
70
- <!-- 4. Render UI (actor via props) -->
71
- <PlayRenderer :actor="actor" :components="components">
72
- <template #fallback>
73
- <div>Loading...</div>
74
- </template>
75
- </PlayRenderer>
76
- </template>
62
+ const registryResult = defineRegistry(myCatalog, {
63
+ components: {
64
+ Home: HomeSFC, // .vue SFCs are auto-wrapped
65
+ Login: LoginSFC,
66
+ },
67
+ actions: {
68
+ login: async (args) => actor.send({ type: "auth.login", ...args }),
69
+ logout: async () => actor.send({ type: "auth.logout" }),
70
+ },
71
+ });
72
+ </script>
77
73
  ```
78
74
 
79
- ## API Reference
75
+ ---
80
76
 
81
- ### PlayRenderer
77
+ ## API Summary
82
78
 
83
- Main renderer component subscribing to actor signals and dynamically rendering catalog components:
79
+ ### Components
84
80
 
85
- ```typescript
86
- interface PlayRendererProps {
87
- actor: AbstractActor<any>;
88
- components: Record<string, Component>;
89
- }
90
- ```
81
+ #### `<PlayUIProvider>`
91
82
 
92
- **Props:**
83
+ Batteries-included composite provider. Wraps `<ActorProvider>` and `JSONUIProvider` in one component. **Recommended for most apps.**
93
84
 
94
- - `actor` - Actor instance with `currentView` signal
95
- - `components` - Map of component names to Vue components
85
+ | Prop | Type | Required | Description |
86
+ | --------------------- | -------------------------- | -------- | ------------------------------------------- |
87
+ | `actor` | `AbstractActor & Viewable` | ✅ | The XMachines actor instance |
88
+ | `registryResult` | `DefineRegistryResult` | ✅ | Result of `defineRegistry()` |
89
+ | `store` | `StateStore` | — | External controlled state store (optional) |
90
+ | `onRenderError` | `RenderErrorHandler` | — | Error handler for render failures |
91
+ | `navigate` | `(path: string) => void` | — | Link navigation function |
92
+ | `validationFunctions` | `Record<string, Function>` | — | Custom validation functions |
93
+ | `functions` | `Record<string, Function>` | — | Named functions for `$computed` expressions |
96
94
 
97
- **Slots:**
95
+ **Slots:** `default` (rendered content), `fallback` (shown while actor view is `null`)
98
96
 
99
- - `fallback` - Slot shown when `currentView` is null
97
+ #### `<PlayRenderer>`
100
98
 
101
- **Behavior:**
99
+ Zero-prop leaf component. Reads the current `spec` and `registry` from the nearest `<ActorProvider>` or `<PlayUIProvider>` context and renders via `<Renderer>`. Must be placed inside one of those providers.
102
100
 
103
- 1. Subscribes to `actor.currentView` signal using a `Signal.subtle.Watcher` inside a Vue component
104
- 2. Looks up component from `components` map using `view.component` string
105
- 3. Renders component with props from `view.props` + `send` function via Vue's dynamic `<component :is="..."/>`
101
+ ```vue
102
+ <PlayUIProvider :actor="actor" :registryResult="registryResult">
103
+ <PlayRenderer />
104
+ </PlayUIProvider>
105
+ ```
106
106
 
107
- **Example Component (LoginForm.vue):**
107
+ #### `<ActorProvider>`
108
108
 
109
- ```vue
110
- <script setup lang="ts">
111
- import type { AbstractActor } from "@xmachines/play-actor";
112
- import { ref } from "vue";
113
-
114
- const props = defineProps<{
115
- error?: string;
116
- send: AbstractActor<any>["send"];
117
- }>();
118
-
119
- const username = ref("");
120
-
121
- function handleSubmit() {
122
- props.send({
123
- type: "auth.login",
124
- username: username.value,
125
- });
126
- }
127
- </script>
109
+ Low-level escape hatch for custom provider composition. Owns the full actor lifecycle — signal subscription, per-view state store, handler resolution, and Vue context provision. Use `<PlayUIProvider>` unless you need fine-grained control.
128
110
 
129
- <template>
130
- <form @submit.prevent="handleSubmit">
131
- <p v-if="error" style="color: red">{{ error }}</p>
132
- <input v-model="username" required placeholder="Username" />
133
- <button type="submit">Log In</button>
134
- </form>
135
- </template>
136
- ```
111
+ | Prop | Type | Required | Description |
112
+ | ---------------- | -------------------------- | -------- | ------------------------------- |
113
+ | `actor` | `AbstractActor & Viewable` | ✅ | The XMachines actor instance |
114
+ | `registryResult` | `DefineRegistryResult` | ✅ | Result of `defineRegistry()` |
115
+ | `store` | `StateStore` | — | External controlled state store |
116
+ | `onRenderError` | `RenderErrorHandler` | — | Override render error handler |
137
117
 
138
- ## Examples
118
+ ---
139
119
 
140
- ### Provider Pattern
120
+ ### Functions
141
121
 
142
- ```vue
143
- <!-- App.vue -->
144
- <script setup lang="ts">
145
- import { PlayVueRouterProvider } from "@xmachines/play-vue-router";
146
- import { PlayRenderer } from "@xmachines/play-vue";
147
- import { provide } from "vue";
148
- import Header from "./components/Header.vue";
149
- import Footer from "./components/Footer.vue";
150
-
151
- // Provide actor to nested components like Header
152
- provide("actor", actor);
153
- </script>
122
+ #### `defineRegistry(catalog, options)`
154
123
 
155
- <template>
156
- <PlayVueRouterProvider :actor="actor" :router="router" :routeMap="routeMap">
157
- <template #default="{ currentActor, currentRouter }">
158
- <div>
159
- <Header />
160
- <PlayRenderer :actor="currentActor" :components="components" />
161
- <Footer />
162
- </div>
163
- </template>
164
- </PlayVueRouterProvider>
165
- </template>
166
- ```
124
+ Drop-in replacement for `defineRegistry` from `@xmachines/json-render-vue`. **Always import from `@xmachines/play-vue`** rather than `@xmachines/json-render-vue` when working with Vue SFCs — this wrapper automatically detects `.vue` SFCs in the `components` map and wraps them via `h()` so Vue composables (including `inject`-based ones) work correctly inside `<script setup>`.
167
125
 
168
- ```vue
169
- <!-- Header.vue -->
170
- <script setup lang="ts">
171
- import { inject, ref, onMounted, onUnmounted } from "vue";
172
- import type { AbstractActor } from "@xmachines/play-actor";
173
-
174
- const actor = inject<AbstractActor<any>>("actor")!;
175
- const route = ref<string | null>(null);
176
-
177
- let watcher: any;
178
-
179
- onMounted(() => {
180
- let pending = false;
181
- watcher = new Signal.subtle.Watcher(() => {
182
- if (!pending) {
183
- pending = true;
184
- queueMicrotask(() => {
185
- pending = false;
186
- for (const s of watcher.getPending()) s.get();
187
- route.value = actor.currentRoute.get();
188
- watcher.watch(actor.currentRoute);
189
- });
190
- }
191
- });
192
- route.value = actor.currentRoute.get();
193
- watcher.watch(actor.currentRoute);
126
+ ```typescript
127
+ import { defineRegistry } from "@xmachines/play-vue";
128
+ // NOT: import { defineRegistry } from "@xmachines/json-render-vue"
129
+
130
+ import LoginSFC from "./views/Login.vue";
131
+ import DashboardSFC from "./views/Dashboard.vue";
132
+
133
+ const registryResult = defineRegistry(catalog, {
134
+ components: {
135
+ Login: LoginSFC, // .vue SFC — auto-wrapped via h()
136
+ Dashboard: DashboardSFC,
137
+ },
138
+ actions: {
139
+ login: async (args, setState, getState) => {
140
+ /* ... */
141
+ },
142
+ },
194
143
  });
144
+ ```
195
145
 
196
- onUnmounted(() => {
197
- if (watcher) watcher.unwatch(actor.currentRoute);
198
- });
199
- </script>
146
+ Plain `ComponentFn` functions (non-SFC) also work and are passed through unchanged. Mixing SFCs and plain functions in the same registry is supported.
200
147
 
201
- <template>
202
- <header>
203
- <nav>Current: {{ route }}</nav>
204
- </header>
205
- </template>
148
+ #### `useActor()`
149
+
150
+ Vue composable for accessing the raw actor inside a `PlayRenderer` tree. Avoids prop drilling for deeply nested components.
151
+
152
+ ```typescript
153
+ import { useActor } from "@xmachines/play-vue";
154
+
155
+ // Inside a component rendered by PlayRenderer:
156
+ const actor = useActor();
157
+ actor.send({ type: "SUBMIT" });
206
158
  ```
207
159
 
208
- ## Architecture
160
+ Throws if called outside an `<ActorProvider>` or `<PlayUIProvider>` tree.
209
161
 
210
- This package implements **Signal-Only Reactivity (INV-05)** and **Passive Infrastructure (INV-04)**:
162
+ #### `getPlayViewContext()`
211
163
 
212
- 1. **No Business Logic in Vue:**
213
- - No ref/reactive for business state
214
- - No watch/watchEffect for business side effects
215
- - Vue only triggers renders, doesn't control state
164
+ Access the current `ViewContextValue` `{ spec, handlers, registry, store }` — from inside an `<ActorProvider>` tree.
216
165
 
217
- 2. **Signals as Source of Truth:**
218
- - `actor.currentView.get()` provides UI structure
219
- - `actor.currentRoute.get()` provides navigation state
220
- - Components observe signals via explicit watcher patterns
166
+ ```typescript
167
+ import { getPlayViewContext } from "@xmachines/play-vue";
221
168
 
222
- 3. **Event Forwarding:**
223
- - Components receive `send` function via props
224
- - User actions send events to actor (e.g., `{ type: "auth.login" }`)
225
- - Actor guards validate and process events
169
+ // Inside setup() of a component within an ActorProvider tree:
170
+ const view = getPlayViewContext();
171
+ // view.spec, view.handlers, view.registry, view.store
172
+ ```
226
173
 
227
- 4. **Microtask Batching:**
228
- - `Signal.subtle.Watcher` coalesces rapid signal changes
229
- - Prevents Vue thrashing from multiple signal updates
230
- - Single Vue render per microtask batch
174
+ ---
231
175
 
232
- 5. **Explicit Disposal Contract:**
233
- - Component teardown calls watcher `unwatch` in `onUnmounted`
234
- - Do not rely on GC-only cleanup
176
+ ### Re-exported from `@xmachines/json-render-vue`
235
177
 
236
- **Pattern:**
178
+ The following are re-exported so consumers import everything from `@xmachines/play-vue`:
237
179
 
238
- - Renderer receives actor via props (provider pattern)
239
- - Enables composition with navigation, headers, footers
240
- - Supports multiple renderers in same app
180
+ **Components:** `JSONUIProvider`, `StateProvider`, `ActionProvider`, `VisibilityProvider`, `ValidationProvider`, `Renderer`
241
181
 
242
- **Architectural Invariants:**
182
+ **Composables:** `useBoundProp`
243
183
 
244
- - **Signal-Only Reactivity (INV-05):** No Vue state for business logic
245
- - **Passive Infrastructure (INV-04):** Components reflect, never decide
184
+ **Types:** `JSONUIProviderProps`, `StateProviderProps`, `ActionProviderProps`, `ValidationProviderProps`, `RendererProps`, `ComponentFn`, `ComponentContext`, `DefineRegistryResult`
246
185
 
247
- ## Canonical Watcher Lifecycle
186
+ ---
248
187
 
249
- If you write your own custom integration, use the same watcher flow as `PlayRenderer`:
188
+ ## Testing
250
189
 
251
- 1. `notify` callback runs
252
- 2. Schedule work with `queueMicrotask`
253
- 3. Drain `watcher.getPending()`
254
- 4. Read actor signals and update Vue-local ref state
255
- 5. Re-arm with `watch(...)` or `watch()`
190
+ Run tests for this package in isolation:
256
191
 
257
- Watcher notify is one-shot. Re-arm is required for continuous observation.
192
+ ```bash
193
+ # From the monorepo root
194
+ pnpm --filter @xmachines/play-vue test
258
195
 
259
- ## Benefits
196
+ # Watch mode
197
+ pnpm --filter @xmachines/play-vue run test:watch
260
198
 
261
- - **Framework Swappable:** Business logic has zero Vue imports
262
- - **Type Safety:** Props validated against catalog schemas
263
- - **Simple Testing:** Test actors without Vue renderer
264
- - **Performance:** Microtask batching reduces unnecessary renders
265
- - **Composability:** Renderer prop enables complex layouts
199
+ # With coverage (80% threshold enforced on lines, functions, branches, statements)
200
+ pnpm exec vitest run --coverage --config packages/play-vue/vitest.config.ts
201
+ ```
266
202
 
267
- ## Related Packages
203
+ Tests use [Vitest](https://vitest.dev/) with `jsdom` environment and `@vue/test-utils` for component mounting.
268
204
 
269
- - **[@xmachines/play-xstate](../play-xstate)** - XState adapter providing actors
270
- - **[@xmachines/play-catalog](../play-catalog)** - UI schema validation
271
- - **[@xmachines/play-vue-router](../play-vue-router)** - Vue Router integration
272
- - **[@xmachines/play-actor](../play-actor)** - Actor base
273
- - **[@xmachines/play-signals](../play-signals)** - TC39 Signals primitives
205
+ ---
274
206
 
275
207
  ## License
276
208
 
277
- Copyright (c) 2016 [Mikael Karon](mailto:mikael@karon.se). All rights reserved.
278
-
279
- This work is licensed under the terms of the MIT license.
280
- For a copy, see <https://opensource.org/licenses/MIT>.
209
+ MIT see [LICENSE](LICENSE).
@@ -0,0 +1,8 @@
1
+ import "./actor-provider-context.js";
2
+ import e from "./ActorProvider.vue_vue_type_script_lang.js";
3
+ //#region src/ActorProvider.vue
4
+ var t = e;
5
+ //#endregion
6
+ export { t as default };
7
+
8
+ //# sourceMappingURL=ActorProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActorProvider.js","names":[],"sources":["../src/ActorProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * ActorProvider — escape-hatch primitive for the XMachines Vue render architecture.\n *\n * Owns the full actor lifecycle:\n * - Signal subscription (watchSignal) bridging TC39 Signals to Vue reactivity\n * - Per-view StateStore lifecycle (controlled/uncontrolled)\n * - Handler resolution via ActorProviderInner (must be inside StateProvider)\n * - ViewContextValue provision via ViewKey injection key\n * - ActionProvider + VisibilityProvider wrapping for downstream Renderer\n * - onRenderError injection into registry\n *\n * Library authors who need fine-grained control use this directly.\n * End users should use <PlayUIProvider> instead.\n *\n * @invariant Actor Authority - Actor decides all state transitions via guards\n * @invariant Passive Infrastructure - Component observes signals, sends events\n * @invariant Signal-Only Reactivity - Business logic state lives in actor signals\n */\n\nimport {\n\tdefineComponent,\n\tref,\n\tcomputed,\n\ttoRaw,\n\tmarkRaw,\n\tonUnmounted,\n\th,\n\tprovide,\n\tshallowRef,\n\twatch,\n} from \"vue\";\nimport type { PropType } from \"vue\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport { toAtomState, attachRenderErrorHandler } from \"@xmachines/play-actor\";\nimport type { AbstractActor, Viewable, PlaySpec } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport type {\n\tDefineRegistryResult,\n\tSetState,\n\tRenderErrorHandler,\n} from \"@xmachines/json-render-vue\";\n\nimport {\n\tStateProvider,\n\tuseStateStore,\n\tActionProvider,\n\tVisibilityProvider,\n} from \"@xmachines/json-render-vue\";\nimport type { StateStore } from \"@xmachines/json-render-core\";\nimport { createAtom } from \"@xstate/store\";\nimport { xstateStoreStateStore } from \"@xmachines/json-render-xstate\";\nimport { provideActor, type AnyPlayActor } from \"./useActor.js\";\nimport { ViewKey, type ViewContextValue } from \"./actor-provider-context.js\";\nimport type { ActorProviderProps } from \"./types.js\";\n\n// Re-export props type and context accessors for consumers who import from this file\nexport type { ActorProviderProps } from \"./types.js\";\nexport { getPlayViewContext } from \"./actor-provider-context.js\";\nexport type { ViewContextValue } from \"./actor-provider-context.js\";\n\n// ---------------------------------------------------------------------------\n// ActorProviderInner — renders inside StateProvider to call useStateStore()\n// Provides ViewContextValue + ActionProvider + VisibilityProvider for downstream Renderer.\n// ---------------------------------------------------------------------------\n\nconst ActorProviderInner = defineComponent({\n\tname: \"ActorProviderInner\",\n\tprops: {\n\t\tregistryResult: {\n\t\t\ttype: Object as PropType<DefineRegistryResult>,\n\t\t\trequired: true,\n\t\t},\n\t\tspec: {\n\t\t\ttype: Object as PropType<PlaySpec | null>,\n\t\t\tdefault: null,\n\t\t},\n\t\tstore: {\n\t\t\ttype: Object as PropType<StateStore>,\n\t\t\trequired: true,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// Use shallowRef for the view context value to avoid deep reactivity overhead.\n\t\t// The Proxy below allows inject() to always read the latest value.\n\t\tconst viewRef = shallowRef<ViewContextValue | null>(null);\n\n\t\t// Provide the ViewContextValue via Vue's inject/provide system.\n\t\t// Called synchronously in setup() so Vue registers it on the component instance.\n\t\t// Uses Proxy so descendants always receive the latest viewRef.value.\n\t\tprovide(\n\t\t\tViewKey,\n\t\t\tnew Proxy({} as ViewContextValue, {\n\t\t\t\tget(_target, prop: string) {\n\t\t\t\t\treturn viewRef.value?.[prop as keyof ViewContextValue];\n\t\t\t\t},\n\t\t\t}),\n\t\t);\n\n\t\t// Call useStateStore() at setup() time (synchronous, before return).\n\t\t// Vue composables that use inject() must be called during setup(), not in a render function.\n\t\tconst stateStore = useStateStore();\n\n\t\t// Build a SetState adapter: handlers factory expects updater-function pattern.\n\t\t// Defined once in setup() — reads stateStore.getSnapshot/update at invocation\n\t\t// time so it always operates on the live store state without needing to be\n\t\t// recreated when stateStore identity changes across renders.\n\t\tconst setStateAdapter: SetState = (updater) => {\n\t\t\tconst prev = stateStore.getSnapshot();\n\t\t\tstateStore.update(updater(prev));\n\t\t};\n\n\t\t// Memoize handlers keyed to registryResult prop identity. The getter functions\n\t\t// are stable closures capturing stateStore from setup(), so they do not\n\t\t// contribute to invalidation. Handlers are only recomputed when the registry\n\t\t// definition itself changes — not on every render triggered by view updates.\n\t\tconst handlers = computed(() =>\n\t\t\tprops.registryResult.handlers(\n\t\t\t\t() => setStateAdapter,\n\t\t\t\t() => stateStore.getSnapshot(),\n\t\t\t),\n\t\t);\n\n\t\treturn () => {\n\t\t\tif (!props.spec) {\n\t\t\t\tviewRef.value = null;\n\t\t\t\treturn slots.default?.() ?? null;\n\t\t\t}\n\n\t\t\tviewRef.value = {\n\t\t\t\tspec: props.spec,\n\t\t\t\thandlers: handlers.value,\n\t\t\t\tregistry: props.registryResult.registry,\n\t\t\t\tstore: props.store,\n\t\t\t};\n\n\t\t\t// Wrap with ActionProvider + VisibilityProvider so PlayRenderer's Renderer works\n\t\t\t// even when ActorProvider is used directly (without PlayUIProvider / JSONUIProvider)\n\t\t\treturn h(ActionProvider, { handlers: handlers.value }, () =>\n\t\t\t\th(VisibilityProvider, {}, () => slots.default?.() ?? null),\n\t\t\t);\n\t\t};\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// ActorProvider — main export\n// ---------------------------------------------------------------------------\n\nexport default defineComponent({\n\tname: \"ActorProvider\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<ActorProviderProps[\"actor\"]>,\n\t\t\trequired: true,\n\t\t},\n\t\tregistryResult: {\n\t\t\ttype: Object as PropType<DefineRegistryResult>,\n\t\t\trequired: true,\n\t\t},\n\t\tstore: {\n\t\t\ttype: Object as PropType<StateStore>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tonRenderError: {\n\t\t\ttype: Function as PropType<RenderErrorHandler>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// Unwrap actor from Vue's reactive proxy to access raw Signal objects.\n\t\t// Held in a shallowRef so the provided injection and the signal watcher\n\t\t// can follow `props.actor` when the prop swaps after mount.\n\t\tconst actorRef = shallowRef(toRaw(props.actor as AbstractActor<AnyActorLogic> & Viewable));\n\n\t\t// Unwrap the registryResult and mark components as raw to avoid Vue reactivity overhead\n\t\tconst rawRegistry = Object.fromEntries(\n\t\t\tObject.entries(toRaw(props.registryResult).registry).map(([k, v]) => [\n\t\t\t\tk,\n\t\t\t\tmarkRaw(v as object),\n\t\t\t]),\n\t\t) as DefineRegistryResult[\"registry\"];\n\n\t\t// Inject onRenderError prop into registry (non-enumerable, overrides defineRegistry-level\n\t\t// handler). attachRenderErrorHandler clones — the caller's registry is never mutated.\n\t\t// The clone must be re-markRaw'd: markRaw's non-enumerable flag is not carried by the clone.\n\t\tconst rawRegistryResult: DefineRegistryResult = {\n\t\t\t...toRaw(props.registryResult),\n\t\t\tregistry: markRaw(\n\t\t\t\tprops.onRenderError\n\t\t\t\t\t? attachRenderErrorHandler(rawRegistry, props.onRenderError)\n\t\t\t\t\t: rawRegistry,\n\t\t\t),\n\t\t};\n\n\t\t// Provide the actor to all descendants via Vue's provide/inject mechanism.\n\t\t//\n\t\t// A stable Proxy (same pattern as ActorProviderInner's ViewKey proxy) is\n\t\t// provided instead of the raw actor so the injection tracks the CURRENT\n\t\t// `props.actor`: injectors keep the reference obtained at setup() time,\n\t\t// yet every trap resolves against the latest actor (actorRef.value) after a\n\t\t// prop swap. All relevant traps are forwarded so the proxy behaves like the\n\t\t// underlying actor for reads, writes, membership (`in`), enumeration\n\t\t// (Object.keys / spread), and prototype checks (`instanceof`). Methods are\n\t\t// bound to the current raw actor so `this` (including private fields) works\n\t\t// exactly as with a direct call.\n\t\t//\n\t\t// IMPORTANT: this proxy is NOT identity-equal to `props.actor` — it is a\n\t\t// distinct object, hence a distinct WeakMap key. Identity-keyed consumers —\n\t\t// notably RouterBridgeBase's one-bridge-per-actor guard, which keys a\n\t\t// module-level WeakMap on actor identity — must be handed the `props.actor`\n\t\t// value itself, never this injected proxy.\n\t\t//\n\t\t// Method identity: bound methods are cached per underlying method so repeated\n\t\t// reads return the SAME function (actor.send === actor.send — allocation-free\n\t\t// hot path). A read rebinds only when the underlying method changes; the cache\n\t\t// is CLEARED on actor swap (see the watch handler below) so prototype methods\n\t\t// rebind to the new actor.\n\t\tconst boundMethodCache = new Map<PropertyKey, { source: unknown; bound: unknown }>();\n\t\tprovideActor(\n\t\t\tnew Proxy({} as AnyPlayActor, {\n\t\t\t\tget(_target, prop) {\n\t\t\t\t\tconst current = actorRef.value;\n\t\t\t\t\tconst value = Reflect.get(current, prop, current) as unknown;\n\t\t\t\t\tif (typeof value !== \"function\") return value;\n\t\t\t\t\tconst cached = boundMethodCache.get(prop);\n\t\t\t\t\tif (cached && cached.source === value) return cached.bound;\n\t\t\t\t\tconst bound = (value as (...args: unknown[]) => unknown).bind(current);\n\t\t\t\t\tboundMethodCache.set(prop, { source: value, bound });\n\t\t\t\t\treturn bound;\n\t\t\t\t},\n\t\t\t\thas(_target, prop) {\n\t\t\t\t\treturn prop in actorRef.value;\n\t\t\t\t},\n\t\t\t\tset(_target, prop, value) {\n\t\t\t\t\treturn Reflect.set(actorRef.value, prop, value, actorRef.value);\n\t\t\t\t},\n\t\t\t\tdeleteProperty(_target, prop) {\n\t\t\t\t\treturn Reflect.deleteProperty(actorRef.value, prop);\n\t\t\t\t},\n\t\t\t\townKeys() {\n\t\t\t\t\treturn Reflect.ownKeys(actorRef.value);\n\t\t\t\t},\n\t\t\t\tgetOwnPropertyDescriptor(_target, prop) {\n\t\t\t\t\tconst desc = Reflect.getOwnPropertyDescriptor(actorRef.value, prop);\n\t\t\t\t\tif (!desc) return undefined;\n\t\t\t\t\t// Proxy invariant: the proxy target is an empty {}, so every key\n\t\t\t\t\t// reported via ownKeys must resolve to a configurable descriptor here\n\t\t\t\t\t// — otherwise the [[GetOwnProperty]] invariant throws (a non-existent\n\t\t\t\t\t// target property may not be reported as non-configurable). The actor's\n\t\t\t\t\t// own props are runtime-mutable anyway, so reporting configurable:true\n\t\t\t\t\t// is faithful enough for Object.keys / spread enumeration.\n\t\t\t\t\tdesc.configurable = true;\n\t\t\t\t\treturn desc;\n\t\t\t\t},\n\t\t\t\tgetPrototypeOf() {\n\t\t\t\t\treturn Reflect.getPrototypeOf(actorRef.value);\n\t\t\t\t},\n\t\t\t}) as AnyPlayActor,\n\t\t);\n\n\t\t// Seed initial value then subscribe — both synchronous, no scheduler gap.\n\t\t// This mirrors the atomic seed+watch pattern used in Solid (createEffect)\n\t\t// and Svelte ($effect) for cross-framework consistency.\n\t\tconst view = ref<PlaySpec | null>(actorRef.value.currentView.get());\n\n\t\t// Internal per-view store — recreated on each view transition when no external store\n\t\tlet internalStore: StateStore | null = null;\n\t\tlet lastView: PlaySpec | null = null;\n\t\t// storeKey is only incremented in the internalStore branch (not when props.store is set)\n\t\tlet storeKey = 0;\n\n\t\t// Signal watcher for bridging TC39 Signals to Vue reactivity.\n\t\t// Re-assigned on actor swap so cleanup always releases the live watcher.\n\t\tlet unwatch = watchSignal(actorRef.value.currentView, (nextView) => {\n\t\t\tview.value = nextView;\n\t\t});\n\n\t\t// React to `props.actor` swaps: unsubscribe the OLD actor's currentView,\n\t\t// re-seed the view from the NEW actor synchronously (flush: \"sync\" — no\n\t\t// scheduler gap, matching the mount-time seed+watch pattern above), and\n\t\t// subscribe the new signal. The re-seeded view is a new object identity,\n\t\t// so the internal-store branch below rebuilds the store and bumps\n\t\t// storeKey exactly like a regular view transition.\n\t\twatch(\n\t\t\t() => toRaw(props.actor as AbstractActor<AnyActorLogic> & Viewable),\n\t\t\t(nextActor) => {\n\t\t\t\tif (nextActor === actorRef.value) return;\n\t\t\t\tunwatch();\n\t\t\t\tactorRef.value = nextActor;\n\t\t\t\t// Invalidate cached bound methods so prototype methods rebind to the\n\t\t\t\t// new actor on the next read (see the injected proxy's get trap).\n\t\t\t\tboundMethodCache.clear();\n\t\t\t\tview.value = nextActor.currentView.get();\n\t\t\t\tunwatch = watchSignal(nextActor.currentView, (nextView) => {\n\t\t\t\t\tview.value = nextView;\n\t\t\t\t});\n\t\t\t},\n\t\t\t{ flush: \"sync\" },\n\t\t);\n\n\t\tonUnmounted(() => {\n\t\t\tunwatch();\n\t\t});\n\n\t\treturn () => {\n\t\t\t// No view — show fallback slot or nothing\n\t\t\tif (!view.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\tconst spec = view.value;\n\n\t\t\t// Resolve the store: external (controlled) or internal per-view atom\n\t\t\tlet store: StateStore;\n\t\t\tif (props.store) {\n\t\t\t\tstore = props.store;\n\t\t\t} else {\n\t\t\t\tif (internalStore === null || lastView !== view.value) {\n\t\t\t\t\t// T-37-04-02: Proper proto-safe guard for spec.state\n\t\t\t\t\tinternalStore = xstateStoreStateStore({\n\t\t\t\t\t\tatom: createAtom(toAtomState(spec.state)),\n\t\t\t\t\t});\n\t\t\t\t\tlastView = view.value;\n\t\t\t\t\tstoreKey++;\n\t\t\t\t}\n\t\t\t\tstore = internalStore;\n\t\t\t}\n\n\t\t\t// ActorProviderInner renders inside StateProvider so useStateStore() works\n\t\t\treturn h(StateProvider, { store, key: storeKey }, () =>\n\t\t\t\th(ActorProviderInner, { registryResult: rawRegistryResult, spec, store }, slots),\n\t\t\t);\n\t\t};\n\t},\n});\n</script>\n"],"mappings":""}
@@ -0,0 +1,51 @@
1
+ import { PropType } from 'vue';
2
+ import { DefineRegistryResult, RenderErrorHandler } from '@xmachines/json-render-vue';
3
+ import { StateStore } from '@xmachines/json-render-core';
4
+ import { ActorProviderProps } from './types.js';
5
+ export type { ActorProviderProps } from './types.js';
6
+ export { getPlayViewContext } from './actor-provider-context.js';
7
+ export type { ViewContextValue } from './actor-provider-context.js';
8
+ declare const _default: import('@vue/runtime-core').DefineComponent<import('@vue/runtime-core').ExtractPropTypes<{
9
+ actor: {
10
+ type: PropType<ActorProviderProps["actor"]>;
11
+ required: true;
12
+ };
13
+ registryResult: {
14
+ type: PropType<DefineRegistryResult>;
15
+ required: true;
16
+ };
17
+ store: {
18
+ type: PropType<StateStore>;
19
+ default: undefined;
20
+ };
21
+ onRenderError: {
22
+ type: PropType<RenderErrorHandler>;
23
+ default: undefined;
24
+ };
25
+ }>, () => import('@vue/runtime-core').VNode<import('@vue/runtime-core').RendererNode, import('@vue/runtime-core').RendererElement, {
26
+ [key: string]: any;
27
+ }> | import('@vue/runtime-core').VNode<import('@vue/runtime-core').RendererNode, import('@vue/runtime-core').RendererElement, {
28
+ [key: string]: any;
29
+ }>[] | null, {}, {}, {}, import('@vue/runtime-core').ComponentOptionsMixin, import('@vue/runtime-core').ComponentOptionsMixin, {}, string, import('@vue/runtime-core').PublicProps, Readonly<import('@vue/runtime-core').ExtractPropTypes<{
30
+ actor: {
31
+ type: PropType<ActorProviderProps["actor"]>;
32
+ required: true;
33
+ };
34
+ registryResult: {
35
+ type: PropType<DefineRegistryResult>;
36
+ required: true;
37
+ };
38
+ store: {
39
+ type: PropType<StateStore>;
40
+ default: undefined;
41
+ };
42
+ onRenderError: {
43
+ type: PropType<RenderErrorHandler>;
44
+ default: undefined;
45
+ };
46
+ }>> & Readonly<{}>, {
47
+ onRenderError: RenderErrorHandler;
48
+ store: StateStore;
49
+ }, {}, {}, {}, string, import('@vue/runtime-core').ComponentProvideOptions, true, {}, any>;
50
+ export default _default;
51
+ //# sourceMappingURL=ActorProvider.vue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ActorProvider.vue.d.ts","sourceRoot":"","sources":["../src/ActorProvider.vue"],"names":[],"mappings":"AAkXA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAKpC,OAAO,KAAK,EACX,oBAAoB,EAEpB,kBAAkB,EAClB,MAAM,4BAA4B,CAAC;AAQpC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAK9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;;;cA8FjD,QAAQ,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;;;;cAIrC,QAAQ,CAAC,oBAAoB,CAAC;;;;cAI9B,QAAQ,CAAC,UAAU,CAAC;;;;cAIlB,QAAQ,CAAC,kBAAkB,CAAC;;;;;;;;;cAZ9B,QAAQ,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;;;;cAIrC,QAAQ,CAAC,oBAAoB,CAAC;;;;cAI9B,QAAQ,CAAC,UAAU,CAAC;;;;cAIlB,QAAQ,CAAC,kBAAkB,CAAC;;;;;;;AAhBjD,wBA0LG"}