@xmachines/play-vue 2.2.0 → 3.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.
- package/README.md +34 -12
- package/dist/ActorProvider.js.map +1 -1
- package/dist/ActorProvider.vue_vue_type_script_lang.js +105 -3
- package/dist/ActorProvider.vue_vue_type_script_lang.js.map +1 -1
- package/dist/PlayUIProvider.js.map +1 -1
- package/dist/PlayUIProvider.vue_vue_type_script_lang.js +6 -1
- package/dist/PlayUIProvider.vue_vue_type_script_lang.js.map +1 -1
- package/dist/actor-provider-context.d.ts +5 -0
- package/dist/actor-provider-context.d.ts.map +1 -1
- package/dist/actor-provider-context.js +5 -0
- package/dist/actor-provider-context.js.map +1 -1
- package/dist/error-source.d.ts +85 -0
- package/dist/error-source.d.ts.map +1 -0
- package/dist/error-source.js +132 -0
- package/dist/error-source.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Vue 3 renderer for the XMachines Play Architecture. It observes the actor signals and renders the UI through `@xmachines/json-render-vue`.
|
|
4
4
|
|
|
5
|
-
[](https://opensource.org/licenses/MIT) [](https://opensource.org/licenses/MIT) [](https://www.npmjs.com/package/@xmachines/play-vue)
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -27,7 +27,7 @@ pnpm add @xmachines/play-vue
|
|
|
27
27
|
**Peer dependencies.** Install them with the package:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
pnpm add vue@^3.5.0 xstate@^5.31.0 @xstate/store@^3.17.0 @xmachines/json-render-vue@^0.20.0-xm.
|
|
30
|
+
pnpm add vue@^3.5.0 xstate@^5.31.0 @xstate/store@^3.17.0 @xmachines/json-render-vue@^0.20.0-xm.4 @xmachines/json-render-core@^0.20.0-xm.4 @xmachines/json-render-xstate@^0.20.0-xm.4
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
---
|
|
@@ -77,17 +77,32 @@ const registryResult = defineRegistry(myCatalog, {
|
|
|
77
77
|
|
|
78
78
|
The composite provider. It wraps `<ActorProvider>` and `JSONUIProvider` in one component. **Use it in most applications.**
|
|
79
79
|
|
|
80
|
-
| Prop | Type
|
|
81
|
-
| --------------------- |
|
|
82
|
-
| `actor` | `AbstractActor & Viewable`
|
|
83
|
-
| `registryResult` | `DefineRegistryResult`
|
|
84
|
-
| `store` | `StateStore`
|
|
85
|
-
| `onRenderError` | `RenderErrorHandler`
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
80
|
+
| Prop | Type | Required | Description |
|
|
81
|
+
| --------------------- | --------------------------------------------- | -------- | ---------------------------------------------------------------------- |
|
|
82
|
+
| `actor` | `AbstractActor & Viewable` | ✅ | The XMachines actor instance |
|
|
83
|
+
| `registryResult` | `DefineRegistryResult` | ✅ | Result of `defineRegistry()` |
|
|
84
|
+
| `store` | `StateStore` | — | External controlled state store (optional) |
|
|
85
|
+
| `onRenderError` | `RenderErrorHandler` | — | Error handler for render failures |
|
|
86
|
+
| `onError` | `(error: unknown, reset: () => void) => void` | — | Error handler of the outer boundary; the second parameter is the reset |
|
|
87
|
+
| `navigate` | `(path: string) => void` | — | Link navigation function |
|
|
88
|
+
| `validationFunctions` | `Record<string, Function>` | — | Custom validation functions |
|
|
89
|
+
| `functions` | `Record<string, Function>` | — | Named functions for `$computed` expressions |
|
|
89
90
|
|
|
90
|
-
**Slots:** `default` (the rendered content), `fallback` (the content while the actor view is `null
|
|
91
|
+
**Slots:** `default` (the rendered content), `fallback` (the content while the actor view is `null`, and while the boundary holds an error)
|
|
92
|
+
|
|
93
|
+
The provider contains an error of the **render** of a descendant — the render function, the setup function, and a lifecycle hook — so that a subtree that throws cannot take the whole application down. It calls `onError`, and it renders the `fallback` slot instead of the children. The next view emission clears the error, so a view transition retries the render. A throw of a catalog component reaches the per-element boundary of `@xmachines/json-render-vue` first, and `onRenderError` reports it; this boundary is the outer net for everything else.
|
|
94
|
+
|
|
95
|
+
The boundary stops a render error, so `app.config.errorHandler` sees it no longer. Without an `onError` prop the provider therefore writes the error to `console.error`.
|
|
96
|
+
|
|
97
|
+
The second parameter is the **reset**, for a retry that the host starts. It renders the view that the actor holds at the moment of the call, so a retry cannot rewind the screen to the view that failed. A reset that the host calls from inside the handler does nothing, because no input changed between the two attempts, and a reset after the provider goes away does nothing. The five renderers hold the same three rules.
|
|
98
|
+
|
|
99
|
+
**A handler that throws reaches no caller.** The provider contains that throw, and it writes the throw to `console.error`. The errored flag schedules a render, so a throw that left the handler would stop that render and skip the `return false` of `onErrorCaptured`: the subtree that threw stays on the screen and the error reaches `app.config.errorHandler` as well. Report a failure from `onError`. To escalate one, raise it from a task of your own — `queueMicrotask(() => { throw error; })` — which reaches the global handler of the page and leaves the containment whole. The five renderers hold this one rule.
|
|
100
|
+
|
|
101
|
+
`onErrorCaptured` of Vue also fires for an error of an **event handler** and of a **watcher callback**, which the boundaries of React, Solid, and Svelte each see never. The provider leaves such an error alone: the view stays on the screen, `onError` stays silent, and the error continues to `app.config.errorHandler` exactly as it does without this provider. A click handler that throws therefore does not replace your view with the fallback, and it reaches your global handler one time. `onError` reports a failure of the render, in every renderer.
|
|
102
|
+
|
|
103
|
+
Vue reports such an error a **second** time where the application wires no `app.config.errorHandler`: it re-throws the error, and a queued job carries it back as a failure of a render. The provider recognises that repeat and holds the view. It writes the error value to `console.error` with the name of this package, because Vue writes that value nowhere on this path, and `onError` stays silent. A development build re-throws every time, and a production build only where the application sets `app.config.throwUnhandledErrorInProduction`.
|
|
104
|
+
|
|
105
|
+
**One case escapes that rule.** An ancestor `errorCaptured` hook that returns `false` stops Vue before the re-throw, so the record of the provider waits for a repeat that never comes. A second failure in the same flush can take that record. It must carry the same error value, and it must unwind out of the update job of a parent. Vue reports such a failure as a component update. The provider then holds the stale subtree on the screen, and `onError` stays silent.
|
|
91
106
|
|
|
92
107
|
#### `<PlayRenderer>`
|
|
93
108
|
|
|
@@ -167,6 +182,13 @@ const view = usePlayView();
|
|
|
167
182
|
// view.spec, view.handlers, view.registry, view.store
|
|
168
183
|
```
|
|
169
184
|
|
|
185
|
+
> **Read a field where you use it, and destructure it not.** The value is a live view of
|
|
186
|
+
> the provider, so `const { spec } = usePlayView()` freezes at the first read and the
|
|
187
|
+
> screen stops following the actor — with no error and no warning. Write `view.spec` at
|
|
188
|
+
> the place that needs it.
|
|
189
|
+
|
|
190
|
+
`usePlayView()` returns a **Proxy**: every property read goes to the live context of the provider.
|
|
191
|
+
|
|
170
192
|
> **Note:** `usePlayView` was previously named `getPlayViewContext`. The old name is still exported as a deprecated alias and will be removed in the next major.
|
|
171
193
|
|
|
172
194
|
---
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActorProvider.js","names":[],"sources":["../src/ActorProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * ActorProvider — the low-level provider of the XMachines Vue render architecture.\n *\n * It owns the complete actor lifecycle:\n * - the signal subscription (watchSignal), which connects the TC39 Signals to the Vue reactivity\n * - the StateStore lifecycle of each view, controlled and uncontrolled\n * - the resolution of the handlers, through ActorProviderInner, which must be inside StateProvider\n * - the ViewContextValue, which it provides through the ViewKey injection key\n * - the ActionProvider wrapper and the VisibilityProvider wrapper, for the Renderer below\n * - the injection of onRenderError into the registry\n *\n * The author of a library who needs this control uses the component directly.\n * An end user takes <PlayUIProvider> instead.\n *\n * @invariant Actor Authority - the Actor decides each state transition, with its guards\n * @invariant Passive Infrastructure - the component observes the signals, and it sends the events\n * @invariant Signal-Only Reactivity - the state of the business logic is in the 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 { attachRenderErrorHandler, createViewStoreLifecycle } 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 the props type and the context accessors, for a consumer that imports from this file\nexport type { ActorProviderProps } from \"./types.js\";\nexport { usePlayView, getPlayViewContext } from \"./actor-provider-context.js\"; // getPlayViewContext @deprecated\nexport type { ViewContextValue } from \"./actor-provider-context.js\";\n\n// ---------------------------------------------------------------------------\n// ActorProviderInner — it renders inside StateProvider, so that it can call useStateStore()\n// It gives the ViewContextValue, the ActionProvider, and the VisibilityProvider to the Renderer below.\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// The code uses a shallowRef for the value of the view context, because a deep\n\t\t// reactivity costs too much. The Proxy below lets inject() always read the newest\n\t\t// value.\n\t\tconst viewRef = shallowRef<ViewContextValue | null>(null);\n\n\t\t// Give the ViewContextValue through the inject and provide system of Vue.\n\t\t// The code calls provide synchronously in setup(), so that Vue registers the value on\n\t\t// the instance of the component. It uses a Proxy, so that each descendant always\n\t\t// receives the newest 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() during setup(), which is synchronous and before the return.\n\t\t// A Vue composable that uses inject() must run during setup(), and not in a render\n\t\t// function.\n\t\tconst stateStore = useStateStore();\n\n\t\t// Build a SetState adapter, because the factory of the handlers expects the pattern\n\t\t// of an updater function. The code defines it one time, in setup(). It reads\n\t\t// stateStore.getSnapshot and stateStore.update at the moment of the call. Therefore\n\t\t// it always works on the live state of the store, and it needs no new definition when\n\t\t// the identity of stateStore changes between two 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// Keep the handlers, with the identity of the registryResult prop as the key. The\n\t\t// getter functions are stable closures, and they hold the stateStore of setup().\n\t\t// Therefore they cause no new computation. The code computes the handlers again only\n\t\t// after a change of the definition of the registry, and not on each render of a view\n\t\t// update.\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 the children with an ActionProvider and a VisibilityProvider. The Renderer of\n\t\t\t// PlayRenderer therefore works also with ActorProvider alone, without PlayUIProvider\n\t\t\t// and without 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 — the 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// Take the actor out of the reactive proxy of Vue, to reach the raw Signal objects.\n\t\t// A shallowRef holds the actor. Therefore the injected value and the signal watcher\n\t\t// can follow `props.actor` when the prop changes after the mount.\n\t\tconst actorRef = shallowRef(toRaw(props.actor as AbstractActor<AnyActorLogic> & Viewable));\n\n\t\t// Take the registryResult out of the proxy, and mark each component as raw, because the reactivity of Vue costs too much here\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// Put the onRenderError prop into the registry. The property is not enumerable, and\n\t\t// it replaces the handler of the defineRegistry level. attachRenderErrorHandler makes\n\t\t// a copy, and it changes the registry of the caller never.\n\t\t// The code must call markRaw on the copy again, because a copy does not carry the\n\t\t// non-enumerable flag of markRaw.\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// Give the actor to each descendant, through the provide and inject mechanism of Vue.\n\t\t//\n\t\t// The code gives a stable Proxy, and not the raw actor. The pattern is the same as\n\t\t// the ViewKey proxy of ActorProviderInner. The injected value therefore follows the\n\t\t// CURRENT `props.actor`: each injector keeps the reference of the setup(), and every\n\t\t// trap resolves against the newest actor (actorRef.value) after a change of the prop.\n\t\t// The proxy forwards each relevant trap. Therefore it behaves like the actor below it\n\t\t// for a read, for a write, for a membership test (`in`), for an enumeration\n\t\t// (Object.keys and a spread), and for a prototype test (`instanceof`). Each method\n\t\t// binds to the current raw actor. Therefore `this`, and also each private field,\n\t\t// works exactly as in a direct call.\n\t\t//\n\t\t// IMPORTANT: this proxy is NOT identity-equal to `props.actor`, because it is a\n\t\t// separate object, and therefore a separate key of a WeakMap. A consumer that uses\n\t\t// the identity as its key must receive the `props.actor` value itself, and never this\n\t\t// injected proxy. The most important such consumer is the guard of one bridge for\n\t\t// each actor in RouterBridgeBase: it uses the identity of the actor as the key of a\n\t\t// WeakMap at the module level.\n\t\t//\n\t\t// The identity of a method: the code keeps each bound method, with the method below\n\t\t// it as the key. A repeated read therefore returns the SAME function\n\t\t// (actor.send === actor.send), and the hot path allocates nothing. A read binds the\n\t\t// method again only after a change of the method below it. The watch handler below\n\t\t// CLEARS the cache on a change of the actor. Therefore each prototype method binds to\n\t\t// 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// An invariant of a Proxy: the target of this proxy is an empty {}. Therefore each\n\t\t\t\t\t// key of ownKeys must resolve to a configurable descriptor here. Without that, the\n\t\t\t\t\t// [[GetOwnProperty]] invariant throws, because a proxy must not report a property that\n\t\t\t\t\t// its target has not as non-configurable. The own props of the actor are mutable at\n\t\t\t\t\t// run time in each case. Therefore `configurable: true` is correct enough for the\n\t\t\t\t\t// enumeration of Object.keys and of a spread.\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 the first value, then subscribe. Both steps are synchronous, and no scheduler\n\t\t// runs between them.\n\t\t// Solid does the same with createEffect, and Svelte does the same with $effect.\n\t\t// The pattern is therefore the same in each framework.\n\t\tconst view = ref<PlaySpec | null>(actorRef.value.currentView.get());\n\n\t\t// The store lifecycle: it seeds the store again on a change of the viewKey, it\n\t\t// refreshes /context in place in every other case, it resets the store on a change of\n\t\t// the actor, and it guards the identity cache. The shared coordinator comes from\n\t\t// @xmachines/play-actor. Only the wiring of the reactivity below belongs to Vue.\n\t\tconst storeLifecycle = createViewStoreLifecycle((seed) =>\n\t\t\txstateStoreStateStore({ atom: createAtom(seed) }),\n\t\t);\n\t\t// storeKey is the key of a SUBTREE REMOUNT. Therefore it must change only after a\n\t\t// new seed of the store, and never after a refresh of /context in place. Without this\n\t\t// rule, each update of the projection removes the subtree on the screen.\n\t\t// (The controlled mode seeds the store never again. Therefore the key changes never\n\t\t// there.)\n\t\tlet storeKey = 0;\n\n\t\t// The signal watcher connects the TC39 Signals to the reactivity of Vue.\n\t\t// The code assigns it again on a change of the actor. Therefore the cleanup always\n\t\t// 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 a change of `props.actor`: cancel the subscription to the currentView of\n\t\t// the OLD actor, seed the view from the NEW actor synchronously (`flush: \"sync\"`, so\n\t\t// that no scheduler runs between the steps, as in the seed and watch pattern of the\n\t\t// mount above), and subscribe to the new signal. The view of the new seed has a new\n\t\t// identity. Therefore the branch of the internal store below builds the store again,\n\t\t// and it changes storeKey, exactly like a normal 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// (The store lifecycle resets itself on a change of the actor, because a store that\n\t\t\t\t// stays alive must not outlive its actor.)\n\t\t\t\t// Clear the cache of the bound methods. Each prototype method therefore binds to the\n\t\t\t\t// new actor on the next read. See the get trap of the injected proxy.\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 the fallback slot, or show 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, which is external and controlled, or internal for each\n\t\t\t// viewKey, through the shared lifecycle. The children receive the store with the\n\t\t\t// guard, because /context is read-only to the spec. The machinery refreshes the\n\t\t\t// store through the reference without a guard, inside the coordinator.\n\t\t\tconst resolution = storeLifecycle.resolve(actorRef.value, spec, props.store);\n\t\t\tif (resolution.reseeded) storeKey++;\n\t\t\tconst store: StateStore = resolution.guardedStore;\n\n\t\t\t// ActorProviderInner renders inside StateProvider. Therefore 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":""}
|
|
1
|
+
{"version":3,"file":"ActorProvider.js","names":[],"sources":["../src/ActorProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * ActorProvider — the low-level provider of the XMachines Vue render architecture.\n *\n * It owns the complete actor lifecycle:\n * - the signal subscription (watchSignal), which connects the TC39 Signals to the Vue reactivity\n * - the StateStore lifecycle of each view, controlled and uncontrolled\n * - the resolution of the handlers, through ActorProviderInner, which must be inside StateProvider\n * - the ViewContextValue, which it provides through the ViewKey injection key\n * - the ActionProvider wrapper and the VisibilityProvider wrapper, for the Renderer below\n * - the injection of onRenderError into the registry\n *\n * The author of a library who needs this control uses the component directly.\n * An end user takes <PlayUIProvider> instead.\n *\n * @invariant Actor Authority - the Actor decides each state transition, with its guards\n * @invariant Passive Infrastructure - the component observes the signals, and it sends the events\n * @invariant Signal-Only Reactivity - the state of the business logic is in the actor signals\n */\n\nimport {\n\tdefineComponent,\n\tref,\n\tcomputed,\n\ttoRaw,\n\tmarkRaw,\n\tonUnmounted,\n\tonErrorCaptured,\n\th,\n\tprovide,\n\tshallowRef,\n\twatch,\n\tgetCurrentInstance,\n} from \"vue\";\nimport type { ComponentPublicInstance, PropType } from \"vue\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport { canRaiseAgain, failsTheRender, isComponentUpdate } from \"./error-source.js\";\nimport {\n\tattachRenderErrorHandler,\n\tcreateFailureLatch,\n\tcreateReportGuard,\n\tcreateViewStoreLifecycle,\n} 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 the props type and the context accessors, for a consumer that imports from this file\nexport type { ActorProviderProps } from \"./types.js\";\nexport { usePlayView, getPlayViewContext } from \"./actor-provider-context.js\"; // getPlayViewContext @deprecated\nexport type { ViewContextValue } from \"./actor-provider-context.js\";\n\n// ---------------------------------------------------------------------------\n// ActorProviderInner — it renders inside StateProvider, so that it can call useStateStore()\n// It gives the ViewContextValue, the ActionProvider, and the VisibilityProvider to the Renderer below.\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// The code uses a shallowRef for the value of the view context, because a deep\n\t\t// reactivity costs too much. The Proxy below lets inject() always read the newest\n\t\t// value.\n\t\tconst viewRef = shallowRef<ViewContextValue | null>(null);\n\n\t\t// Give the ViewContextValue through the inject and provide system of Vue.\n\t\t// The code calls provide synchronously in setup(), so that Vue registers the value on\n\t\t// the instance of the component. It uses a Proxy, so that each descendant always\n\t\t// receives the newest 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() during setup(), which is synchronous and before the return.\n\t\t// A Vue composable that uses inject() must run during setup(), and not in a render\n\t\t// function.\n\t\tconst stateStore = useStateStore();\n\n\t\t// Build a SetState adapter, because the factory of the handlers expects the pattern\n\t\t// of an updater function. The code defines it one time, in setup(). It reads\n\t\t// stateStore.getSnapshot and stateStore.update at the moment of the call. Therefore\n\t\t// it always works on the live state of the store, and it needs no new definition when\n\t\t// the identity of stateStore changes between two 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// Keep the handlers, with the identity of the registryResult prop as the key. The\n\t\t// getter functions are stable closures, and they hold the stateStore of setup().\n\t\t// Therefore they cause no new computation. The code computes the handlers again only\n\t\t// after a change of the definition of the registry, and not on each render of a view\n\t\t// update.\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 the children with an ActionProvider and a VisibilityProvider. The Renderer of\n\t\t\t// PlayRenderer therefore works also with ActorProvider alone, without PlayUIProvider\n\t\t\t// and without 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 — the 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\tonError: {\n\t\t\ttype: Function as PropType<(error: unknown, reset: () => void) => void>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// The error boundary. It contains an error of the RENDER of a descendant — the\n\t\t// render function, the setup function, and a lifecycle hook — so that a subtree\n\t\t// that throws cannot take the whole application down. `onErrorCaptured` notifies\n\t\t// `onError`, it sets the errored flag, and the render function below then returns\n\t\t// the `fallback` slot instead of the children.\n\t\t//\n\t\t// `onErrorCaptured` of Vue also fires for an error of an event handler, and of a\n\t\t// watcher callback, which no other renderer of this workspace sees: the boundary\n\t\t// of React, the ErrorBoundary of Solid, and `<svelte:boundary>` each catch a\n\t\t// failure of the render only. A click handler that throws must therefore NOT\n\t\t// replace the view with the fallback. This boundary leaves such an error\n\t\t// COMPLETELY alone: it calls `onError` never, and it returns undefined, so the\n\t\t// error continues to `app.config.errorHandler` exactly as it does without this\n\t\t// provider. A report here would give play-vue a wider `onError` contract than the\n\t\t// other four renderers, and it would reach a caller that wires both this prop and\n\t\t// the global handler two times for one click.\n\t\t//\n\t\t// RESET RULE: the next view emission clears the flag (see the callbacks of\n\t\t// watchSignal and the handler of the actor swap). A view transition therefore\n\t\t// retries the render. Without the rule the user sends a recovery event, the actor\n\t\t// moves to a healthy view, and the screen keeps the fallback.\n\t\t//\n\t\t// The per-element boundary of @json-render catches a throw of a catalog component\n\t\t// upstream, and reports it through `onRenderError`. This boundary is the outer net\n\t\t// for everything else.\n\t\t//\n\t\t// The return of false stops a render error HERE. Without `onError` that error\n\t\t// therefore reaches neither `app.config.errorHandler` nor the console, and a\n\t\t// consumer that gives no handler loses it in silence. Write it to `console.error`\n\t\t// in that case, in the same way as the inner renderer does for a component that\n\t\t// throws without an `onRenderError` handler.\n\t\tconst errored = ref(false);\n\t\t/**\n\t\t * The report-once rule, from @xmachines/play-actor.\n\t\t *\n\t\t * It holds the actor, the view and the store of the failure that it reported. A\n\t\t * latch that compared the identity of the ERROR held nothing: an accessor that\n\t\t * throws builds a new one at each read, which is the ordinary shape of a failing\n\t\t * projection. play-svelte and play-solid use the same latch.\n\t\t */\n\t\tconst failureLatch = createFailureLatch();\n\n\t\t// The report guard, from @xmachines/play-actor: it blocks a reset that the host\n\t\t// calls from INSIDE the report, and every callback once the provider is gone. The\n\t\t// five renderers share the one implementation.\n\t\t//\n\t\t// The re-entry rule matters more here than a stack suggests: a write of a ref\n\t\t// renders on the NEXT tick, so a host with a retry-once policy loops with no stack\n\t\t// at all, and a flag around the retry itself would catch nothing.\n\t\t//\n\t\t// It CONTAINS a handler of the host that throws. The flag below schedules a\n\t\t// RENDER, and a throw that leaves the hook stops that render and skips the\n\t\t// `return false`, so the subtree that threw stays on the screen AND the error\n\t\t// reaches `app.config.errorHandler`.\n\t\tconst guard = createReportGuard({\n\t\t\tnoHandler:\n\t\t\t\t\"[@xmachines/play-vue] ActorProvider contained an error of a descendant. \" +\n\t\t\t\t\"Give an onError prop to receive it.\",\n\t\t\thandlerThrew:\n\t\t\t\t\"[@xmachines/play-vue] the onError handler of the host threw. \" +\n\t\t\t\t\"ActorProvider contained the failure of the view all the same.\",\n\t\t});\n\t\t// The retry renders again through a nonce that the render function reads. Clearing\n\t\t// the flags alone renders nothing: `errored.value = false` writes the value that\n\t\t// the ref holds already on the path of a failed RESOLUTION, and Vue schedules\n\t\t// nothing for a write that changes no value.\n\t\tconst retryNonce = ref(0);\n\n\t\t/**\n\t\t * Clears the contained error and renders again.\n\t\t *\n\t\t * The render function reads `view.value`, so the retry resolves the view that the\n\t\t * actor holds NOW. A retry bound to the view that failed would take a host that\n\t\t * keeps the callback back onto the screen that threw.\n\t\t */\n\t\tconst retry = (): void => {\n\t\t\tif (guard.blocked()) return;\n\t\t\terrored.value = false;\n\t\t\tfailureLatch.clear();\n\t\t\tretryNonce.value++;\n\t\t};\n\n\t\t/** Sends a contained failure to `onError`, or to the console when none is given. */\n\t\tconst report = (err: unknown): void => {\n\t\t\tconst handler = props.onError;\n\t\t\tguard.report(err, handler ? () => handler(err, retry) : undefined);\n\t\t};\n\n\t\t// The configuration of the application, read one time. `canRaiseAgain` asks the two\n\t\t// fields that `handleError` of Vue reads on every error. It lives in error-source.ts,\n\t\t// because its production half answers a spelling that a mounted test reaches never.\n\t\tconst appConfig = getCurrentInstance()?.appContext.config;\n\n\t\t// The error that this boundary let through LAST, and that the NEXT raise can still\n\t\t// claim as a repeat.\n\t\t//\n\t\t// The second raise is SYNCHRONOUS and IMMEDIATE. `logError` of the development\n\t\t// bundle re-throws, and the throw unwinds straight back into `callWithErrorHandling`\n\t\t// of `flushJobs`, which reports the same value again. No other raise reaches this\n\t\t// hook in between, so ONE slot holds the complete window.\n\t\t//\n\t\t// EVERY raise that fails the render takes the slot, and not a component update\n\t\t// alone. A record that no raise takes can otherwise outlive its window.\n\t\t//\n\t\t// The record names the INSTANCE beside the error value. A repeat carries the\n\t\t// instance whose JOB ran when the error unwound, and that is the owner of the\n\t\t// watcher OR one of its ANCESTORS: a pre-flush watcher of a child runs inside the\n\t\t// update of the parent that passes it a prop, and `flushJobs` reports the throw\n\t\t// under the parent — or under the outermost ancestor whose update ran. The take\n\t\t// therefore walks the `$parent` chain of the recorded instance. A SIBLING stands on\n\t\t// that chain never, and a sibling that fails with the same cached error is one\n\t\t// loader with two consumers: that failure is a first incident, and the boundary\n\t\t// contains it.\n\t\t//\n\t\t// The microtask is the BACKSTOP, for a raise that neither a repeat nor another\n\t\t// raise follows. A record for the life of the provider would disarm the boundary\n\t\t// for that error for ever, and a descendant that keeps a failure and raises it\n\t\t// again from a LATER render is an ordinary shape.\n\t\t//\n\t\t// Each `remember` schedules a clear of ITS OWN record, and that clear takes no\n\t\t// other. A clear that removed whatever the slot holds could run after a take\n\t\t// emptied the slot and a later `remember` filled it again, and it would then\n\t\t// disarm the boundary for a repeat that is still to come.\n\t\tlet letThrough: { error: unknown; instance: ComponentPublicInstance | null } | null = null;\n\t\tconst remember = (error: unknown, instance: ComponentPublicInstance | null): void => {\n\t\t\tconst record = { error, instance };\n\t\t\tletThrough = record;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (letThrough === record) letThrough = null;\n\t\t\t});\n\t\t};\n\t\t/**\n\t\t * Takes the record. It answers TRUE when the record names this error, and when the\n\t\t * instance that raises it is the recorded one or one of its ancestors.\n\t\t *\n\t\t * The comparison reads `$` on both sides, which is the INTERNAL instance. Vue hands\n\t\t * `onErrorCaptured` the `proxy` of a component, and `$parent` answers with the\n\t\t * `exposeProxy` of a parent that called `expose()` — which the compiler makes every\n\t\t * `<script setup>` component do. The two objects differ, so a walk that compared\n\t\t * them found an exposed ancestor never, and the boundary contained a watcher error\n\t\t * that it must let through. `$` resolves through both proxies and names one object.\n\t\t */\n\t\tconst takeRepeat = (error: unknown, instance: ComponentPublicInstance | null): boolean => {\n\t\t\tconst record = letThrough;\n\t\t\tletThrough = null;\n\t\t\tif (record === null || record.error !== error) return false;\n\t\t\tfor (let node = record.instance; node !== null; node = node.$parent) {\n\t\t\t\tif (node.$ === instance?.$) return true;\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t\tonErrorCaptured((err, instance, info) => {\n\t\t\t// An error that did NOT fail the render leaves this boundary untouched: the\n\t\t\t// screen keeps its view, `onError` stays silent, and the return of undefined\n\t\t\t// sends the error on to `app.config.errorHandler`. The boundary of React, the\n\t\t\t// ErrorBoundary of Solid, and `<svelte:boundary>` each see such an error never,\n\t\t\t// so a call of `onError` here would give play-vue a wider contract than the\n\t\t\t// other four, AND it would report the same failure twice to a caller that wires\n\t\t\t// both `onError` and the global handler of the application.\n\t\t\tif (!failsTheRender(info)) {\n\t\t\t\t// Arm the record only where Vue WILL raise this error a second time. A record\n\t\t\t\t// that no repeat claims stands until the microtask, and a genuine failure of\n\t\t\t\t// a render in the same flush — the same descendant, the same cached error —\n\t\t\t\t// would then take it: no fallback, no `onError`, and no screen.\n\t\t\t\t//\n\t\t\t\t// `handleError` of Vue calls `app.config.errorHandler` where one exists, and\n\t\t\t\t// it reaches `logError` never; the production bundle throws only where the\n\t\t\t\t// application asks for `throwUnhandledErrorInProduction`. The one case that\n\t\t\t\t// this line cannot see is an ANCESTOR `errorCaptured` that answers false,\n\t\t\t\t// which stops Vue before `logError` too. That case stays open, and the doc of\n\t\t\t\t// `onError` names it.\n\t\t\t\tif (canRaiseAgain(info, appConfig)) remember(err, instance);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Vue can raise the SAME error a SECOND time under another source. The\n\t\t\t// development bundle of `logError` re-throws an error that no\n\t\t\t// `app.config.errorHandler` took. A queued job then loses that throw to\n\t\t\t// `callWithErrorHandling(job, job.i, 15)` of `flushJobs` — a `watch` callback\n\t\t\t// with the default `pre` flush, for one — and `flushJobs` reports it again as\n\t\t\t// \"component update\". The classification above answers \"render failure\" for that\n\t\t\t// second spelling, so a watcher that threw would take the view off the screen\n\t\t\t// after all. One error is one incident: an error that this boundary let through\n\t\t\t// in this turn stays through.\n\t\t\t//\n\t\t\t// The gate asks for the SOURCE of the repeat as well. `flushJobs` reports a\n\t\t\t// job under \"component update\", and no other source can carry a second raise\n\t\t\t// to this hook, so a record of the turn can disarm the boundary for that one\n\t\t\t// spelling alone. Without the question a setup function or a render function\n\t\t\t// that throws the SAME error value in the same turn — a loader that caches its\n\t\t\t// rejection, for one — would reach no fallback, no `onError`, and no screen.\n\t\t\t//\n\t\t\t// It returns FALSE, and not undefined. The FIRST raise went on to\n\t\t\t// `app.config.errorHandler`, which is the contract of a non-render source, and\n\t\t\t// the development bundle re-threw it there — that throw IS this second report.\n\t\t\t// A second `undefined` sends it on one more time, so `logError` warns twice for\n\t\t\t// one incident and throws again, and that throw leaves `flushJobs`. The\n\t\t\t// `finally` of `flushJobs` runs `queue.length = 0`. `flushJobs` therefore drops\n\t\t\t// every job behind the one that failed, and a sibling of the descendant that\n\t\t\t// threw renders never. One incident reaches the caller ONE time, and the flush\n\t\t\t// of Vue finishes.\n\t\t\t// `takeRepeat` runs for EVERY raise that fails the render, and not inside the\n\t\t\t// condition below: a raise of another source ENDS the window of the repeat, so\n\t\t\t// the record can disarm the boundary for the one raise that follows it and for\n\t\t\t// no other.\n\t\t\tconst repeated = takeRepeat(err, instance);\n\t\t\tif (isComponentUpdate(info) && repeated) {\n\t\t\t\t// The value reaches no console without this line. `logError` of the\n\t\t\t\t// development bundle writes the SOURCE, and not the value. It then throws\n\t\t\t\t// the value, and that throw IS this second report, which the `return`\n\t\t\t\t// below stops. `report()` wrote such an error before this gate existed, so\n\t\t\t\t// a host with no `onError` prop could still debug a watcher or a loader\n\t\t\t\t// that failed. The production bundle writes the value on the FIRST raise,\n\t\t\t\t// and it throws instead where the application asks for\n\t\t\t\t// `throwUnhandledErrorInProduction` — that application gets the write here.\n\t\t\t\t// One incident reaches the console ONE time in both bundles.\n\t\t\t\t//\n\t\t\t\t// The line carries the name of this package, as every other console write\n\t\t\t\t// of this provider does. Without it a developer reads a bare error and\n\t\t\t\t// cannot tell that the provider wrote it, and not Vue.\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[@xmachines/play-vue] ActorProvider stopped a second report of this \" +\n\t\t\t\t\t\t\"error. Vue raised it again under the source of a component update, \" +\n\t\t\t\t\t\t\"and it failed no render.\",\n\t\t\t\t\terr,\n\t\t\t\t);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// The SCREEN comes first, and the report second, as `contain()` of play-dom\n\t\t\t// states it. A reporter can throw — one that re-throws to escalate, or a\n\t\t\t// transport that fails — and here the order alone is not enough: the flag\n\t\t\t// schedules a RENDER, and a throw that leaves this hook stops that render and\n\t\t\t// skips the `return false`. The GUARD holds that half, for the four renderers\n\t\t\t// that need it: it carries a `handlerThrew` message, so it contains the throw\n\t\t\t// of the handler and writes it to `console.error`.\n\t\t\t//\n\t\t\t// The five renderers hold the one rule: a handler of the host never defeats the\n\t\t\t// containment, and a host that must escalate raises the failure from a task of\n\t\t\t// its own.\n\t\t\terrored.value = true;\n\t\t\treport(err);\n\t\t\treturn false;\n\t\t});\n\n\t\t// Take the actor out of the reactive proxy of Vue, to reach the raw Signal objects.\n\t\t// A shallowRef holds the actor. Therefore the injected value and the signal watcher\n\t\t// can follow `props.actor` when the prop changes after the mount.\n\t\tconst actorRef = shallowRef(toRaw(props.actor as AbstractActor<AnyActorLogic> & Viewable));\n\n\t\t// Take the registryResult out of the proxy, and mark each component as raw, because the reactivity of Vue costs too much here\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// Put the onRenderError prop into the registry. The property is not enumerable, and\n\t\t// it replaces the handler of the defineRegistry level. attachRenderErrorHandler makes\n\t\t// a copy, and it changes the registry of the caller never.\n\t\t// The code must call markRaw on the copy again, because a copy does not carry the\n\t\t// non-enumerable flag of markRaw.\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// Give the actor to each descendant, through the provide and inject mechanism of Vue.\n\t\t//\n\t\t// The code gives a stable Proxy, and not the raw actor. The pattern is the same as\n\t\t// the ViewKey proxy of ActorProviderInner. The injected value therefore follows the\n\t\t// CURRENT `props.actor`: each injector keeps the reference of the setup(), and every\n\t\t// trap resolves against the newest actor (actorRef.value) after a change of the prop.\n\t\t// The proxy forwards each relevant trap. Therefore it behaves like the actor below it\n\t\t// for a read, for a write, for a membership test (`in`), for an enumeration\n\t\t// (Object.keys and a spread), and for a prototype test (`instanceof`). Each method\n\t\t// binds to the current raw actor. Therefore `this`, and also each private field,\n\t\t// works exactly as in a direct call.\n\t\t//\n\t\t// IMPORTANT: this proxy is NOT identity-equal to `props.actor`, because it is a\n\t\t// separate object, and therefore a separate key of a WeakMap. A consumer that uses\n\t\t// the identity as its key must receive the `props.actor` value itself, and never this\n\t\t// injected proxy. The most important such consumer is the guard of one bridge for\n\t\t// each actor in RouterBridgeBase: it uses the identity of the actor as the key of a\n\t\t// WeakMap at the module level.\n\t\t//\n\t\t// The identity of a method: the code keeps each bound method, with the method below\n\t\t// it as the key. A repeated read therefore returns the SAME function\n\t\t// (actor.send === actor.send), and the hot path allocates nothing. A read binds the\n\t\t// method again only after a change of the method below it. The watch handler below\n\t\t// CLEARS the cache on a change of the actor. Therefore each prototype method binds to\n\t\t// 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// An invariant of a Proxy: the target of this proxy is an empty {}. Therefore each\n\t\t\t\t\t// key of ownKeys must resolve to a configurable descriptor here. Without that, the\n\t\t\t\t\t// [[GetOwnProperty]] invariant throws, because a proxy must not report a property that\n\t\t\t\t\t// its target has not as non-configurable. The own props of the actor are mutable at\n\t\t\t\t\t// run time in each case. Therefore `configurable: true` is correct enough for the\n\t\t\t\t\t// enumeration of Object.keys and of a spread.\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 the first value, then subscribe. Both steps are synchronous, and no scheduler\n\t\t// runs between them.\n\t\t// Solid does the same with createEffect, and Svelte does the same with $effect.\n\t\t// The pattern is therefore the same in each framework.\n\t\tconst view = ref<PlaySpec | null>(actorRef.value.currentView.get());\n\n\t\t// The store lifecycle: it seeds the store again on a change of the viewKey, it\n\t\t// refreshes /context in place in every other case, it resets the store on a change of\n\t\t// the actor, and it guards the identity cache. The shared coordinator comes from\n\t\t// @xmachines/play-actor. Only the wiring of the reactivity below belongs to Vue.\n\t\tconst storeLifecycle = createViewStoreLifecycle((seed) =>\n\t\t\txstateStoreStateStore({ atom: createAtom(seed) }),\n\t\t);\n\t\t// storeKey is the key of a SUBTREE REMOUNT. Therefore it must change only after a\n\t\t// new seed of the store, and never after a refresh of /context in place. Without this\n\t\t// rule, each update of the projection removes the subtree on the screen.\n\t\t// (The controlled mode seeds the store never again. Therefore the key changes never\n\t\t// there.)\n\t\tlet storeKey = 0;\n\n\t\t// The signal watcher connects the TC39 Signals to the reactivity of Vue.\n\t\t// The code assigns it again on a change of the actor. Therefore the cleanup always\n\t\t// releases the live watcher.\n\t\t// Every view emission clears the errored flag (see the reset rule above) before the\n\t\t// new view renders. A hit of the error boundary therefore retries here.\n\t\tlet unwatch = watchSignal(actorRef.value.currentView, (nextView) => {\n\t\t\terrored.value = false;\n\t\t\tfailureLatch.clear();\n\t\t\tview.value = nextView;\n\t\t});\n\n\t\t// The reset watches the STORE too. A render fails on it, so a caller that repairs a\n\t\t// controlled store — without a new emission — must see the children again.\n\t\t// play-react and play-solid hold the same rule, and the actor swap below covers the\n\t\t// third input.\n\t\twatch(\n\t\t\t() => props.store,\n\t\t\t() => {\n\t\t\t\terrored.value = false;\n\t\t\t\tfailureLatch.clear();\n\t\t\t},\n\t\t);\n\n\t\t// React to a change of `props.actor`: cancel the subscription to the currentView of\n\t\t// the OLD actor, seed the view from the NEW actor synchronously (`flush: \"sync\"`, so\n\t\t// that no scheduler runs between the steps, as in the seed and watch pattern of the\n\t\t// mount above), and subscribe to the new signal. The view of the new seed has a new\n\t\t// identity. Therefore the branch of the internal store below builds the store again,\n\t\t// and it changes storeKey, exactly like a normal 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// (The store lifecycle resets itself on a change of the actor, because a store that\n\t\t\t\t// stays alive must not outlive its actor.)\n\t\t\t\t// Clear the cache of the bound methods. Each prototype method therefore binds to the\n\t\t\t\t// new actor on the next read. See the get trap of the injected proxy.\n\t\t\t\tboundMethodCache.clear();\n\t\t\t\t// The new seed is a view emission — clear the errored flag with it.\n\t\t\t\terrored.value = false;\n\t\t\t\tfailureLatch.clear();\n\t\t\t\tview.value = nextActor.currentView.get();\n\t\t\t\tunwatch = watchSignal(nextActor.currentView, (nextView) => {\n\t\t\t\t\terrored.value = false;\n\t\t\t\t\tfailureLatch.clear();\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\tguard.dispose();\n\t\t\tunwatch();\n\t\t});\n\n\t\treturn () => {\n\t\t\t// The nonce of the retry. The read makes this render function depend on it, so\n\t\t\t// the reset of the host renders again — see `retry` above.\n\t\t\tvoid retryNonce.value;\n\n\t\t\t// A contained error of a descendant: show the fallback slot, or show nothing,\n\t\t\t// until the next view emission clears the flag and retries the render.\n\t\t\tif (errored.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\t// No view: show the fallback slot, or show 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, which is external and controlled, or internal for each\n\t\t\t// viewKey, through the shared lifecycle. The children receive the store with the\n\t\t\t// guard, because /context is read-only to the spec. The machinery refreshes the\n\t\t\t// store through the reference without a guard, inside the coordinator.\n\t\t\t//\n\t\t\t// The try CONTAINS a failure of this resolution. `spec.state` can throw — a\n\t\t\t// projection with an accessor, or a seed of a store — and `onErrorCaptured`\n\t\t\t// above sees a throw of a DESCENDANT only, never one of this render function.\n\t\t\t// Without the try such a failure leaves the provider, reaches\n\t\t\t// app.config.errorHandler, and takes the application down, while `onError` and\n\t\t\t// the fallback slot both stay unused.\n\t\t\t//\n\t\t\t// The branch sets the errored flag NOT: a mutation of the state during a render\n\t\t\t// starts a second render. It returns the fallback for THIS render instead, and\n\t\t\t// the next view emission renders the children again — which is the reset rule,\n\t\t\t// with no flag to clear.\n\t\t\tlet resolution;\n\t\t\ttry {\n\t\t\t\tresolution = storeLifecycle.resolve(actorRef.value, spec, props.store);\n\t\t\t} catch (error) {\n\t\t\t\t// LATCH the failure, and report it ONE time. The render function runs again\n\t\t\t\t// for any tracked change, and a report on each attempt would call onError a\n\t\t\t\t// second and a third time for one failure — play-react, play-solid and\n\t\t\t\t// play-svelte each report once.\n\t\t\t\tif (failureLatch.shouldReport(actorRef.value, spec, props.store)) report(error);\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\t\t\tfailureLatch.clear();\n\t\t\tif (resolution.reseeded) storeKey++;\n\t\t\tconst store: StateStore = resolution.guardedStore;\n\n\t\t\t// ActorProviderInner renders inside StateProvider. Therefore 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":""}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ViewKey } from "./actor-provider-context.js";
|
|
2
|
+
import { canRaiseAgain, failsTheRender, isComponentUpdate } from "./error-source.js";
|
|
2
3
|
import { provideActor } from "./useActor.js";
|
|
3
|
-
import { computed, defineComponent, h, markRaw, onUnmounted, provide, ref, shallowRef, toRaw, watch } from "vue";
|
|
4
|
+
import { computed, defineComponent, getCurrentInstance, h, markRaw, onErrorCaptured, onUnmounted, provide, ref, shallowRef, toRaw, watch } from "vue";
|
|
4
5
|
import { ActionProvider, StateProvider, VisibilityProvider, useStateStore } from "@xmachines/json-render-vue";
|
|
5
6
|
import { watchSignal } from "@xmachines/play-signals";
|
|
6
|
-
import { attachRenderErrorHandler, createViewStoreLifecycle } from "@xmachines/play-actor";
|
|
7
|
+
import { attachRenderErrorHandler, createFailureLatch, createReportGuard, createViewStoreLifecycle } from "@xmachines/play-actor";
|
|
7
8
|
import { createAtom } from "@xstate/store";
|
|
8
9
|
import { xstateStoreStateStore } from "@xmachines/json-render-xstate";
|
|
9
10
|
//#region packages/play-vue/src/ActorProvider.vue?vue&type=script&lang.ts
|
|
@@ -85,9 +86,90 @@ var ActorProvider_vue_vue_type_script_lang_default = defineComponent({
|
|
|
85
86
|
onRenderError: {
|
|
86
87
|
type: Function,
|
|
87
88
|
default: void 0
|
|
89
|
+
},
|
|
90
|
+
onError: {
|
|
91
|
+
type: Function,
|
|
92
|
+
default: void 0
|
|
88
93
|
}
|
|
89
94
|
},
|
|
90
95
|
setup(props, { slots }) {
|
|
96
|
+
const errored = ref(false);
|
|
97
|
+
/**
|
|
98
|
+
* The report-once rule, from @xmachines/play-actor.
|
|
99
|
+
*
|
|
100
|
+
* It holds the actor, the view and the store of the failure that it reported. A
|
|
101
|
+
* latch that compared the identity of the ERROR held nothing: an accessor that
|
|
102
|
+
* throws builds a new one at each read, which is the ordinary shape of a failing
|
|
103
|
+
* projection. play-svelte and play-solid use the same latch.
|
|
104
|
+
*/
|
|
105
|
+
const failureLatch = createFailureLatch();
|
|
106
|
+
const guard = createReportGuard({
|
|
107
|
+
noHandler: "[@xmachines/play-vue] ActorProvider contained an error of a descendant. Give an onError prop to receive it.",
|
|
108
|
+
handlerThrew: "[@xmachines/play-vue] the onError handler of the host threw. ActorProvider contained the failure of the view all the same."
|
|
109
|
+
});
|
|
110
|
+
const retryNonce = ref(0);
|
|
111
|
+
/**
|
|
112
|
+
* Clears the contained error and renders again.
|
|
113
|
+
*
|
|
114
|
+
* The render function reads `view.value`, so the retry resolves the view that the
|
|
115
|
+
* actor holds NOW. A retry bound to the view that failed would take a host that
|
|
116
|
+
* keeps the callback back onto the screen that threw.
|
|
117
|
+
*/
|
|
118
|
+
const retry = () => {
|
|
119
|
+
if (guard.blocked()) return;
|
|
120
|
+
errored.value = false;
|
|
121
|
+
failureLatch.clear();
|
|
122
|
+
retryNonce.value++;
|
|
123
|
+
};
|
|
124
|
+
/** Sends a contained failure to `onError`, or to the console when none is given. */
|
|
125
|
+
const report = (err) => {
|
|
126
|
+
const handler = props.onError;
|
|
127
|
+
guard.report(err, handler ? () => handler(err, retry) : void 0);
|
|
128
|
+
};
|
|
129
|
+
const appConfig = getCurrentInstance()?.appContext.config;
|
|
130
|
+
let letThrough = null;
|
|
131
|
+
const remember = (error, instance) => {
|
|
132
|
+
const record = {
|
|
133
|
+
error,
|
|
134
|
+
instance
|
|
135
|
+
};
|
|
136
|
+
letThrough = record;
|
|
137
|
+
queueMicrotask(() => {
|
|
138
|
+
if (letThrough === record) letThrough = null;
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Takes the record. It answers TRUE when the record names this error, and when the
|
|
143
|
+
* instance that raises it is the recorded one or one of its ancestors.
|
|
144
|
+
*
|
|
145
|
+
* The comparison reads `$` on both sides, which is the INTERNAL instance. Vue hands
|
|
146
|
+
* `onErrorCaptured` the `proxy` of a component, and `$parent` answers with the
|
|
147
|
+
* `exposeProxy` of a parent that called `expose()` — which the compiler makes every
|
|
148
|
+
* `<script setup>` component do. The two objects differ, so a walk that compared
|
|
149
|
+
* them found an exposed ancestor never, and the boundary contained a watcher error
|
|
150
|
+
* that it must let through. `$` resolves through both proxies and names one object.
|
|
151
|
+
*/
|
|
152
|
+
const takeRepeat = (error, instance) => {
|
|
153
|
+
const record = letThrough;
|
|
154
|
+
letThrough = null;
|
|
155
|
+
if (record === null || record.error !== error) return false;
|
|
156
|
+
for (let node = record.instance; node !== null; node = node.$parent) if (node.$ === instance?.$) return true;
|
|
157
|
+
return false;
|
|
158
|
+
};
|
|
159
|
+
onErrorCaptured((err, instance, info) => {
|
|
160
|
+
if (!failsTheRender(info)) {
|
|
161
|
+
if (canRaiseAgain(info, appConfig)) remember(err, instance);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const repeated = takeRepeat(err, instance);
|
|
165
|
+
if (isComponentUpdate(info) && repeated) {
|
|
166
|
+
console.error("[@xmachines/play-vue] ActorProvider stopped a second report of this error. Vue raised it again under the source of a component update, and it failed no render.", err);
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
errored.value = true;
|
|
170
|
+
report(err);
|
|
171
|
+
return false;
|
|
172
|
+
});
|
|
91
173
|
const actorRef = shallowRef(toRaw(props.actor));
|
|
92
174
|
const rawRegistry = Object.fromEntries(Object.entries(toRaw(props.registryResult).registry).map(([k, v]) => [k, markRaw(v)]));
|
|
93
175
|
const rawRegistryResult = {
|
|
@@ -135,25 +217,45 @@ var ActorProvider_vue_vue_type_script_lang_default = defineComponent({
|
|
|
135
217
|
const storeLifecycle = createViewStoreLifecycle((seed) => xstateStoreStateStore({ atom: createAtom(seed) }));
|
|
136
218
|
let storeKey = 0;
|
|
137
219
|
let unwatch = watchSignal(actorRef.value.currentView, (nextView) => {
|
|
220
|
+
errored.value = false;
|
|
221
|
+
failureLatch.clear();
|
|
138
222
|
view.value = nextView;
|
|
139
223
|
});
|
|
224
|
+
watch(() => props.store, () => {
|
|
225
|
+
errored.value = false;
|
|
226
|
+
failureLatch.clear();
|
|
227
|
+
});
|
|
140
228
|
watch(() => toRaw(props.actor), (nextActor) => {
|
|
141
229
|
if (nextActor === actorRef.value) return;
|
|
142
230
|
unwatch();
|
|
143
231
|
actorRef.value = nextActor;
|
|
144
232
|
boundMethodCache.clear();
|
|
233
|
+
errored.value = false;
|
|
234
|
+
failureLatch.clear();
|
|
145
235
|
view.value = nextActor.currentView.get();
|
|
146
236
|
unwatch = watchSignal(nextActor.currentView, (nextView) => {
|
|
237
|
+
errored.value = false;
|
|
238
|
+
failureLatch.clear();
|
|
147
239
|
view.value = nextView;
|
|
148
240
|
});
|
|
149
241
|
}, { flush: "sync" });
|
|
150
242
|
onUnmounted(() => {
|
|
243
|
+
guard.dispose();
|
|
151
244
|
unwatch();
|
|
152
245
|
});
|
|
153
246
|
return () => {
|
|
247
|
+
retryNonce.value;
|
|
248
|
+
if (errored.value) return slots.fallback ? slots.fallback() : null;
|
|
154
249
|
if (!view.value) return slots.fallback ? slots.fallback() : null;
|
|
155
250
|
const spec = view.value;
|
|
156
|
-
|
|
251
|
+
let resolution;
|
|
252
|
+
try {
|
|
253
|
+
resolution = storeLifecycle.resolve(actorRef.value, spec, props.store);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (failureLatch.shouldReport(actorRef.value, spec, props.store)) report(error);
|
|
256
|
+
return slots.fallback ? slots.fallback() : null;
|
|
257
|
+
}
|
|
258
|
+
failureLatch.clear();
|
|
157
259
|
if (resolution.reseeded) storeKey++;
|
|
158
260
|
const store = resolution.guardedStore;
|
|
159
261
|
return h(StateProvider, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActorProvider.vue_vue_type_script_lang.js","names":[],"sources":["../src/ActorProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * ActorProvider — the low-level provider of the XMachines Vue render architecture.\n *\n * It owns the complete actor lifecycle:\n * - the signal subscription (watchSignal), which connects the TC39 Signals to the Vue reactivity\n * - the StateStore lifecycle of each view, controlled and uncontrolled\n * - the resolution of the handlers, through ActorProviderInner, which must be inside StateProvider\n * - the ViewContextValue, which it provides through the ViewKey injection key\n * - the ActionProvider wrapper and the VisibilityProvider wrapper, for the Renderer below\n * - the injection of onRenderError into the registry\n *\n * The author of a library who needs this control uses the component directly.\n * An end user takes <PlayUIProvider> instead.\n *\n * @invariant Actor Authority - the Actor decides each state transition, with its guards\n * @invariant Passive Infrastructure - the component observes the signals, and it sends the events\n * @invariant Signal-Only Reactivity - the state of the business logic is in the 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 { attachRenderErrorHandler, createViewStoreLifecycle } 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 the props type and the context accessors, for a consumer that imports from this file\nexport type { ActorProviderProps } from \"./types.js\";\nexport { usePlayView, getPlayViewContext } from \"./actor-provider-context.js\"; // getPlayViewContext @deprecated\nexport type { ViewContextValue } from \"./actor-provider-context.js\";\n\n// ---------------------------------------------------------------------------\n// ActorProviderInner — it renders inside StateProvider, so that it can call useStateStore()\n// It gives the ViewContextValue, the ActionProvider, and the VisibilityProvider to the Renderer below.\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// The code uses a shallowRef for the value of the view context, because a deep\n\t\t// reactivity costs too much. The Proxy below lets inject() always read the newest\n\t\t// value.\n\t\tconst viewRef = shallowRef<ViewContextValue | null>(null);\n\n\t\t// Give the ViewContextValue through the inject and provide system of Vue.\n\t\t// The code calls provide synchronously in setup(), so that Vue registers the value on\n\t\t// the instance of the component. It uses a Proxy, so that each descendant always\n\t\t// receives the newest 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() during setup(), which is synchronous and before the return.\n\t\t// A Vue composable that uses inject() must run during setup(), and not in a render\n\t\t// function.\n\t\tconst stateStore = useStateStore();\n\n\t\t// Build a SetState adapter, because the factory of the handlers expects the pattern\n\t\t// of an updater function. The code defines it one time, in setup(). It reads\n\t\t// stateStore.getSnapshot and stateStore.update at the moment of the call. Therefore\n\t\t// it always works on the live state of the store, and it needs no new definition when\n\t\t// the identity of stateStore changes between two 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// Keep the handlers, with the identity of the registryResult prop as the key. The\n\t\t// getter functions are stable closures, and they hold the stateStore of setup().\n\t\t// Therefore they cause no new computation. The code computes the handlers again only\n\t\t// after a change of the definition of the registry, and not on each render of a view\n\t\t// update.\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 the children with an ActionProvider and a VisibilityProvider. The Renderer of\n\t\t\t// PlayRenderer therefore works also with ActorProvider alone, without PlayUIProvider\n\t\t\t// and without 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 — the 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// Take the actor out of the reactive proxy of Vue, to reach the raw Signal objects.\n\t\t// A shallowRef holds the actor. Therefore the injected value and the signal watcher\n\t\t// can follow `props.actor` when the prop changes after the mount.\n\t\tconst actorRef = shallowRef(toRaw(props.actor as AbstractActor<AnyActorLogic> & Viewable));\n\n\t\t// Take the registryResult out of the proxy, and mark each component as raw, because the reactivity of Vue costs too much here\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// Put the onRenderError prop into the registry. The property is not enumerable, and\n\t\t// it replaces the handler of the defineRegistry level. attachRenderErrorHandler makes\n\t\t// a copy, and it changes the registry of the caller never.\n\t\t// The code must call markRaw on the copy again, because a copy does not carry the\n\t\t// non-enumerable flag of markRaw.\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// Give the actor to each descendant, through the provide and inject mechanism of Vue.\n\t\t//\n\t\t// The code gives a stable Proxy, and not the raw actor. The pattern is the same as\n\t\t// the ViewKey proxy of ActorProviderInner. The injected value therefore follows the\n\t\t// CURRENT `props.actor`: each injector keeps the reference of the setup(), and every\n\t\t// trap resolves against the newest actor (actorRef.value) after a change of the prop.\n\t\t// The proxy forwards each relevant trap. Therefore it behaves like the actor below it\n\t\t// for a read, for a write, for a membership test (`in`), for an enumeration\n\t\t// (Object.keys and a spread), and for a prototype test (`instanceof`). Each method\n\t\t// binds to the current raw actor. Therefore `this`, and also each private field,\n\t\t// works exactly as in a direct call.\n\t\t//\n\t\t// IMPORTANT: this proxy is NOT identity-equal to `props.actor`, because it is a\n\t\t// separate object, and therefore a separate key of a WeakMap. A consumer that uses\n\t\t// the identity as its key must receive the `props.actor` value itself, and never this\n\t\t// injected proxy. The most important such consumer is the guard of one bridge for\n\t\t// each actor in RouterBridgeBase: it uses the identity of the actor as the key of a\n\t\t// WeakMap at the module level.\n\t\t//\n\t\t// The identity of a method: the code keeps each bound method, with the method below\n\t\t// it as the key. A repeated read therefore returns the SAME function\n\t\t// (actor.send === actor.send), and the hot path allocates nothing. A read binds the\n\t\t// method again only after a change of the method below it. The watch handler below\n\t\t// CLEARS the cache on a change of the actor. Therefore each prototype method binds to\n\t\t// 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// An invariant of a Proxy: the target of this proxy is an empty {}. Therefore each\n\t\t\t\t\t// key of ownKeys must resolve to a configurable descriptor here. Without that, the\n\t\t\t\t\t// [[GetOwnProperty]] invariant throws, because a proxy must not report a property that\n\t\t\t\t\t// its target has not as non-configurable. The own props of the actor are mutable at\n\t\t\t\t\t// run time in each case. Therefore `configurable: true` is correct enough for the\n\t\t\t\t\t// enumeration of Object.keys and of a spread.\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 the first value, then subscribe. Both steps are synchronous, and no scheduler\n\t\t// runs between them.\n\t\t// Solid does the same with createEffect, and Svelte does the same with $effect.\n\t\t// The pattern is therefore the same in each framework.\n\t\tconst view = ref<PlaySpec | null>(actorRef.value.currentView.get());\n\n\t\t// The store lifecycle: it seeds the store again on a change of the viewKey, it\n\t\t// refreshes /context in place in every other case, it resets the store on a change of\n\t\t// the actor, and it guards the identity cache. The shared coordinator comes from\n\t\t// @xmachines/play-actor. Only the wiring of the reactivity below belongs to Vue.\n\t\tconst storeLifecycle = createViewStoreLifecycle((seed) =>\n\t\t\txstateStoreStateStore({ atom: createAtom(seed) }),\n\t\t);\n\t\t// storeKey is the key of a SUBTREE REMOUNT. Therefore it must change only after a\n\t\t// new seed of the store, and never after a refresh of /context in place. Without this\n\t\t// rule, each update of the projection removes the subtree on the screen.\n\t\t// (The controlled mode seeds the store never again. Therefore the key changes never\n\t\t// there.)\n\t\tlet storeKey = 0;\n\n\t\t// The signal watcher connects the TC39 Signals to the reactivity of Vue.\n\t\t// The code assigns it again on a change of the actor. Therefore the cleanup always\n\t\t// 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 a change of `props.actor`: cancel the subscription to the currentView of\n\t\t// the OLD actor, seed the view from the NEW actor synchronously (`flush: \"sync\"`, so\n\t\t// that no scheduler runs between the steps, as in the seed and watch pattern of the\n\t\t// mount above), and subscribe to the new signal. The view of the new seed has a new\n\t\t// identity. Therefore the branch of the internal store below builds the store again,\n\t\t// and it changes storeKey, exactly like a normal 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// (The store lifecycle resets itself on a change of the actor, because a store that\n\t\t\t\t// stays alive must not outlive its actor.)\n\t\t\t\t// Clear the cache of the bound methods. Each prototype method therefore binds to the\n\t\t\t\t// new actor on the next read. See the get trap of the injected proxy.\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 the fallback slot, or show 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, which is external and controlled, or internal for each\n\t\t\t// viewKey, through the shared lifecycle. The children receive the store with the\n\t\t\t// guard, because /context is read-only to the spec. The machinery refreshes the\n\t\t\t// store through the reference without a guard, inside the coordinator.\n\t\t\tconst resolution = storeLifecycle.resolve(actorRef.value, spec, props.store);\n\t\t\tif (resolution.reseeded) storeKey++;\n\t\t\tconst store: StateStore = resolution.guardedStore;\n\n\t\t\t// ActorProviderInner renders inside StateProvider. Therefore 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEA,IAAM,qBAAqB,gBAAgB;CAC1C,MAAM;CACN,OAAO;EACN,gBAAgB;GACf,MAAM;GACN,UAAU;EACX;EACA,MAAM;GACL,MAAM;GACN,SAAS;EACV;EACA,OAAO;GACN,MAAM;GACN,UAAU;EACX;CACD;CACA,MAAM,OAAO,EAAE,SAAS;EAIvB,MAAM,UAAU,WAAoC,IAAI;EAMxD,QACC,SACA,IAAI,MAAM,CAAC,GAAuB,EACjC,IAAI,SAAS,MAAc;GAC1B,OAAO,QAAQ,QAAQ;EACxB,EACD,CAAC,CACF;EAKA,MAAM,aAAa,cAAc;EAOjC,MAAM,mBAA6B,YAAY;GAC9C,MAAM,OAAO,WAAW,YAAY;GACpC,WAAW,OAAO,QAAQ,IAAI,CAAC;EAChC;EAOA,MAAM,WAAW,eAChB,MAAM,eAAe,eACd,uBACA,WAAW,YAAY,CAC9B,CACD;EAEA,aAAa;GACZ,IAAI,CAAC,MAAM,MAAM;IAChB,QAAQ,QAAQ;IAChB,OAAO,MAAM,UAAU,KAAK;GAC7B;GAEA,QAAQ,QAAQ;IACf,MAAM,MAAM;IACZ,UAAU,SAAS;IACnB,UAAU,MAAM,eAAe;IAC/B,OAAO,MAAM;GACd;GAKA,OAAO,EAAE,gBAAgB,EAAE,UAAU,SAAS,MAAM,SACnD,EAAE,oBAAoB,CAAC,SAAS,MAAM,UAAU,KAAK,IAAI,CAC1D;EACD;CACD;AACD,CAAC;AAMD,IAAA,iDAAe,gBAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;EACX;EACA,gBAAgB;GACf,MAAM;GACN,UAAU;EACX;EACA,OAAO;GACN,MAAM;GACN,SAAS,KAAA;EACV;EACA,eAAe;GACd,MAAM;GACN,SAAS,KAAA;EACV;CACD;CACA,MAAM,OAAO,EAAE,SAAS;EAIvB,MAAM,WAAW,WAAW,MAAM,MAAM,KAAgD,CAAC;EAGzF,MAAM,cAAc,OAAO,YAC1B,OAAO,QAAQ,MAAM,MAAM,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CACpE,GACA,QAAQ,CAAW,CACpB,CAAC,CACF;EAOA,MAAM,oBAA0C;GAC/C,GAAG,MAAM,MAAM,cAAc;GAC7B,UAAU,QACT,MAAM,gBACH,yBAAyB,aAAa,MAAM,aAAa,IACzD,WACJ;EACD;EA2BA,MAAM,mCAAmB,IAAI,IAAsD;EACnF,aACC,IAAI,MAAM,CAAC,GAAmB;GAC7B,IAAI,SAAS,MAAM;IAClB,MAAM,UAAU,SAAS;IACzB,MAAM,QAAQ,QAAQ,IAAI,SAAS,MAAM,OAAO;IAChD,IAAI,OAAO,UAAU,YAAY,OAAO;IACxC,MAAM,SAAS,iBAAiB,IAAI,IAAI;IACxC,IAAI,UAAU,OAAO,WAAW,OAAO,OAAO,OAAO;IACrD,MAAM,QAAS,MAA0C,KAAK,OAAO;IACrE,iBAAiB,IAAI,MAAM;KAAE,QAAQ;KAAO;IAAM,CAAC;IACnD,OAAO;GACR;GACA,IAAI,SAAS,MAAM;IAClB,OAAO,QAAQ,SAAS;GACzB;GACA,IAAI,SAAS,MAAM,OAAO;IACzB,OAAO,QAAQ,IAAI,SAAS,OAAO,MAAM,OAAO,SAAS,KAAK;GAC/D;GACA,eAAe,SAAS,MAAM;IAC7B,OAAO,QAAQ,eAAe,SAAS,OAAO,IAAI;GACnD;GACA,UAAU;IACT,OAAO,QAAQ,QAAQ,SAAS,KAAK;GACtC;GACA,yBAAyB,SAAS,MAAM;IACvC,MAAM,OAAO,QAAQ,yBAAyB,SAAS,OAAO,IAAI;IAClE,IAAI,CAAC,MAAM,OAAO,KAAA;IAOlB,KAAK,eAAe;IACpB,OAAO;GACR;GACA,iBAAiB;IAChB,OAAO,QAAQ,eAAe,SAAS,KAAK;GAC7C;EACD,CAAC,CACF;EAMA,MAAM,OAAO,IAAqB,SAAS,MAAM,YAAY,IAAI,CAAC;EAMlE,MAAM,iBAAiB,0BAA0B,SAChD,sBAAsB,EAAE,MAAM,WAAW,IAAI,EAAE,CAAC,CACjD;EAMA,IAAI,WAAW;EAKf,IAAI,UAAU,YAAY,SAAS,MAAM,cAAc,aAAa;GACnE,KAAK,QAAQ;EACd,CAAC;EAQD,YACO,MAAM,MAAM,KAAgD,IACjE,cAAc;GACd,IAAI,cAAc,SAAS,OAAO;GAClC,QAAQ;GACR,SAAS,QAAQ;GAKjB,iBAAiB,MAAM;GACvB,KAAK,QAAQ,UAAU,YAAY,IAAI;GACvC,UAAU,YAAY,UAAU,cAAc,aAAa;IAC1D,KAAK,QAAQ;GACd,CAAC;EACF,GACA,EAAE,OAAO,OAAO,CACjB;EAEA,kBAAkB;GACjB,QAAQ;EACT,CAAC;EAED,aAAa;GAEZ,IAAI,CAAC,KAAK,OACT,OAAO,MAAM,WAAW,MAAM,SAAS,IAAI;GAG5C,MAAM,OAAO,KAAK;GAMlB,MAAM,aAAa,eAAe,QAAQ,SAAS,OAAO,MAAM,MAAM,KAAK;GAC3E,IAAI,WAAW,UAAU;GACzB,MAAM,QAAoB,WAAW;GAGrC,OAAO,EAAE,eAAe;IAAE;IAAO,KAAK;GAAS,SAC9C,EAAE,oBAAoB;IAAE,gBAAgB;IAAmB;IAAM;GAAM,GAAG,KAAK,CAChF;EACD;CACD;AACD,CAAC"}
|
|
1
|
+
{"version":3,"file":"ActorProvider.vue_vue_type_script_lang.js","names":[],"sources":["../src/ActorProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * ActorProvider — the low-level provider of the XMachines Vue render architecture.\n *\n * It owns the complete actor lifecycle:\n * - the signal subscription (watchSignal), which connects the TC39 Signals to the Vue reactivity\n * - the StateStore lifecycle of each view, controlled and uncontrolled\n * - the resolution of the handlers, through ActorProviderInner, which must be inside StateProvider\n * - the ViewContextValue, which it provides through the ViewKey injection key\n * - the ActionProvider wrapper and the VisibilityProvider wrapper, for the Renderer below\n * - the injection of onRenderError into the registry\n *\n * The author of a library who needs this control uses the component directly.\n * An end user takes <PlayUIProvider> instead.\n *\n * @invariant Actor Authority - the Actor decides each state transition, with its guards\n * @invariant Passive Infrastructure - the component observes the signals, and it sends the events\n * @invariant Signal-Only Reactivity - the state of the business logic is in the actor signals\n */\n\nimport {\n\tdefineComponent,\n\tref,\n\tcomputed,\n\ttoRaw,\n\tmarkRaw,\n\tonUnmounted,\n\tonErrorCaptured,\n\th,\n\tprovide,\n\tshallowRef,\n\twatch,\n\tgetCurrentInstance,\n} from \"vue\";\nimport type { ComponentPublicInstance, PropType } from \"vue\";\nimport { watchSignal } from \"@xmachines/play-signals\";\nimport { canRaiseAgain, failsTheRender, isComponentUpdate } from \"./error-source.js\";\nimport {\n\tattachRenderErrorHandler,\n\tcreateFailureLatch,\n\tcreateReportGuard,\n\tcreateViewStoreLifecycle,\n} 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 the props type and the context accessors, for a consumer that imports from this file\nexport type { ActorProviderProps } from \"./types.js\";\nexport { usePlayView, getPlayViewContext } from \"./actor-provider-context.js\"; // getPlayViewContext @deprecated\nexport type { ViewContextValue } from \"./actor-provider-context.js\";\n\n// ---------------------------------------------------------------------------\n// ActorProviderInner — it renders inside StateProvider, so that it can call useStateStore()\n// It gives the ViewContextValue, the ActionProvider, and the VisibilityProvider to the Renderer below.\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// The code uses a shallowRef for the value of the view context, because a deep\n\t\t// reactivity costs too much. The Proxy below lets inject() always read the newest\n\t\t// value.\n\t\tconst viewRef = shallowRef<ViewContextValue | null>(null);\n\n\t\t// Give the ViewContextValue through the inject and provide system of Vue.\n\t\t// The code calls provide synchronously in setup(), so that Vue registers the value on\n\t\t// the instance of the component. It uses a Proxy, so that each descendant always\n\t\t// receives the newest 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() during setup(), which is synchronous and before the return.\n\t\t// A Vue composable that uses inject() must run during setup(), and not in a render\n\t\t// function.\n\t\tconst stateStore = useStateStore();\n\n\t\t// Build a SetState adapter, because the factory of the handlers expects the pattern\n\t\t// of an updater function. The code defines it one time, in setup(). It reads\n\t\t// stateStore.getSnapshot and stateStore.update at the moment of the call. Therefore\n\t\t// it always works on the live state of the store, and it needs no new definition when\n\t\t// the identity of stateStore changes between two 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// Keep the handlers, with the identity of the registryResult prop as the key. The\n\t\t// getter functions are stable closures, and they hold the stateStore of setup().\n\t\t// Therefore they cause no new computation. The code computes the handlers again only\n\t\t// after a change of the definition of the registry, and not on each render of a view\n\t\t// update.\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 the children with an ActionProvider and a VisibilityProvider. The Renderer of\n\t\t\t// PlayRenderer therefore works also with ActorProvider alone, without PlayUIProvider\n\t\t\t// and without 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 — the 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\tonError: {\n\t\t\ttype: Function as PropType<(error: unknown, reset: () => void) => void>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\t// The error boundary. It contains an error of the RENDER of a descendant — the\n\t\t// render function, the setup function, and a lifecycle hook — so that a subtree\n\t\t// that throws cannot take the whole application down. `onErrorCaptured` notifies\n\t\t// `onError`, it sets the errored flag, and the render function below then returns\n\t\t// the `fallback` slot instead of the children.\n\t\t//\n\t\t// `onErrorCaptured` of Vue also fires for an error of an event handler, and of a\n\t\t// watcher callback, which no other renderer of this workspace sees: the boundary\n\t\t// of React, the ErrorBoundary of Solid, and `<svelte:boundary>` each catch a\n\t\t// failure of the render only. A click handler that throws must therefore NOT\n\t\t// replace the view with the fallback. This boundary leaves such an error\n\t\t// COMPLETELY alone: it calls `onError` never, and it returns undefined, so the\n\t\t// error continues to `app.config.errorHandler` exactly as it does without this\n\t\t// provider. A report here would give play-vue a wider `onError` contract than the\n\t\t// other four renderers, and it would reach a caller that wires both this prop and\n\t\t// the global handler two times for one click.\n\t\t//\n\t\t// RESET RULE: the next view emission clears the flag (see the callbacks of\n\t\t// watchSignal and the handler of the actor swap). A view transition therefore\n\t\t// retries the render. Without the rule the user sends a recovery event, the actor\n\t\t// moves to a healthy view, and the screen keeps the fallback.\n\t\t//\n\t\t// The per-element boundary of @json-render catches a throw of a catalog component\n\t\t// upstream, and reports it through `onRenderError`. This boundary is the outer net\n\t\t// for everything else.\n\t\t//\n\t\t// The return of false stops a render error HERE. Without `onError` that error\n\t\t// therefore reaches neither `app.config.errorHandler` nor the console, and a\n\t\t// consumer that gives no handler loses it in silence. Write it to `console.error`\n\t\t// in that case, in the same way as the inner renderer does for a component that\n\t\t// throws without an `onRenderError` handler.\n\t\tconst errored = ref(false);\n\t\t/**\n\t\t * The report-once rule, from @xmachines/play-actor.\n\t\t *\n\t\t * It holds the actor, the view and the store of the failure that it reported. A\n\t\t * latch that compared the identity of the ERROR held nothing: an accessor that\n\t\t * throws builds a new one at each read, which is the ordinary shape of a failing\n\t\t * projection. play-svelte and play-solid use the same latch.\n\t\t */\n\t\tconst failureLatch = createFailureLatch();\n\n\t\t// The report guard, from @xmachines/play-actor: it blocks a reset that the host\n\t\t// calls from INSIDE the report, and every callback once the provider is gone. The\n\t\t// five renderers share the one implementation.\n\t\t//\n\t\t// The re-entry rule matters more here than a stack suggests: a write of a ref\n\t\t// renders on the NEXT tick, so a host with a retry-once policy loops with no stack\n\t\t// at all, and a flag around the retry itself would catch nothing.\n\t\t//\n\t\t// It CONTAINS a handler of the host that throws. The flag below schedules a\n\t\t// RENDER, and a throw that leaves the hook stops that render and skips the\n\t\t// `return false`, so the subtree that threw stays on the screen AND the error\n\t\t// reaches `app.config.errorHandler`.\n\t\tconst guard = createReportGuard({\n\t\t\tnoHandler:\n\t\t\t\t\"[@xmachines/play-vue] ActorProvider contained an error of a descendant. \" +\n\t\t\t\t\"Give an onError prop to receive it.\",\n\t\t\thandlerThrew:\n\t\t\t\t\"[@xmachines/play-vue] the onError handler of the host threw. \" +\n\t\t\t\t\"ActorProvider contained the failure of the view all the same.\",\n\t\t});\n\t\t// The retry renders again through a nonce that the render function reads. Clearing\n\t\t// the flags alone renders nothing: `errored.value = false` writes the value that\n\t\t// the ref holds already on the path of a failed RESOLUTION, and Vue schedules\n\t\t// nothing for a write that changes no value.\n\t\tconst retryNonce = ref(0);\n\n\t\t/**\n\t\t * Clears the contained error and renders again.\n\t\t *\n\t\t * The render function reads `view.value`, so the retry resolves the view that the\n\t\t * actor holds NOW. A retry bound to the view that failed would take a host that\n\t\t * keeps the callback back onto the screen that threw.\n\t\t */\n\t\tconst retry = (): void => {\n\t\t\tif (guard.blocked()) return;\n\t\t\terrored.value = false;\n\t\t\tfailureLatch.clear();\n\t\t\tretryNonce.value++;\n\t\t};\n\n\t\t/** Sends a contained failure to `onError`, or to the console when none is given. */\n\t\tconst report = (err: unknown): void => {\n\t\t\tconst handler = props.onError;\n\t\t\tguard.report(err, handler ? () => handler(err, retry) : undefined);\n\t\t};\n\n\t\t// The configuration of the application, read one time. `canRaiseAgain` asks the two\n\t\t// fields that `handleError` of Vue reads on every error. It lives in error-source.ts,\n\t\t// because its production half answers a spelling that a mounted test reaches never.\n\t\tconst appConfig = getCurrentInstance()?.appContext.config;\n\n\t\t// The error that this boundary let through LAST, and that the NEXT raise can still\n\t\t// claim as a repeat.\n\t\t//\n\t\t// The second raise is SYNCHRONOUS and IMMEDIATE. `logError` of the development\n\t\t// bundle re-throws, and the throw unwinds straight back into `callWithErrorHandling`\n\t\t// of `flushJobs`, which reports the same value again. No other raise reaches this\n\t\t// hook in between, so ONE slot holds the complete window.\n\t\t//\n\t\t// EVERY raise that fails the render takes the slot, and not a component update\n\t\t// alone. A record that no raise takes can otherwise outlive its window.\n\t\t//\n\t\t// The record names the INSTANCE beside the error value. A repeat carries the\n\t\t// instance whose JOB ran when the error unwound, and that is the owner of the\n\t\t// watcher OR one of its ANCESTORS: a pre-flush watcher of a child runs inside the\n\t\t// update of the parent that passes it a prop, and `flushJobs` reports the throw\n\t\t// under the parent — or under the outermost ancestor whose update ran. The take\n\t\t// therefore walks the `$parent` chain of the recorded instance. A SIBLING stands on\n\t\t// that chain never, and a sibling that fails with the same cached error is one\n\t\t// loader with two consumers: that failure is a first incident, and the boundary\n\t\t// contains it.\n\t\t//\n\t\t// The microtask is the BACKSTOP, for a raise that neither a repeat nor another\n\t\t// raise follows. A record for the life of the provider would disarm the boundary\n\t\t// for that error for ever, and a descendant that keeps a failure and raises it\n\t\t// again from a LATER render is an ordinary shape.\n\t\t//\n\t\t// Each `remember` schedules a clear of ITS OWN record, and that clear takes no\n\t\t// other. A clear that removed whatever the slot holds could run after a take\n\t\t// emptied the slot and a later `remember` filled it again, and it would then\n\t\t// disarm the boundary for a repeat that is still to come.\n\t\tlet letThrough: { error: unknown; instance: ComponentPublicInstance | null } | null = null;\n\t\tconst remember = (error: unknown, instance: ComponentPublicInstance | null): void => {\n\t\t\tconst record = { error, instance };\n\t\t\tletThrough = record;\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tif (letThrough === record) letThrough = null;\n\t\t\t});\n\t\t};\n\t\t/**\n\t\t * Takes the record. It answers TRUE when the record names this error, and when the\n\t\t * instance that raises it is the recorded one or one of its ancestors.\n\t\t *\n\t\t * The comparison reads `$` on both sides, which is the INTERNAL instance. Vue hands\n\t\t * `onErrorCaptured` the `proxy` of a component, and `$parent` answers with the\n\t\t * `exposeProxy` of a parent that called `expose()` — which the compiler makes every\n\t\t * `<script setup>` component do. The two objects differ, so a walk that compared\n\t\t * them found an exposed ancestor never, and the boundary contained a watcher error\n\t\t * that it must let through. `$` resolves through both proxies and names one object.\n\t\t */\n\t\tconst takeRepeat = (error: unknown, instance: ComponentPublicInstance | null): boolean => {\n\t\t\tconst record = letThrough;\n\t\t\tletThrough = null;\n\t\t\tif (record === null || record.error !== error) return false;\n\t\t\tfor (let node = record.instance; node !== null; node = node.$parent) {\n\t\t\t\tif (node.$ === instance?.$) return true;\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t\tonErrorCaptured((err, instance, info) => {\n\t\t\t// An error that did NOT fail the render leaves this boundary untouched: the\n\t\t\t// screen keeps its view, `onError` stays silent, and the return of undefined\n\t\t\t// sends the error on to `app.config.errorHandler`. The boundary of React, the\n\t\t\t// ErrorBoundary of Solid, and `<svelte:boundary>` each see such an error never,\n\t\t\t// so a call of `onError` here would give play-vue a wider contract than the\n\t\t\t// other four, AND it would report the same failure twice to a caller that wires\n\t\t\t// both `onError` and the global handler of the application.\n\t\t\tif (!failsTheRender(info)) {\n\t\t\t\t// Arm the record only where Vue WILL raise this error a second time. A record\n\t\t\t\t// that no repeat claims stands until the microtask, and a genuine failure of\n\t\t\t\t// a render in the same flush — the same descendant, the same cached error —\n\t\t\t\t// would then take it: no fallback, no `onError`, and no screen.\n\t\t\t\t//\n\t\t\t\t// `handleError` of Vue calls `app.config.errorHandler` where one exists, and\n\t\t\t\t// it reaches `logError` never; the production bundle throws only where the\n\t\t\t\t// application asks for `throwUnhandledErrorInProduction`. The one case that\n\t\t\t\t// this line cannot see is an ANCESTOR `errorCaptured` that answers false,\n\t\t\t\t// which stops Vue before `logError` too. That case stays open, and the doc of\n\t\t\t\t// `onError` names it.\n\t\t\t\tif (canRaiseAgain(info, appConfig)) remember(err, instance);\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Vue can raise the SAME error a SECOND time under another source. The\n\t\t\t// development bundle of `logError` re-throws an error that no\n\t\t\t// `app.config.errorHandler` took. A queued job then loses that throw to\n\t\t\t// `callWithErrorHandling(job, job.i, 15)` of `flushJobs` — a `watch` callback\n\t\t\t// with the default `pre` flush, for one — and `flushJobs` reports it again as\n\t\t\t// \"component update\". The classification above answers \"render failure\" for that\n\t\t\t// second spelling, so a watcher that threw would take the view off the screen\n\t\t\t// after all. One error is one incident: an error that this boundary let through\n\t\t\t// in this turn stays through.\n\t\t\t//\n\t\t\t// The gate asks for the SOURCE of the repeat as well. `flushJobs` reports a\n\t\t\t// job under \"component update\", and no other source can carry a second raise\n\t\t\t// to this hook, so a record of the turn can disarm the boundary for that one\n\t\t\t// spelling alone. Without the question a setup function or a render function\n\t\t\t// that throws the SAME error value in the same turn — a loader that caches its\n\t\t\t// rejection, for one — would reach no fallback, no `onError`, and no screen.\n\t\t\t//\n\t\t\t// It returns FALSE, and not undefined. The FIRST raise went on to\n\t\t\t// `app.config.errorHandler`, which is the contract of a non-render source, and\n\t\t\t// the development bundle re-threw it there — that throw IS this second report.\n\t\t\t// A second `undefined` sends it on one more time, so `logError` warns twice for\n\t\t\t// one incident and throws again, and that throw leaves `flushJobs`. The\n\t\t\t// `finally` of `flushJobs` runs `queue.length = 0`. `flushJobs` therefore drops\n\t\t\t// every job behind the one that failed, and a sibling of the descendant that\n\t\t\t// threw renders never. One incident reaches the caller ONE time, and the flush\n\t\t\t// of Vue finishes.\n\t\t\t// `takeRepeat` runs for EVERY raise that fails the render, and not inside the\n\t\t\t// condition below: a raise of another source ENDS the window of the repeat, so\n\t\t\t// the record can disarm the boundary for the one raise that follows it and for\n\t\t\t// no other.\n\t\t\tconst repeated = takeRepeat(err, instance);\n\t\t\tif (isComponentUpdate(info) && repeated) {\n\t\t\t\t// The value reaches no console without this line. `logError` of the\n\t\t\t\t// development bundle writes the SOURCE, and not the value. It then throws\n\t\t\t\t// the value, and that throw IS this second report, which the `return`\n\t\t\t\t// below stops. `report()` wrote such an error before this gate existed, so\n\t\t\t\t// a host with no `onError` prop could still debug a watcher or a loader\n\t\t\t\t// that failed. The production bundle writes the value on the FIRST raise,\n\t\t\t\t// and it throws instead where the application asks for\n\t\t\t\t// `throwUnhandledErrorInProduction` — that application gets the write here.\n\t\t\t\t// One incident reaches the console ONE time in both bundles.\n\t\t\t\t//\n\t\t\t\t// The line carries the name of this package, as every other console write\n\t\t\t\t// of this provider does. Without it a developer reads a bare error and\n\t\t\t\t// cannot tell that the provider wrote it, and not Vue.\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[@xmachines/play-vue] ActorProvider stopped a second report of this \" +\n\t\t\t\t\t\t\"error. Vue raised it again under the source of a component update, \" +\n\t\t\t\t\t\t\"and it failed no render.\",\n\t\t\t\t\terr,\n\t\t\t\t);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// The SCREEN comes first, and the report second, as `contain()` of play-dom\n\t\t\t// states it. A reporter can throw — one that re-throws to escalate, or a\n\t\t\t// transport that fails — and here the order alone is not enough: the flag\n\t\t\t// schedules a RENDER, and a throw that leaves this hook stops that render and\n\t\t\t// skips the `return false`. The GUARD holds that half, for the four renderers\n\t\t\t// that need it: it carries a `handlerThrew` message, so it contains the throw\n\t\t\t// of the handler and writes it to `console.error`.\n\t\t\t//\n\t\t\t// The five renderers hold the one rule: a handler of the host never defeats the\n\t\t\t// containment, and a host that must escalate raises the failure from a task of\n\t\t\t// its own.\n\t\t\terrored.value = true;\n\t\t\treport(err);\n\t\t\treturn false;\n\t\t});\n\n\t\t// Take the actor out of the reactive proxy of Vue, to reach the raw Signal objects.\n\t\t// A shallowRef holds the actor. Therefore the injected value and the signal watcher\n\t\t// can follow `props.actor` when the prop changes after the mount.\n\t\tconst actorRef = shallowRef(toRaw(props.actor as AbstractActor<AnyActorLogic> & Viewable));\n\n\t\t// Take the registryResult out of the proxy, and mark each component as raw, because the reactivity of Vue costs too much here\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// Put the onRenderError prop into the registry. The property is not enumerable, and\n\t\t// it replaces the handler of the defineRegistry level. attachRenderErrorHandler makes\n\t\t// a copy, and it changes the registry of the caller never.\n\t\t// The code must call markRaw on the copy again, because a copy does not carry the\n\t\t// non-enumerable flag of markRaw.\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// Give the actor to each descendant, through the provide and inject mechanism of Vue.\n\t\t//\n\t\t// The code gives a stable Proxy, and not the raw actor. The pattern is the same as\n\t\t// the ViewKey proxy of ActorProviderInner. The injected value therefore follows the\n\t\t// CURRENT `props.actor`: each injector keeps the reference of the setup(), and every\n\t\t// trap resolves against the newest actor (actorRef.value) after a change of the prop.\n\t\t// The proxy forwards each relevant trap. Therefore it behaves like the actor below it\n\t\t// for a read, for a write, for a membership test (`in`), for an enumeration\n\t\t// (Object.keys and a spread), and for a prototype test (`instanceof`). Each method\n\t\t// binds to the current raw actor. Therefore `this`, and also each private field,\n\t\t// works exactly as in a direct call.\n\t\t//\n\t\t// IMPORTANT: this proxy is NOT identity-equal to `props.actor`, because it is a\n\t\t// separate object, and therefore a separate key of a WeakMap. A consumer that uses\n\t\t// the identity as its key must receive the `props.actor` value itself, and never this\n\t\t// injected proxy. The most important such consumer is the guard of one bridge for\n\t\t// each actor in RouterBridgeBase: it uses the identity of the actor as the key of a\n\t\t// WeakMap at the module level.\n\t\t//\n\t\t// The identity of a method: the code keeps each bound method, with the method below\n\t\t// it as the key. A repeated read therefore returns the SAME function\n\t\t// (actor.send === actor.send), and the hot path allocates nothing. A read binds the\n\t\t// method again only after a change of the method below it. The watch handler below\n\t\t// CLEARS the cache on a change of the actor. Therefore each prototype method binds to\n\t\t// 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// An invariant of a Proxy: the target of this proxy is an empty {}. Therefore each\n\t\t\t\t\t// key of ownKeys must resolve to a configurable descriptor here. Without that, the\n\t\t\t\t\t// [[GetOwnProperty]] invariant throws, because a proxy must not report a property that\n\t\t\t\t\t// its target has not as non-configurable. The own props of the actor are mutable at\n\t\t\t\t\t// run time in each case. Therefore `configurable: true` is correct enough for the\n\t\t\t\t\t// enumeration of Object.keys and of a spread.\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 the first value, then subscribe. Both steps are synchronous, and no scheduler\n\t\t// runs between them.\n\t\t// Solid does the same with createEffect, and Svelte does the same with $effect.\n\t\t// The pattern is therefore the same in each framework.\n\t\tconst view = ref<PlaySpec | null>(actorRef.value.currentView.get());\n\n\t\t// The store lifecycle: it seeds the store again on a change of the viewKey, it\n\t\t// refreshes /context in place in every other case, it resets the store on a change of\n\t\t// the actor, and it guards the identity cache. The shared coordinator comes from\n\t\t// @xmachines/play-actor. Only the wiring of the reactivity below belongs to Vue.\n\t\tconst storeLifecycle = createViewStoreLifecycle((seed) =>\n\t\t\txstateStoreStateStore({ atom: createAtom(seed) }),\n\t\t);\n\t\t// storeKey is the key of a SUBTREE REMOUNT. Therefore it must change only after a\n\t\t// new seed of the store, and never after a refresh of /context in place. Without this\n\t\t// rule, each update of the projection removes the subtree on the screen.\n\t\t// (The controlled mode seeds the store never again. Therefore the key changes never\n\t\t// there.)\n\t\tlet storeKey = 0;\n\n\t\t// The signal watcher connects the TC39 Signals to the reactivity of Vue.\n\t\t// The code assigns it again on a change of the actor. Therefore the cleanup always\n\t\t// releases the live watcher.\n\t\t// Every view emission clears the errored flag (see the reset rule above) before the\n\t\t// new view renders. A hit of the error boundary therefore retries here.\n\t\tlet unwatch = watchSignal(actorRef.value.currentView, (nextView) => {\n\t\t\terrored.value = false;\n\t\t\tfailureLatch.clear();\n\t\t\tview.value = nextView;\n\t\t});\n\n\t\t// The reset watches the STORE too. A render fails on it, so a caller that repairs a\n\t\t// controlled store — without a new emission — must see the children again.\n\t\t// play-react and play-solid hold the same rule, and the actor swap below covers the\n\t\t// third input.\n\t\twatch(\n\t\t\t() => props.store,\n\t\t\t() => {\n\t\t\t\terrored.value = false;\n\t\t\t\tfailureLatch.clear();\n\t\t\t},\n\t\t);\n\n\t\t// React to a change of `props.actor`: cancel the subscription to the currentView of\n\t\t// the OLD actor, seed the view from the NEW actor synchronously (`flush: \"sync\"`, so\n\t\t// that no scheduler runs between the steps, as in the seed and watch pattern of the\n\t\t// mount above), and subscribe to the new signal. The view of the new seed has a new\n\t\t// identity. Therefore the branch of the internal store below builds the store again,\n\t\t// and it changes storeKey, exactly like a normal 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// (The store lifecycle resets itself on a change of the actor, because a store that\n\t\t\t\t// stays alive must not outlive its actor.)\n\t\t\t\t// Clear the cache of the bound methods. Each prototype method therefore binds to the\n\t\t\t\t// new actor on the next read. See the get trap of the injected proxy.\n\t\t\t\tboundMethodCache.clear();\n\t\t\t\t// The new seed is a view emission — clear the errored flag with it.\n\t\t\t\terrored.value = false;\n\t\t\t\tfailureLatch.clear();\n\t\t\t\tview.value = nextActor.currentView.get();\n\t\t\t\tunwatch = watchSignal(nextActor.currentView, (nextView) => {\n\t\t\t\t\terrored.value = false;\n\t\t\t\t\tfailureLatch.clear();\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\tguard.dispose();\n\t\t\tunwatch();\n\t\t});\n\n\t\treturn () => {\n\t\t\t// The nonce of the retry. The read makes this render function depend on it, so\n\t\t\t// the reset of the host renders again — see `retry` above.\n\t\t\tvoid retryNonce.value;\n\n\t\t\t// A contained error of a descendant: show the fallback slot, or show nothing,\n\t\t\t// until the next view emission clears the flag and retries the render.\n\t\t\tif (errored.value) {\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\n\t\t\t// No view: show the fallback slot, or show 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, which is external and controlled, or internal for each\n\t\t\t// viewKey, through the shared lifecycle. The children receive the store with the\n\t\t\t// guard, because /context is read-only to the spec. The machinery refreshes the\n\t\t\t// store through the reference without a guard, inside the coordinator.\n\t\t\t//\n\t\t\t// The try CONTAINS a failure of this resolution. `spec.state` can throw — a\n\t\t\t// projection with an accessor, or a seed of a store — and `onErrorCaptured`\n\t\t\t// above sees a throw of a DESCENDANT only, never one of this render function.\n\t\t\t// Without the try such a failure leaves the provider, reaches\n\t\t\t// app.config.errorHandler, and takes the application down, while `onError` and\n\t\t\t// the fallback slot both stay unused.\n\t\t\t//\n\t\t\t// The branch sets the errored flag NOT: a mutation of the state during a render\n\t\t\t// starts a second render. It returns the fallback for THIS render instead, and\n\t\t\t// the next view emission renders the children again — which is the reset rule,\n\t\t\t// with no flag to clear.\n\t\t\tlet resolution;\n\t\t\ttry {\n\t\t\t\tresolution = storeLifecycle.resolve(actorRef.value, spec, props.store);\n\t\t\t} catch (error) {\n\t\t\t\t// LATCH the failure, and report it ONE time. The render function runs again\n\t\t\t\t// for any tracked change, and a report on each attempt would call onError a\n\t\t\t\t// second and a third time for one failure — play-react, play-solid and\n\t\t\t\t// play-svelte each report once.\n\t\t\t\tif (failureLatch.shouldReport(actorRef.value, spec, props.store)) report(error);\n\t\t\t\treturn slots.fallback ? slots.fallback() : null;\n\t\t\t}\n\t\t\tfailureLatch.clear();\n\t\t\tif (resolution.reseeded) storeKey++;\n\t\t\tconst store: StateStore = resolution.guardedStore;\n\n\t\t\t// ActorProviderInner renders inside StateProvider. Therefore 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,IAAM,qBAAqB,gBAAgB;CAC1C,MAAM;CACN,OAAO;EACN,gBAAgB;GACf,MAAM;GACN,UAAU;EACX;EACA,MAAM;GACL,MAAM;GACN,SAAS;EACV;EACA,OAAO;GACN,MAAM;GACN,UAAU;EACX;CACD;CACA,MAAM,OAAO,EAAE,SAAS;EAIvB,MAAM,UAAU,WAAoC,IAAI;EAMxD,QACC,SACA,IAAI,MAAM,CAAC,GAAuB,EACjC,IAAI,SAAS,MAAc;GAC1B,OAAO,QAAQ,QAAQ;EACxB,EACD,CAAC,CACF;EAKA,MAAM,aAAa,cAAc;EAOjC,MAAM,mBAA6B,YAAY;GAC9C,MAAM,OAAO,WAAW,YAAY;GACpC,WAAW,OAAO,QAAQ,IAAI,CAAC;EAChC;EAOA,MAAM,WAAW,eAChB,MAAM,eAAe,eACd,uBACA,WAAW,YAAY,CAC9B,CACD;EAEA,aAAa;GACZ,IAAI,CAAC,MAAM,MAAM;IAChB,QAAQ,QAAQ;IAChB,OAAO,MAAM,UAAU,KAAK;GAC7B;GAEA,QAAQ,QAAQ;IACf,MAAM,MAAM;IACZ,UAAU,SAAS;IACnB,UAAU,MAAM,eAAe;IAC/B,OAAO,MAAM;GACd;GAKA,OAAO,EAAE,gBAAgB,EAAE,UAAU,SAAS,MAAM,SACnD,EAAE,oBAAoB,CAAC,SAAS,MAAM,UAAU,KAAK,IAAI,CAC1D;EACD;CACD;AACD,CAAC;AAMD,IAAA,iDAAe,gBAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;EACX;EACA,gBAAgB;GACf,MAAM;GACN,UAAU;EACX;EACA,OAAO;GACN,MAAM;GACN,SAAS,KAAA;EACV;EACA,eAAe;GACd,MAAM;GACN,SAAS,KAAA;EACV;EACA,SAAS;GACR,MAAM;GACN,SAAS,KAAA;EACV;CACD;CACA,MAAM,OAAO,EAAE,SAAS;EAgCvB,MAAM,UAAU,IAAI,KAAK;;;;;;;;;EASzB,MAAM,eAAe,mBAAmB;EAcxC,MAAM,QAAQ,kBAAkB;GAC/B,WACC;GAED,cACC;EAEF,CAAC;EAKD,MAAM,aAAa,IAAI,CAAC;;;;;;;;EASxB,MAAM,cAAoB;GACzB,IAAI,MAAM,QAAQ,GAAG;GACrB,QAAQ,QAAQ;GAChB,aAAa,MAAM;GACnB,WAAW;EACZ;;EAGA,MAAM,UAAU,QAAuB;GACtC,MAAM,UAAU,MAAM;GACtB,MAAM,OAAO,KAAK,gBAAgB,QAAQ,KAAK,KAAK,IAAI,KAAA,CAAS;EAClE;EAKA,MAAM,YAAY,mBAAmB,CAAC,EAAE,WAAW;EAgCnD,IAAI,aAAkF;EACtF,MAAM,YAAY,OAAgB,aAAmD;GACpF,MAAM,SAAS;IAAE;IAAO;GAAS;GACjC,aAAa;GACb,qBAAqB;IACpB,IAAI,eAAe,QAAQ,aAAa;GACzC,CAAC;EACF;;;;;;;;;;;;EAYA,MAAM,cAAc,OAAgB,aAAsD;GACzF,MAAM,SAAS;GACf,aAAa;GACb,IAAI,WAAW,QAAQ,OAAO,UAAU,OAAO,OAAO;GACtD,KAAK,IAAI,OAAO,OAAO,UAAU,SAAS,MAAM,OAAO,KAAK,SAC3D,IAAI,KAAK,MAAM,UAAU,GAAG,OAAO;GAEpC,OAAO;EACR;EAEA,iBAAiB,KAAK,UAAU,SAAS;GAQxC,IAAI,CAAC,eAAe,IAAI,GAAG;IAY1B,IAAI,cAAc,MAAM,SAAS,GAAG,SAAS,KAAK,QAAQ;IAC1D;GACD;GAgCA,MAAM,WAAW,WAAW,KAAK,QAAQ;GACzC,IAAI,kBAAkB,IAAI,KAAK,UAAU;IAcxC,QAAQ,MACP,mKAGA,GACD;IACA,OAAO;GACR;GAaA,QAAQ,QAAQ;GAChB,OAAO,GAAG;GACV,OAAO;EACR,CAAC;EAKD,MAAM,WAAW,WAAW,MAAM,MAAM,KAAgD,CAAC;EAGzF,MAAM,cAAc,OAAO,YAC1B,OAAO,QAAQ,MAAM,MAAM,cAAc,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CACpE,GACA,QAAQ,CAAW,CACpB,CAAC,CACF;EAOA,MAAM,oBAA0C;GAC/C,GAAG,MAAM,MAAM,cAAc;GAC7B,UAAU,QACT,MAAM,gBACH,yBAAyB,aAAa,MAAM,aAAa,IACzD,WACJ;EACD;EA2BA,MAAM,mCAAmB,IAAI,IAAsD;EACnF,aACC,IAAI,MAAM,CAAC,GAAmB;GAC7B,IAAI,SAAS,MAAM;IAClB,MAAM,UAAU,SAAS;IACzB,MAAM,QAAQ,QAAQ,IAAI,SAAS,MAAM,OAAO;IAChD,IAAI,OAAO,UAAU,YAAY,OAAO;IACxC,MAAM,SAAS,iBAAiB,IAAI,IAAI;IACxC,IAAI,UAAU,OAAO,WAAW,OAAO,OAAO,OAAO;IACrD,MAAM,QAAS,MAA0C,KAAK,OAAO;IACrE,iBAAiB,IAAI,MAAM;KAAE,QAAQ;KAAO;IAAM,CAAC;IACnD,OAAO;GACR;GACA,IAAI,SAAS,MAAM;IAClB,OAAO,QAAQ,SAAS;GACzB;GACA,IAAI,SAAS,MAAM,OAAO;IACzB,OAAO,QAAQ,IAAI,SAAS,OAAO,MAAM,OAAO,SAAS,KAAK;GAC/D;GACA,eAAe,SAAS,MAAM;IAC7B,OAAO,QAAQ,eAAe,SAAS,OAAO,IAAI;GACnD;GACA,UAAU;IACT,OAAO,QAAQ,QAAQ,SAAS,KAAK;GACtC;GACA,yBAAyB,SAAS,MAAM;IACvC,MAAM,OAAO,QAAQ,yBAAyB,SAAS,OAAO,IAAI;IAClE,IAAI,CAAC,MAAM,OAAO,KAAA;IAOlB,KAAK,eAAe;IACpB,OAAO;GACR;GACA,iBAAiB;IAChB,OAAO,QAAQ,eAAe,SAAS,KAAK;GAC7C;EACD,CAAC,CACF;EAMA,MAAM,OAAO,IAAqB,SAAS,MAAM,YAAY,IAAI,CAAC;EAMlE,MAAM,iBAAiB,0BAA0B,SAChD,sBAAsB,EAAE,MAAM,WAAW,IAAI,EAAE,CAAC,CACjD;EAMA,IAAI,WAAW;EAOf,IAAI,UAAU,YAAY,SAAS,MAAM,cAAc,aAAa;GACnE,QAAQ,QAAQ;GAChB,aAAa,MAAM;GACnB,KAAK,QAAQ;EACd,CAAC;EAMD,YACO,MAAM,aACN;GACL,QAAQ,QAAQ;GAChB,aAAa,MAAM;EACpB,CACD;EAQA,YACO,MAAM,MAAM,KAAgD,IACjE,cAAc;GACd,IAAI,cAAc,SAAS,OAAO;GAClC,QAAQ;GACR,SAAS,QAAQ;GAKjB,iBAAiB,MAAM;GAEvB,QAAQ,QAAQ;GAChB,aAAa,MAAM;GACnB,KAAK,QAAQ,UAAU,YAAY,IAAI;GACvC,UAAU,YAAY,UAAU,cAAc,aAAa;IAC1D,QAAQ,QAAQ;IAChB,aAAa,MAAM;IACnB,KAAK,QAAQ;GACd,CAAC;EACF,GACA,EAAE,OAAO,OAAO,CACjB;EAEA,kBAAkB;GACjB,MAAM,QAAQ;GACd,QAAQ;EACT,CAAC;EAED,aAAa;GAGZ,WAAgB;GAIhB,IAAI,QAAQ,OACX,OAAO,MAAM,WAAW,MAAM,SAAS,IAAI;GAI5C,IAAI,CAAC,KAAK,OACT,OAAO,MAAM,WAAW,MAAM,SAAS,IAAI;GAG5C,MAAM,OAAO,KAAK;GAkBlB,IAAI;GACJ,IAAI;IACH,aAAa,eAAe,QAAQ,SAAS,OAAO,MAAM,MAAM,KAAK;GACtE,SAAS,OAAO;IAKf,IAAI,aAAa,aAAa,SAAS,OAAO,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK;IAC9E,OAAO,MAAM,WAAW,MAAM,SAAS,IAAI;GAC5C;GACA,aAAa,MAAM;GACnB,IAAI,WAAW,UAAU;GACzB,MAAM,QAAoB,WAAW;GAGrC,OAAO,EAAE,eAAe;IAAE;IAAO,KAAK;GAAS,SAC9C,EAAE,oBAAoB;IAAE,gBAAgB;IAAmB;IAAM;GAAM,GAAG,KAAK,CAChF;EACD;CACD;AACD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayUIProvider.js","names":[],"sources":["../src/PlayUIProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayUIProvider — the composite provider of an actor-driven UI.\n *\n * It wraps ActorProvider and JSONUIProvider, from @xmachines/json-render-vue. One\n * component therefore serves the complete actor lifecycle and the complete UI\n * rendering.\n *\n * The standard use:\n * ```ts\n * <PlayUIProvider :actor=\"actor\" :registryResult=\"registryResult\">\n * <PlayRenderer />\n * </PlayUIProvider>\n * ```\n *\n * For a custom provider composition, use <ActorProvider> directly.\n *\n * @packageDocumentation\n */\n\nimport { defineComponent, h } from \"vue\";\nimport type { PropType } from \"vue\";\nimport { JSONUIProvider } from \"@xmachines/json-render-vue\";\nimport type { DefineRegistryResult, RenderErrorHandler } from \"@xmachines/json-render-vue\";\n\nimport type { StateStore } from \"@xmachines/json-render-core\";\nimport type { AbstractActor, Viewable } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport ActorProvider from \"./ActorProvider.vue\";\nimport { usePlayView } from \"./actor-provider-context.js\";\nimport type { PlayUIProviderProps } from \"./types.js\";\n\n// Re-export the props type, for a consumer that imports from this file\nexport type { PlayUIProviderProps } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSONUIBridge — the inner component inside the tree of ActorProvider\n// It reads the ViewContextValue with usePlayView(), and it gives the handlers\n// and the registry to JSONUIProvider. Therefore ActorProvider and JSONUIProvider\n// share one state.\n// ---------------------------------------------------------------------------\n\nconst JSONUIBridge = defineComponent({\n\tname: \"JSONUIBridge\",\n\tprops: {\n\t\tnavigate: {\n\t\t\ttype: Function as PropType<(path: string) => void>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tvalidationFunctions: {\n\t\t\ttype: Object as PropType<\n\t\t\t\tRecord<string, (value: unknown, args?: Record<string, unknown>) => boolean>\n\t\t\t>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfunctions: {\n\t\t\ttype: Object as PropType<\n\t\t\t\tRecord<\n\t\t\t\t\tstring,\n\t\t\t\t\t(args?: Record<string, unknown>, state?: Record<string, unknown>) => unknown\n\t\t\t\t>\n\t\t\t>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\treturn () => {\n\t\t\tconst view = usePlayView();\n\t\t\treturn h(\n\t\t\t\tJSONUIProvider,\n\t\t\t\t{\n\t\t\t\t\tregistry: view.registry,\n\t\t\t\t\thandlers: view.handlers,\n\t\t\t\t\tstore: view.store,\n\t\t\t\t\t...(props.navigate ? { navigate: props.navigate } : {}),\n\t\t\t\t\t...(props.validationFunctions\n\t\t\t\t\t\t? { validationFunctions: props.validationFunctions }\n\t\t\t\t\t\t: {}),\n\t\t\t\t\t...(props.functions ? { functions: props.functions } : {}),\n\t\t\t\t},\n\t\t\t\tslots,\n\t\t\t);\n\t\t};\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// PlayUIProvider — the main export\n// ---------------------------------------------------------------------------\n\nexport default defineComponent({\n\tname: \"PlayUIProvider\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\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\tnavigate: {\n\t\t\ttype: Function as PropType<PlayUIProviderProps[\"navigate\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tvalidationFunctions: {\n\t\t\ttype: Object as PropType<PlayUIProviderProps[\"validationFunctions\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfunctions: {\n\t\t\ttype: Object as PropType<PlayUIProviderProps[\"functions\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\treturn () =>\n\t\t\th(\n\t\t\t\tActorProvider,\n\t\t\t\t{\n\t\t\t\t\tactor: props.actor,\n\t\t\t\t\tregistryResult: props.registryResult,\n\t\t\t\t\t...(props.store ? { store: props.store } : {}),\n\t\t\t\t\t...(props.onRenderError ? { onRenderError: props.onRenderError } : {}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdefault: () =>\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\tJSONUIBridge,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...(props.navigate ? { navigate: props.navigate } : {}),\n\t\t\t\t\t\t\t\t...(props.validationFunctions\n\t\t\t\t\t\t\t\t\t? { validationFunctions: props.validationFunctions }\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t\t...(props.functions ? { functions: props.functions } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tslots,\n\t\t\t\t\t\t),\n\t\t\t\t\tfallback: slots.fallback,\n\t\t\t\t},\n\t\t\t);\n\t},\n});\n</script>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"PlayUIProvider.js","names":[],"sources":["../src/PlayUIProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayUIProvider — the composite provider of an actor-driven UI.\n *\n * It wraps ActorProvider and JSONUIProvider, from @xmachines/json-render-vue. One\n * component therefore serves the complete actor lifecycle and the complete UI\n * rendering.\n *\n * The standard use:\n * ```ts\n * <PlayUIProvider :actor=\"actor\" :registryResult=\"registryResult\">\n * <PlayRenderer />\n * </PlayUIProvider>\n * ```\n *\n * For a custom provider composition, use <ActorProvider> directly.\n *\n * @packageDocumentation\n */\n\nimport { defineComponent, h } from \"vue\";\nimport type { PropType } from \"vue\";\nimport { JSONUIProvider } from \"@xmachines/json-render-vue\";\nimport type { DefineRegistryResult, RenderErrorHandler } from \"@xmachines/json-render-vue\";\n\nimport type { StateStore } from \"@xmachines/json-render-core\";\nimport type { AbstractActor, Viewable } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport ActorProvider from \"./ActorProvider.vue\";\nimport { usePlayView } from \"./actor-provider-context.js\";\nimport type { PlayUIProviderProps } from \"./types.js\";\n\n// Re-export the props type, for a consumer that imports from this file\nexport type { PlayUIProviderProps } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSONUIBridge — the inner component inside the tree of ActorProvider\n// It reads the ViewContextValue with usePlayView(), and it gives the handlers\n// and the registry to JSONUIProvider. Therefore ActorProvider and JSONUIProvider\n// share one state.\n// ---------------------------------------------------------------------------\n\nconst JSONUIBridge = defineComponent({\n\tname: \"JSONUIBridge\",\n\tprops: {\n\t\tnavigate: {\n\t\t\ttype: Function as PropType<(path: string) => void>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tvalidationFunctions: {\n\t\t\ttype: Object as PropType<\n\t\t\t\tRecord<string, (value: unknown, args?: Record<string, unknown>) => boolean>\n\t\t\t>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfunctions: {\n\t\t\ttype: Object as PropType<\n\t\t\t\tRecord<\n\t\t\t\t\tstring,\n\t\t\t\t\t(args?: Record<string, unknown>, state?: Record<string, unknown>) => unknown\n\t\t\t\t>\n\t\t\t>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\treturn () => {\n\t\t\tconst view = usePlayView();\n\t\t\treturn h(\n\t\t\t\tJSONUIProvider,\n\t\t\t\t{\n\t\t\t\t\tregistry: view.registry,\n\t\t\t\t\thandlers: view.handlers,\n\t\t\t\t\tstore: view.store,\n\t\t\t\t\t...(props.navigate ? { navigate: props.navigate } : {}),\n\t\t\t\t\t...(props.validationFunctions\n\t\t\t\t\t\t? { validationFunctions: props.validationFunctions }\n\t\t\t\t\t\t: {}),\n\t\t\t\t\t...(props.functions ? { functions: props.functions } : {}),\n\t\t\t\t},\n\t\t\t\tslots,\n\t\t\t);\n\t\t};\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// PlayUIProvider — the main export\n// ---------------------------------------------------------------------------\n\nexport default defineComponent({\n\tname: \"PlayUIProvider\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\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\tonError: {\n\t\t\ttype: Function as PropType<(error: unknown, reset: () => void) => void>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tnavigate: {\n\t\t\ttype: Function as PropType<PlayUIProviderProps[\"navigate\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tvalidationFunctions: {\n\t\t\ttype: Object as PropType<PlayUIProviderProps[\"validationFunctions\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfunctions: {\n\t\t\ttype: Object as PropType<PlayUIProviderProps[\"functions\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\treturn () =>\n\t\t\th(\n\t\t\t\tActorProvider,\n\t\t\t\t{\n\t\t\t\t\tactor: props.actor,\n\t\t\t\t\tregistryResult: props.registryResult,\n\t\t\t\t\t...(props.store ? { store: props.store } : {}),\n\t\t\t\t\t...(props.onRenderError ? { onRenderError: props.onRenderError } : {}),\n\t\t\t\t\t...(props.onError ? { onError: props.onError } : {}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdefault: () =>\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\tJSONUIBridge,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...(props.navigate ? { navigate: props.navigate } : {}),\n\t\t\t\t\t\t\t\t...(props.validationFunctions\n\t\t\t\t\t\t\t\t\t? { validationFunctions: props.validationFunctions }\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t\t...(props.functions ? { functions: props.functions } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tslots,\n\t\t\t\t\t\t),\n\t\t\t\t\tfallback: slots.fallback,\n\t\t\t\t},\n\t\t\t);\n\t},\n});\n</script>\n"],"mappings":""}
|
|
@@ -70,6 +70,10 @@ var PlayUIProvider_vue_vue_type_script_lang_default = defineComponent({
|
|
|
70
70
|
type: Function,
|
|
71
71
|
default: void 0
|
|
72
72
|
},
|
|
73
|
+
onError: {
|
|
74
|
+
type: Function,
|
|
75
|
+
default: void 0
|
|
76
|
+
},
|
|
73
77
|
navigate: {
|
|
74
78
|
type: Function,
|
|
75
79
|
default: void 0
|
|
@@ -88,7 +92,8 @@ var PlayUIProvider_vue_vue_type_script_lang_default = defineComponent({
|
|
|
88
92
|
actor: props.actor,
|
|
89
93
|
registryResult: props.registryResult,
|
|
90
94
|
...props.store ? { store: props.store } : {},
|
|
91
|
-
...props.onRenderError ? { onRenderError: props.onRenderError } : {}
|
|
95
|
+
...props.onRenderError ? { onRenderError: props.onRenderError } : {},
|
|
96
|
+
...props.onError ? { onError: props.onError } : {}
|
|
92
97
|
}, {
|
|
93
98
|
default: () => h(JSONUIBridge, {
|
|
94
99
|
...props.navigate ? { navigate: props.navigate } : {},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlayUIProvider.vue_vue_type_script_lang.js","names":[],"sources":["../src/PlayUIProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayUIProvider — the composite provider of an actor-driven UI.\n *\n * It wraps ActorProvider and JSONUIProvider, from @xmachines/json-render-vue. One\n * component therefore serves the complete actor lifecycle and the complete UI\n * rendering.\n *\n * The standard use:\n * ```ts\n * <PlayUIProvider :actor=\"actor\" :registryResult=\"registryResult\">\n * <PlayRenderer />\n * </PlayUIProvider>\n * ```\n *\n * For a custom provider composition, use <ActorProvider> directly.\n *\n * @packageDocumentation\n */\n\nimport { defineComponent, h } from \"vue\";\nimport type { PropType } from \"vue\";\nimport { JSONUIProvider } from \"@xmachines/json-render-vue\";\nimport type { DefineRegistryResult, RenderErrorHandler } from \"@xmachines/json-render-vue\";\n\nimport type { StateStore } from \"@xmachines/json-render-core\";\nimport type { AbstractActor, Viewable } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport ActorProvider from \"./ActorProvider.vue\";\nimport { usePlayView } from \"./actor-provider-context.js\";\nimport type { PlayUIProviderProps } from \"./types.js\";\n\n// Re-export the props type, for a consumer that imports from this file\nexport type { PlayUIProviderProps } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSONUIBridge — the inner component inside the tree of ActorProvider\n// It reads the ViewContextValue with usePlayView(), and it gives the handlers\n// and the registry to JSONUIProvider. Therefore ActorProvider and JSONUIProvider\n// share one state.\n// ---------------------------------------------------------------------------\n\nconst JSONUIBridge = defineComponent({\n\tname: \"JSONUIBridge\",\n\tprops: {\n\t\tnavigate: {\n\t\t\ttype: Function as PropType<(path: string) => void>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tvalidationFunctions: {\n\t\t\ttype: Object as PropType<\n\t\t\t\tRecord<string, (value: unknown, args?: Record<string, unknown>) => boolean>\n\t\t\t>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfunctions: {\n\t\t\ttype: Object as PropType<\n\t\t\t\tRecord<\n\t\t\t\t\tstring,\n\t\t\t\t\t(args?: Record<string, unknown>, state?: Record<string, unknown>) => unknown\n\t\t\t\t>\n\t\t\t>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\treturn () => {\n\t\t\tconst view = usePlayView();\n\t\t\treturn h(\n\t\t\t\tJSONUIProvider,\n\t\t\t\t{\n\t\t\t\t\tregistry: view.registry,\n\t\t\t\t\thandlers: view.handlers,\n\t\t\t\t\tstore: view.store,\n\t\t\t\t\t...(props.navigate ? { navigate: props.navigate } : {}),\n\t\t\t\t\t...(props.validationFunctions\n\t\t\t\t\t\t? { validationFunctions: props.validationFunctions }\n\t\t\t\t\t\t: {}),\n\t\t\t\t\t...(props.functions ? { functions: props.functions } : {}),\n\t\t\t\t},\n\t\t\t\tslots,\n\t\t\t);\n\t\t};\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// PlayUIProvider — the main export\n// ---------------------------------------------------------------------------\n\nexport default defineComponent({\n\tname: \"PlayUIProvider\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\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\tnavigate: {\n\t\t\ttype: Function as PropType<PlayUIProviderProps[\"navigate\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tvalidationFunctions: {\n\t\t\ttype: Object as PropType<PlayUIProviderProps[\"validationFunctions\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfunctions: {\n\t\t\ttype: Object as PropType<PlayUIProviderProps[\"functions\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\treturn () =>\n\t\t\th(\n\t\t\t\tActorProvider,\n\t\t\t\t{\n\t\t\t\t\tactor: props.actor,\n\t\t\t\t\tregistryResult: props.registryResult,\n\t\t\t\t\t...(props.store ? { store: props.store } : {}),\n\t\t\t\t\t...(props.onRenderError ? { onRenderError: props.onRenderError } : {}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdefault: () =>\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\tJSONUIBridge,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...(props.navigate ? { navigate: props.navigate } : {}),\n\t\t\t\t\t\t\t\t...(props.validationFunctions\n\t\t\t\t\t\t\t\t\t? { validationFunctions: props.validationFunctions }\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t\t...(props.functions ? { functions: props.functions } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tslots,\n\t\t\t\t\t\t),\n\t\t\t\t\tfallback: slots.fallback,\n\t\t\t\t},\n\t\t\t);\n\t},\n});\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAM,eAAe,gBAAgB;CACpC,MAAM;CACN,OAAO;EACN,UAAU;GACT,MAAM;GACN,SAAS,KAAA;EACV;EACA,qBAAqB;GACpB,MAAM;GAGN,SAAS,KAAA;EACV;EACA,WAAW;GACV,MAAM;GAMN,SAAS,KAAA;EACV;CACD;CACA,MAAM,OAAO,EAAE,SAAS;EACvB,aAAa;GACZ,MAAM,OAAO,YAAY;GACzB,OAAO,EACN,gBACA;IACC,UAAU,KAAK;IACf,UAAU,KAAK;IACf,OAAO,KAAK;IACZ,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;IACrD,GAAI,MAAM,sBACP,EAAE,qBAAqB,MAAM,oBAAoB,IACjD,CAAC;IACJ,GAAI,MAAM,YAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;GACzD,GACA,KACD;EACD;CACD;AACD,CAAC;AAMD,IAAA,kDAAe,gBAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;EACX;EACA,gBAAgB;GACf,MAAM;GACN,UAAU;EACX;EACA,OAAO;GACN,MAAM;GACN,SAAS,KAAA;EACV;EACA,eAAe;GACd,MAAM;GACN,SAAS,KAAA;EACV;EACA,UAAU;GACT,MAAM;GACN,SAAS,KAAA;EACV;EACA,qBAAqB;GACpB,MAAM;GACN,SAAS,KAAA;EACV;EACA,WAAW;GACV,MAAM;GACN,SAAS,KAAA;EACV;CACD;CACA,MAAM,OAAO,EAAE,SAAS;EACvB,aACC,EACC,uBACA;GACC,OAAO,MAAM;GACb,gBAAgB,MAAM;GACtB,GAAI,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;GAC5C,GAAI,MAAM,gBAAgB,EAAE,eAAe,MAAM,cAAc,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"PlayUIProvider.vue_vue_type_script_lang.js","names":[],"sources":["../src/PlayUIProvider.vue"],"sourcesContent":["<script lang=\"ts\">\n/**\n * PlayUIProvider — the composite provider of an actor-driven UI.\n *\n * It wraps ActorProvider and JSONUIProvider, from @xmachines/json-render-vue. One\n * component therefore serves the complete actor lifecycle and the complete UI\n * rendering.\n *\n * The standard use:\n * ```ts\n * <PlayUIProvider :actor=\"actor\" :registryResult=\"registryResult\">\n * <PlayRenderer />\n * </PlayUIProvider>\n * ```\n *\n * For a custom provider composition, use <ActorProvider> directly.\n *\n * @packageDocumentation\n */\n\nimport { defineComponent, h } from \"vue\";\nimport type { PropType } from \"vue\";\nimport { JSONUIProvider } from \"@xmachines/json-render-vue\";\nimport type { DefineRegistryResult, RenderErrorHandler } from \"@xmachines/json-render-vue\";\n\nimport type { StateStore } from \"@xmachines/json-render-core\";\nimport type { AbstractActor, Viewable } from \"@xmachines/play-actor\";\nimport type { AnyActorLogic } from \"xstate\";\nimport ActorProvider from \"./ActorProvider.vue\";\nimport { usePlayView } from \"./actor-provider-context.js\";\nimport type { PlayUIProviderProps } from \"./types.js\";\n\n// Re-export the props type, for a consumer that imports from this file\nexport type { PlayUIProviderProps } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// JSONUIBridge — the inner component inside the tree of ActorProvider\n// It reads the ViewContextValue with usePlayView(), and it gives the handlers\n// and the registry to JSONUIProvider. Therefore ActorProvider and JSONUIProvider\n// share one state.\n// ---------------------------------------------------------------------------\n\nconst JSONUIBridge = defineComponent({\n\tname: \"JSONUIBridge\",\n\tprops: {\n\t\tnavigate: {\n\t\t\ttype: Function as PropType<(path: string) => void>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tvalidationFunctions: {\n\t\t\ttype: Object as PropType<\n\t\t\t\tRecord<string, (value: unknown, args?: Record<string, unknown>) => boolean>\n\t\t\t>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfunctions: {\n\t\t\ttype: Object as PropType<\n\t\t\t\tRecord<\n\t\t\t\t\tstring,\n\t\t\t\t\t(args?: Record<string, unknown>, state?: Record<string, unknown>) => unknown\n\t\t\t\t>\n\t\t\t>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\treturn () => {\n\t\t\tconst view = usePlayView();\n\t\t\treturn h(\n\t\t\t\tJSONUIProvider,\n\t\t\t\t{\n\t\t\t\t\tregistry: view.registry,\n\t\t\t\t\thandlers: view.handlers,\n\t\t\t\t\tstore: view.store,\n\t\t\t\t\t...(props.navigate ? { navigate: props.navigate } : {}),\n\t\t\t\t\t...(props.validationFunctions\n\t\t\t\t\t\t? { validationFunctions: props.validationFunctions }\n\t\t\t\t\t\t: {}),\n\t\t\t\t\t...(props.functions ? { functions: props.functions } : {}),\n\t\t\t\t},\n\t\t\t\tslots,\n\t\t\t);\n\t\t};\n\t},\n});\n\n// ---------------------------------------------------------------------------\n// PlayUIProvider — the main export\n// ---------------------------------------------------------------------------\n\nexport default defineComponent({\n\tname: \"PlayUIProvider\",\n\tprops: {\n\t\tactor: {\n\t\t\ttype: Object as PropType<AbstractActor<AnyActorLogic> & Viewable>,\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\tonError: {\n\t\t\ttype: Function as PropType<(error: unknown, reset: () => void) => void>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tnavigate: {\n\t\t\ttype: Function as PropType<PlayUIProviderProps[\"navigate\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tvalidationFunctions: {\n\t\t\ttype: Object as PropType<PlayUIProviderProps[\"validationFunctions\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t\tfunctions: {\n\t\t\ttype: Object as PropType<PlayUIProviderProps[\"functions\"]>,\n\t\t\tdefault: undefined,\n\t\t},\n\t},\n\tsetup(props, { slots }) {\n\t\treturn () =>\n\t\t\th(\n\t\t\t\tActorProvider,\n\t\t\t\t{\n\t\t\t\t\tactor: props.actor,\n\t\t\t\t\tregistryResult: props.registryResult,\n\t\t\t\t\t...(props.store ? { store: props.store } : {}),\n\t\t\t\t\t...(props.onRenderError ? { onRenderError: props.onRenderError } : {}),\n\t\t\t\t\t...(props.onError ? { onError: props.onError } : {}),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tdefault: () =>\n\t\t\t\t\t\th(\n\t\t\t\t\t\t\tJSONUIBridge,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t...(props.navigate ? { navigate: props.navigate } : {}),\n\t\t\t\t\t\t\t\t...(props.validationFunctions\n\t\t\t\t\t\t\t\t\t? { validationFunctions: props.validationFunctions }\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t\t...(props.functions ? { functions: props.functions } : {}),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tslots,\n\t\t\t\t\t\t),\n\t\t\t\t\tfallback: slots.fallback,\n\t\t\t\t},\n\t\t\t);\n\t},\n});\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAM,eAAe,gBAAgB;CACpC,MAAM;CACN,OAAO;EACN,UAAU;GACT,MAAM;GACN,SAAS,KAAA;EACV;EACA,qBAAqB;GACpB,MAAM;GAGN,SAAS,KAAA;EACV;EACA,WAAW;GACV,MAAM;GAMN,SAAS,KAAA;EACV;CACD;CACA,MAAM,OAAO,EAAE,SAAS;EACvB,aAAa;GACZ,MAAM,OAAO,YAAY;GACzB,OAAO,EACN,gBACA;IACC,UAAU,KAAK;IACf,UAAU,KAAK;IACf,OAAO,KAAK;IACZ,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;IACrD,GAAI,MAAM,sBACP,EAAE,qBAAqB,MAAM,oBAAoB,IACjD,CAAC;IACJ,GAAI,MAAM,YAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;GACzD,GACA,KACD;EACD;CACD;AACD,CAAC;AAMD,IAAA,kDAAe,gBAAgB;CAC9B,MAAM;CACN,OAAO;EACN,OAAO;GACN,MAAM;GACN,UAAU;EACX;EACA,gBAAgB;GACf,MAAM;GACN,UAAU;EACX;EACA,OAAO;GACN,MAAM;GACN,SAAS,KAAA;EACV;EACA,eAAe;GACd,MAAM;GACN,SAAS,KAAA;EACV;EACA,SAAS;GACR,MAAM;GACN,SAAS,KAAA;EACV;EACA,UAAU;GACT,MAAM;GACN,SAAS,KAAA;EACV;EACA,qBAAqB;GACpB,MAAM;GACN,SAAS,KAAA;EACV;EACA,WAAW;GACV,MAAM;GACN,SAAS,KAAA;EACV;CACD;CACA,MAAM,OAAO,EAAE,SAAS;EACvB,aACC,EACC,uBACA;GACC,OAAO,MAAM;GACb,gBAAgB,MAAM;GACtB,GAAI,MAAM,QAAQ,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;GAC5C,GAAI,MAAM,gBAAgB,EAAE,eAAe,MAAM,cAAc,IAAI,CAAC;GACpE,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;EACnD,GACA;GACC,eACC,EACC,cACA;IACC,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;IACrD,GAAI,MAAM,sBACP,EAAE,qBAAqB,MAAM,oBAAoB,IACjD,CAAC;IACJ,GAAI,MAAM,YAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;GACzD,GACA,KACD;GACD,UAAU,MAAM;EACjB,CACD;CACF;AACD,CAAC"}
|
|
@@ -37,6 +37,11 @@ export declare const ViewKey: InjectionKey<ViewContextValue>;
|
|
|
37
37
|
* const view = usePlayView();
|
|
38
38
|
* // view.spec, view.handlers, view.registry
|
|
39
39
|
* ```
|
|
40
|
+
*
|
|
41
|
+
* **Read a field where you use it, and destructure it not.** The return value is a Proxy
|
|
42
|
+
* over the live context of the provider, so `const { spec } = usePlayView()` freezes at
|
|
43
|
+
* the first read and the screen stops following the actor — with no error and no
|
|
44
|
+
* warning. play-svelte and play-solid hold the same rule, for the same reason.
|
|
40
45
|
*/
|
|
41
46
|
export declare function usePlayView(): ViewContextValue;
|
|
42
47
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actor-provider-context.d.ts","sourceRoot":"","sources":["../src/actor-provider-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;CAAG;AAEpF;;;;;GAKG;AACH,eAAO,MAAM,OAAO,EAAE,YAAY,CAAC,gBAAgB,CAA4B,CAAC;AAEhF
|
|
1
|
+
{"version":3,"file":"actor-provider-context.d.ts","sourceRoot":"","sources":["../src/actor-provider-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,KAAK,CAAC;AAExC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE;;;;GAIG;AACH,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB,CAAC,iBAAiB,CAAC;CAAG;AAEpF;;;;;GAKG;AACH,eAAO,MAAM,OAAO,EAAE,YAAY,CAAC,gBAAgB,CAA4B,CAAC;AAEhF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,IAAI,gBAAgB,CAE9C;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,oBAAc,CAAC"}
|
|
@@ -30,6 +30,11 @@ var ViewKey = Symbol("xmachines.view");
|
|
|
30
30
|
* const view = usePlayView();
|
|
31
31
|
* // view.spec, view.handlers, view.registry
|
|
32
32
|
* ```
|
|
33
|
+
*
|
|
34
|
+
* **Read a field where you use it, and destructure it not.** The return value is a Proxy
|
|
35
|
+
* over the live context of the provider, so `const { spec } = usePlayView()` freezes at
|
|
36
|
+
* the first read and the screen stops following the actor — with no error and no
|
|
37
|
+
* warning. play-svelte and play-solid hold the same rule, for the same reason.
|
|
33
38
|
*/
|
|
34
39
|
function usePlayView() {
|
|
35
40
|
return assertNonNullable(inject(ViewKey), "ViewKey");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actor-provider-context.js","names":[],"sources":["../src/actor-provider-context.ts"],"sourcesContent":["/**\n * ViewContextValue and usePlayView — the shared context types of the Vue provider architecture.\n *\n * This file holds them, and not ActorProvider.vue, so that TypeScript can\n * re-export the types from index.ts. (The file vue-shim.d.ts declares a default\n * export of each *.vue file only. Therefore TypeScript does not see a named export\n * of a .vue SFC when it resolves a re-export.)\n *\n * @packageDocumentation\n */\n\nimport { inject } from \"vue\";\nimport type { InjectionKey } from \"vue\";\nimport { assertNonNullable } from \"@xmachines/play\";\nimport type { ComponentRegistry } from \"@xmachines/json-render-vue\";\nimport type { BaseViewContextValue } from \"@xmachines/play-actor\";\n\n/**\n * The context value that ActorProvider provides and PlayRenderer, the leaf without\n * props, reads. usePlayView() gives it inside every ActorProvider tree and\n * PlayUIProvider tree.\n */\nexport interface ViewContextValue extends BaseViewContextValue<ComponentRegistry> {}\n\n/**\n * The injection key of the ViewContextValue. This file exports it, so that\n * ActorProvider.vue uses it as the canonical key and declares no second key.\n *\n * @internal Use usePlayView() as the public API. Never inject ViewKey directly.\n */\nexport const ViewKey: InjectionKey<ViewContextValue> = Symbol(\"xmachines.view\");\n\n/**\n * Returns the current ViewContextValue inside an ActorProvider tree.\n *\n * @throws {Error} When the caller is outside an <ActorProvider> tree or a <PlayUIProvider> tree\n *\n * @example\n * ```typescript\n * import { usePlayView } from \"@xmachines/play-vue\";\n *\n * const view = usePlayView();\n * // view.spec, view.handlers, view.registry\n * ```\n */\nexport function usePlayView(): ViewContextValue {\n\treturn assertNonNullable(inject(ViewKey), \"ViewKey\");\n}\n\n/**\n * Access the current ViewContextValue from inside an ActorProvider tree.\n *\n * @deprecated Renamed to {@link usePlayView}; will be removed in the next major.\n */\nexport const getPlayViewContext = usePlayView;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8BA,IAAa,UAA0C,OAAO,gBAAgB
|
|
1
|
+
{"version":3,"file":"actor-provider-context.js","names":[],"sources":["../src/actor-provider-context.ts"],"sourcesContent":["/**\n * ViewContextValue and usePlayView — the shared context types of the Vue provider architecture.\n *\n * This file holds them, and not ActorProvider.vue, so that TypeScript can\n * re-export the types from index.ts. (The file vue-shim.d.ts declares a default\n * export of each *.vue file only. Therefore TypeScript does not see a named export\n * of a .vue SFC when it resolves a re-export.)\n *\n * @packageDocumentation\n */\n\nimport { inject } from \"vue\";\nimport type { InjectionKey } from \"vue\";\nimport { assertNonNullable } from \"@xmachines/play\";\nimport type { ComponentRegistry } from \"@xmachines/json-render-vue\";\nimport type { BaseViewContextValue } from \"@xmachines/play-actor\";\n\n/**\n * The context value that ActorProvider provides and PlayRenderer, the leaf without\n * props, reads. usePlayView() gives it inside every ActorProvider tree and\n * PlayUIProvider tree.\n */\nexport interface ViewContextValue extends BaseViewContextValue<ComponentRegistry> {}\n\n/**\n * The injection key of the ViewContextValue. This file exports it, so that\n * ActorProvider.vue uses it as the canonical key and declares no second key.\n *\n * @internal Use usePlayView() as the public API. Never inject ViewKey directly.\n */\nexport const ViewKey: InjectionKey<ViewContextValue> = Symbol(\"xmachines.view\");\n\n/**\n * Returns the current ViewContextValue inside an ActorProvider tree.\n *\n * @throws {Error} When the caller is outside an <ActorProvider> tree or a <PlayUIProvider> tree\n *\n * @example\n * ```typescript\n * import { usePlayView } from \"@xmachines/play-vue\";\n *\n * const view = usePlayView();\n * // view.spec, view.handlers, view.registry\n * ```\n *\n * **Read a field where you use it, and destructure it not.** The return value is a Proxy\n * over the live context of the provider, so `const { spec } = usePlayView()` freezes at\n * the first read and the screen stops following the actor — with no error and no\n * warning. play-svelte and play-solid hold the same rule, for the same reason.\n */\nexport function usePlayView(): ViewContextValue {\n\treturn assertNonNullable(inject(ViewKey), \"ViewKey\");\n}\n\n/**\n * Access the current ViewContextValue from inside an ActorProvider tree.\n *\n * @deprecated Renamed to {@link usePlayView}; will be removed in the next major.\n */\nexport const getPlayViewContext = usePlayView;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8BA,IAAa,UAA0C,OAAO,gBAAgB;;;;;;;;;;;;;;;;;;;AAoB9E,SAAgB,cAAgC;CAC/C,OAAO,kBAAkB,OAAO,OAAO,GAAG,SAAS;AACpD;;;;;;AAOA,IAAa,qBAAqB"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The classification of the source that `onErrorCaptured` of Vue reports.
|
|
3
|
+
*
|
|
4
|
+
* This module holds four functions, and a test covers each one directly. The rule
|
|
5
|
+
* that they carry cannot be tested through a mounted component: Vue reports the source in TWO
|
|
6
|
+
* spellings, and a test runner loads the DEVELOPMENT bundle, which reports only one of
|
|
7
|
+
* them.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Answers whether `info` carries the spelling of the PRODUCTION bundle.
|
|
13
|
+
*
|
|
14
|
+
* The production bundle re-throws an error that no handler took only where the
|
|
15
|
+
* application asks for `throwUnhandledErrorInProduction`. A boundary that waits for a
|
|
16
|
+
* repeat asks this before it arms a record for one.
|
|
17
|
+
*
|
|
18
|
+
* @param info - The third argument of an `onErrorCaptured` hook.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isProductionSpelling(info: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Answers whether the error failed the RENDER of a descendant.
|
|
23
|
+
*
|
|
24
|
+
* The boundary of ActorProvider replaces the view with the fallback content for a
|
|
25
|
+
* failure of the render only. `onErrorCaptured` of Vue also fires for an error of an
|
|
26
|
+
* event handler and of a watcher callback, which the boundary of React, the
|
|
27
|
+
* ErrorBoundary of Solid, and `<svelte:boundary>` each see never.
|
|
28
|
+
*
|
|
29
|
+
* The function accepts BOTH spellings of the source, so the rule holds in a development
|
|
30
|
+
* build and in a production build. An unknown value counts as a failure of the render,
|
|
31
|
+
* which is the safe answer: the boundary then contains the error instead of letting a
|
|
32
|
+
* broken subtree stay on the screen.
|
|
33
|
+
*
|
|
34
|
+
* @param info - The third argument of an `onErrorCaptured` hook.
|
|
35
|
+
*/
|
|
36
|
+
export declare function failsTheRender(info: string): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* The source that `flushJobs` of Vue reports, in both spellings.
|
|
39
|
+
*
|
|
40
|
+
* `flushJobs` runs every queued job through
|
|
41
|
+
* `callWithErrorHandling(job, job.i, job.i ? 15 : 14)`, and code 15 is "component
|
|
42
|
+
* update". That is the ONE source under which Vue can report an error a SECOND time: a
|
|
43
|
+
* job loses the re-throw of `logError` of the development bundle to that call, and the
|
|
44
|
+
* error arrives at `onErrorCaptured` again with a source that {@link failsTheRender}
|
|
45
|
+
* answers TRUE for. Code 14 belongs to a job with no instance, which reaches no
|
|
46
|
+
* `errorCaptured` hook, and it counts as a non-render source already.
|
|
47
|
+
*
|
|
48
|
+
* A boundary that ignores a repeat asks for this spelling as well. Without it a record
|
|
49
|
+
* of the turn could also disarm the boundary for a failure of a SETUP function or of a
|
|
50
|
+
* RENDER function that carries the same error value.
|
|
51
|
+
*
|
|
52
|
+
* @param info - The third argument of an `onErrorCaptured` hook.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isComponentUpdate(info: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The configuration of the application that {@link canRaiseAgain} reads.
|
|
57
|
+
*
|
|
58
|
+
* It names the two fields of `app.config` alone, so a caller passes the config of Vue
|
|
59
|
+
* itself, and a test passes an object literal.
|
|
60
|
+
*/
|
|
61
|
+
export interface RaiseConfig {
|
|
62
|
+
errorHandler?: unknown;
|
|
63
|
+
throwUnhandledErrorInProduction?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Answers whether Vue can raise this error a SECOND time.
|
|
67
|
+
*
|
|
68
|
+
* `handleError` of Vue reaches `logError` only where the application wires no
|
|
69
|
+
* `app.config.errorHandler`, and it asks that field for TRUTH and not for a type: a host
|
|
70
|
+
* in JavaScript can set `errorHandler = null`, and Vue then re-throws. The development
|
|
71
|
+
* bundle re-throws every time. The production bundle writes the error and throws it only
|
|
72
|
+
* where the application asks for `throwUnhandledErrorInProduction`.
|
|
73
|
+
*
|
|
74
|
+
* The provider arms the record of a repeat only where this answers `true`. A record that
|
|
75
|
+
* waits for a repeat which never comes disarms the boundary for the next raise of that
|
|
76
|
+
* same error value.
|
|
77
|
+
*
|
|
78
|
+
* The production half lives HERE, and not in the component, for the reason that the
|
|
79
|
+
* header of this module states: a mounted test reaches it never.
|
|
80
|
+
*
|
|
81
|
+
* @param info - The source that `onErrorCaptured` reports, in either spelling.
|
|
82
|
+
* @param config - `app.config` of the application, or `undefined` outside an instance.
|
|
83
|
+
*/
|
|
84
|
+
export declare function canRaiseAgain(info: string, config: RaiseConfig | undefined): boolean;
|
|
85
|
+
//# sourceMappingURL=error-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-source.d.ts","sourceRoot":"","sources":["../src/error-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgDH;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC3B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+BAA+B,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAIpF"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//#region packages/play-vue/src/error-source.ts
|
|
2
|
+
/**
|
|
3
|
+
* The classification of the source that `onErrorCaptured` of Vue reports.
|
|
4
|
+
*
|
|
5
|
+
* This module holds four functions, and a test covers each one directly. The rule
|
|
6
|
+
* that they carry cannot be tested through a mounted component: Vue reports the source in TWO
|
|
7
|
+
* spellings, and a test runner loads the DEVELOPMENT bundle, which reports only one of
|
|
8
|
+
* them.
|
|
9
|
+
*
|
|
10
|
+
* @packageDocumentation
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* The sources of an error that leaves the rendered view alone, in the TWO spellings that
|
|
14
|
+
* Vue uses: the key of its `ErrorTypeStrings` map, which the production bundle reports,
|
|
15
|
+
* and the value of that entry, which the development bundle reports.
|
|
16
|
+
*
|
|
17
|
+
* ONE table, and not two sets: the two spellings name the same seven sources, and a
|
|
18
|
+
* second list is a second chance for one half to fall behind the other.
|
|
19
|
+
*/
|
|
20
|
+
var NON_RENDER_SOURCES = /* @__PURE__ */ new Map([
|
|
21
|
+
["2", "watcher getter"],
|
|
22
|
+
["3", "watcher callback"],
|
|
23
|
+
["4", "watcher cleanup function"],
|
|
24
|
+
["5", "native event handler"],
|
|
25
|
+
["6", "component event handler"],
|
|
26
|
+
["13", "async component loader"],
|
|
27
|
+
["14", "scheduler flush"]
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* The sources above, in the spelling of the DEVELOPMENT bundle.
|
|
31
|
+
*
|
|
32
|
+
* The production spelling needs no set of its own: the code IS the key of the table, so
|
|
33
|
+
* `NON_RENDER_SOURCES.has(code)` answers that half directly.
|
|
34
|
+
*/
|
|
35
|
+
var NON_RENDER_SOURCE_NAMES = new Set(NON_RENDER_SOURCES.values());
|
|
36
|
+
/**
|
|
37
|
+
* The prefix that the production bundle puts before the code.
|
|
38
|
+
*
|
|
39
|
+
* `runtime-core.cjs.prod.js` builds the value as
|
|
40
|
+
* `` `https://vuejs.org/error-reference/#runtime-${type}` ``, and the development
|
|
41
|
+
* bundle passes `ErrorTypeStrings[type]` instead. A rule that reads only the names of
|
|
42
|
+
* the development bundle therefore matches nothing in a production application, and it
|
|
43
|
+
* classifies EVERY error as a failure of the render.
|
|
44
|
+
*/
|
|
45
|
+
var PRODUCTION_PREFIX = "https://vuejs.org/error-reference/#runtime-";
|
|
46
|
+
/**
|
|
47
|
+
* `ErrorCodes.COMPONENT_UPDATE` of Vue, the source that `flushJobs` reports a job under,
|
|
48
|
+
* in the TWO spellings — the development name, and the production URL that
|
|
49
|
+
* {@link PRODUCTION_PREFIX} builds. The pair stands beside the table above, and not in
|
|
50
|
+
* it: that table holds the sources that leave the render alone, and this one fails it.
|
|
51
|
+
*/
|
|
52
|
+
var COMPONENT_UPDATE_NAME = "component update";
|
|
53
|
+
var COMPONENT_UPDATE_INFO = `${PRODUCTION_PREFIX}15`;
|
|
54
|
+
/**
|
|
55
|
+
* Answers whether `info` carries the spelling of the PRODUCTION bundle.
|
|
56
|
+
*
|
|
57
|
+
* The production bundle re-throws an error that no handler took only where the
|
|
58
|
+
* application asks for `throwUnhandledErrorInProduction`. A boundary that waits for a
|
|
59
|
+
* repeat asks this before it arms a record for one.
|
|
60
|
+
*
|
|
61
|
+
* @param info - The third argument of an `onErrorCaptured` hook.
|
|
62
|
+
*/
|
|
63
|
+
function isProductionSpelling(info) {
|
|
64
|
+
return info.startsWith(PRODUCTION_PREFIX);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Answers whether the error failed the RENDER of a descendant.
|
|
68
|
+
*
|
|
69
|
+
* The boundary of ActorProvider replaces the view with the fallback content for a
|
|
70
|
+
* failure of the render only. `onErrorCaptured` of Vue also fires for an error of an
|
|
71
|
+
* event handler and of a watcher callback, which the boundary of React, the
|
|
72
|
+
* ErrorBoundary of Solid, and `<svelte:boundary>` each see never.
|
|
73
|
+
*
|
|
74
|
+
* The function accepts BOTH spellings of the source, so the rule holds in a development
|
|
75
|
+
* build and in a production build. An unknown value counts as a failure of the render,
|
|
76
|
+
* which is the safe answer: the boundary then contains the error instead of letting a
|
|
77
|
+
* broken subtree stay on the screen.
|
|
78
|
+
*
|
|
79
|
+
* @param info - The third argument of an `onErrorCaptured` hook.
|
|
80
|
+
*/
|
|
81
|
+
function failsTheRender(info) {
|
|
82
|
+
if (isProductionSpelling(info)) return !NON_RENDER_SOURCES.has(info.slice(43));
|
|
83
|
+
return !NON_RENDER_SOURCE_NAMES.has(info);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The source that `flushJobs` of Vue reports, in both spellings.
|
|
87
|
+
*
|
|
88
|
+
* `flushJobs` runs every queued job through
|
|
89
|
+
* `callWithErrorHandling(job, job.i, job.i ? 15 : 14)`, and code 15 is "component
|
|
90
|
+
* update". That is the ONE source under which Vue can report an error a SECOND time: a
|
|
91
|
+
* job loses the re-throw of `logError` of the development bundle to that call, and the
|
|
92
|
+
* error arrives at `onErrorCaptured` again with a source that {@link failsTheRender}
|
|
93
|
+
* answers TRUE for. Code 14 belongs to a job with no instance, which reaches no
|
|
94
|
+
* `errorCaptured` hook, and it counts as a non-render source already.
|
|
95
|
+
*
|
|
96
|
+
* A boundary that ignores a repeat asks for this spelling as well. Without it a record
|
|
97
|
+
* of the turn could also disarm the boundary for a failure of a SETUP function or of a
|
|
98
|
+
* RENDER function that carries the same error value.
|
|
99
|
+
*
|
|
100
|
+
* @param info - The third argument of an `onErrorCaptured` hook.
|
|
101
|
+
*/
|
|
102
|
+
function isComponentUpdate(info) {
|
|
103
|
+
return info === COMPONENT_UPDATE_NAME || info === COMPONENT_UPDATE_INFO;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Answers whether Vue can raise this error a SECOND time.
|
|
107
|
+
*
|
|
108
|
+
* `handleError` of Vue reaches `logError` only where the application wires no
|
|
109
|
+
* `app.config.errorHandler`, and it asks that field for TRUTH and not for a type: a host
|
|
110
|
+
* in JavaScript can set `errorHandler = null`, and Vue then re-throws. The development
|
|
111
|
+
* bundle re-throws every time. The production bundle writes the error and throws it only
|
|
112
|
+
* where the application asks for `throwUnhandledErrorInProduction`.
|
|
113
|
+
*
|
|
114
|
+
* The provider arms the record of a repeat only where this answers `true`. A record that
|
|
115
|
+
* waits for a repeat which never comes disarms the boundary for the next raise of that
|
|
116
|
+
* same error value.
|
|
117
|
+
*
|
|
118
|
+
* The production half lives HERE, and not in the component, for the reason that the
|
|
119
|
+
* header of this module states: a mounted test reaches it never.
|
|
120
|
+
*
|
|
121
|
+
* @param info - The source that `onErrorCaptured` reports, in either spelling.
|
|
122
|
+
* @param config - `app.config` of the application, or `undefined` outside an instance.
|
|
123
|
+
*/
|
|
124
|
+
function canRaiseAgain(info, config) {
|
|
125
|
+
if (config?.errorHandler) return false;
|
|
126
|
+
if (!isProductionSpelling(info)) return true;
|
|
127
|
+
return Boolean(config?.throwUnhandledErrorInProduction);
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { canRaiseAgain, failsTheRender, isComponentUpdate, isProductionSpelling };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=error-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-source.js","names":[],"sources":["../src/error-source.ts"],"sourcesContent":["/**\n * The classification of the source that `onErrorCaptured` of Vue reports.\n *\n * This module holds four functions, and a test covers each one directly. The rule\n * that they carry cannot be tested through a mounted component: Vue reports the source in TWO\n * spellings, and a test runner loads the DEVELOPMENT bundle, which reports only one of\n * them.\n *\n * @packageDocumentation\n */\n\n/**\n * The sources of an error that leaves the rendered view alone, in the TWO spellings that\n * Vue uses: the key of its `ErrorTypeStrings` map, which the production bundle reports,\n * and the value of that entry, which the development bundle reports.\n *\n * ONE table, and not two sets: the two spellings name the same seven sources, and a\n * second list is a second chance for one half to fall behind the other.\n */\nconst NON_RENDER_SOURCES: ReadonlyMap<string, string> = new Map([\n\t[\"2\", \"watcher getter\"],\n\t[\"3\", \"watcher callback\"],\n\t[\"4\", \"watcher cleanup function\"],\n\t[\"5\", \"native event handler\"],\n\t[\"6\", \"component event handler\"],\n\t[\"13\", \"async component loader\"],\n\t[\"14\", \"scheduler flush\"],\n]);\n\n/**\n * The sources above, in the spelling of the DEVELOPMENT bundle.\n *\n * The production spelling needs no set of its own: the code IS the key of the table, so\n * `NON_RENDER_SOURCES.has(code)` answers that half directly.\n */\nconst NON_RENDER_SOURCE_NAMES: ReadonlySet<string> = new Set(NON_RENDER_SOURCES.values());\n\n/**\n * The prefix that the production bundle puts before the code.\n *\n * `runtime-core.cjs.prod.js` builds the value as\n * `` `https://vuejs.org/error-reference/#runtime-${type}` ``, and the development\n * bundle passes `ErrorTypeStrings[type]` instead. A rule that reads only the names of\n * the development bundle therefore matches nothing in a production application, and it\n * classifies EVERY error as a failure of the render.\n */\nconst PRODUCTION_PREFIX = \"https://vuejs.org/error-reference/#runtime-\";\n\n/**\n * `ErrorCodes.COMPONENT_UPDATE` of Vue, the source that `flushJobs` reports a job under,\n * in the TWO spellings — the development name, and the production URL that\n * {@link PRODUCTION_PREFIX} builds. The pair stands beside the table above, and not in\n * it: that table holds the sources that leave the render alone, and this one fails it.\n */\nconst COMPONENT_UPDATE_NAME = \"component update\";\nconst COMPONENT_UPDATE_INFO = `${PRODUCTION_PREFIX}15`;\n\n/**\n * Answers whether `info` carries the spelling of the PRODUCTION bundle.\n *\n * The production bundle re-throws an error that no handler took only where the\n * application asks for `throwUnhandledErrorInProduction`. A boundary that waits for a\n * repeat asks this before it arms a record for one.\n *\n * @param info - The third argument of an `onErrorCaptured` hook.\n */\nexport function isProductionSpelling(info: string): boolean {\n\treturn info.startsWith(PRODUCTION_PREFIX);\n}\n\n/**\n * Answers whether the error failed the RENDER of a descendant.\n *\n * The boundary of ActorProvider replaces the view with the fallback content for a\n * failure of the render only. `onErrorCaptured` of Vue also fires for an error of an\n * event handler and of a watcher callback, which the boundary of React, the\n * ErrorBoundary of Solid, and `<svelte:boundary>` each see never.\n *\n * The function accepts BOTH spellings of the source, so the rule holds in a development\n * build and in a production build. An unknown value counts as a failure of the render,\n * which is the safe answer: the boundary then contains the error instead of letting a\n * broken subtree stay on the screen.\n *\n * @param info - The third argument of an `onErrorCaptured` hook.\n */\nexport function failsTheRender(info: string): boolean {\n\tif (isProductionSpelling(info)) {\n\t\treturn !NON_RENDER_SOURCES.has(info.slice(PRODUCTION_PREFIX.length));\n\t}\n\treturn !NON_RENDER_SOURCE_NAMES.has(info);\n}\n\n/**\n * The source that `flushJobs` of Vue reports, in both spellings.\n *\n * `flushJobs` runs every queued job through\n * `callWithErrorHandling(job, job.i, job.i ? 15 : 14)`, and code 15 is \"component\n * update\". That is the ONE source under which Vue can report an error a SECOND time: a\n * job loses the re-throw of `logError` of the development bundle to that call, and the\n * error arrives at `onErrorCaptured` again with a source that {@link failsTheRender}\n * answers TRUE for. Code 14 belongs to a job with no instance, which reaches no\n * `errorCaptured` hook, and it counts as a non-render source already.\n *\n * A boundary that ignores a repeat asks for this spelling as well. Without it a record\n * of the turn could also disarm the boundary for a failure of a SETUP function or of a\n * RENDER function that carries the same error value.\n *\n * @param info - The third argument of an `onErrorCaptured` hook.\n */\nexport function isComponentUpdate(info: string): boolean {\n\treturn info === COMPONENT_UPDATE_NAME || info === COMPONENT_UPDATE_INFO;\n}\n\n/**\n * The configuration of the application that {@link canRaiseAgain} reads.\n *\n * It names the two fields of `app.config` alone, so a caller passes the config of Vue\n * itself, and a test passes an object literal.\n */\nexport interface RaiseConfig {\n\terrorHandler?: unknown;\n\tthrowUnhandledErrorInProduction?: boolean;\n}\n\n/**\n * Answers whether Vue can raise this error a SECOND time.\n *\n * `handleError` of Vue reaches `logError` only where the application wires no\n * `app.config.errorHandler`, and it asks that field for TRUTH and not for a type: a host\n * in JavaScript can set `errorHandler = null`, and Vue then re-throws. The development\n * bundle re-throws every time. The production bundle writes the error and throws it only\n * where the application asks for `throwUnhandledErrorInProduction`.\n *\n * The provider arms the record of a repeat only where this answers `true`. A record that\n * waits for a repeat which never comes disarms the boundary for the next raise of that\n * same error value.\n *\n * The production half lives HERE, and not in the component, for the reason that the\n * header of this module states: a mounted test reaches it never.\n *\n * @param info - The source that `onErrorCaptured` reports, in either spelling.\n * @param config - `app.config` of the application, or `undefined` outside an instance.\n */\nexport function canRaiseAgain(info: string, config: RaiseConfig | undefined): boolean {\n\tif (config?.errorHandler) return false;\n\tif (!isProductionSpelling(info)) return true;\n\treturn Boolean(config?.throwUnhandledErrorInProduction);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,IAAM,qCAAkD,IAAI,IAAI;CAC/D,CAAC,KAAK,gBAAgB;CACtB,CAAC,KAAK,kBAAkB;CACxB,CAAC,KAAK,0BAA0B;CAChC,CAAC,KAAK,sBAAsB;CAC5B,CAAC,KAAK,yBAAyB;CAC/B,CAAC,MAAM,wBAAwB;CAC/B,CAAC,MAAM,iBAAiB;AACzB,CAAC;;;;;;;AAQD,IAAM,0BAA+C,IAAI,IAAI,mBAAmB,OAAO,CAAC;;;;;;;;;;AAWxF,IAAM,oBAAoB;;;;;;;AAQ1B,IAAM,wBAAwB;AAC9B,IAAM,wBAAwB,GAAG,kBAAkB;;;;;;;;;;AAWnD,SAAgB,qBAAqB,MAAuB;CAC3D,OAAO,KAAK,WAAW,iBAAiB;AACzC;;;;;;;;;;;;;;;;AAiBA,SAAgB,eAAe,MAAuB;CACrD,IAAI,qBAAqB,IAAI,GAC5B,OAAO,CAAC,mBAAmB,IAAI,KAAK,MAAM,EAAwB,CAAC;CAEpE,OAAO,CAAC,wBAAwB,IAAI,IAAI;AACzC;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,kBAAkB,MAAuB;CACxD,OAAO,SAAS,yBAAyB,SAAS;AACnD;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,cAAc,MAAc,QAA0C;CACrF,IAAI,QAAQ,cAAc,OAAO;CACjC,IAAI,CAAC,qBAAqB,IAAI,GAAG,OAAO;CACxC,OAAO,QAAQ,QAAQ,+BAA+B;AACvD"}
|
package/dist/types.d.ts
CHANGED
|
@@ -9,8 +9,69 @@ import type { BaseActorProviderProps } from "@xmachines/play-actor";
|
|
|
9
9
|
* The props of the ActorProvider component.
|
|
10
10
|
* This file holds them, so that TypeScript can re-export them. The vue-shim has a
|
|
11
11
|
* limit that stops a re-export from the .vue file.
|
|
12
|
+
*
|
|
13
|
+
* The null-view content is a **slot** named `fallback`, and not a prop. The provider
|
|
14
|
+
* shows that slot when `currentView` is null, and also while a contained error of a
|
|
15
|
+
* descendant is active (see `onError`).
|
|
12
16
|
*/
|
|
13
17
|
export interface ActorProviderProps extends BaseActorProviderProps<DefineRegistryResult> {
|
|
18
|
+
/**
|
|
19
|
+
* The optional callback. The provider calls it when a descendant fails the RENDER —
|
|
20
|
+
* the render function, the setup function, or a lifecycle hook — and the error
|
|
21
|
+
* boundary of ActorProvider contains the error (`onErrorCaptured`).
|
|
22
|
+
*
|
|
23
|
+
* `onErrorCaptured` of Vue fires for an error of an event handler and of a watcher
|
|
24
|
+
* callback as well, and this prop reports NEITHER: the boundary of React, the
|
|
25
|
+
* ErrorBoundary of Solid and `<svelte:boundary>` see such an error never, so a report
|
|
26
|
+
* here would give play-vue a wider contract than the other four renderers. Those
|
|
27
|
+
* errors continue to `app.config.errorHandler` exactly as they do without this
|
|
28
|
+
* provider. `onError` reports a failure of the render, in every renderer.
|
|
29
|
+
*
|
|
30
|
+
* Vue reports such an error a SECOND time where the application wires no
|
|
31
|
+
* `app.config.errorHandler`: it re-throws the error, and a queued job carries it back
|
|
32
|
+
* as a failure of a render. The provider recognises that repeat and holds the view.
|
|
33
|
+
* It writes the error value to `console.error` with the name of this package, because
|
|
34
|
+
* Vue writes that value nowhere on this path, and `onError` stays silent. A
|
|
35
|
+
* development build re-throws every time, and a production build only where the
|
|
36
|
+
* application sets `app.config.throwUnhandledErrorInProduction`.
|
|
37
|
+
*
|
|
38
|
+
* ONE case escapes that rule. An ancestor `errorCaptured` hook that returns `false`
|
|
39
|
+
* stops Vue before the re-throw, so the record of the provider waits for a repeat that
|
|
40
|
+
* never comes. A second failure in the same flush can take that record. It must carry
|
|
41
|
+
* the same error value, and it must unwind out of the update job of a parent. Vue
|
|
42
|
+
* reports such a failure as a component update. The provider then holds the stale
|
|
43
|
+
* subtree on the screen, and `onError` stays silent.
|
|
44
|
+
*
|
|
45
|
+
* While the error is active, the provider renders the `fallback` slot instead of its
|
|
46
|
+
* children. The provider clears the error on the next view emission (when
|
|
47
|
+
* `currentView` changes), so a view transition retries the render.
|
|
48
|
+
*
|
|
49
|
+
* The boundary stops the error. Without this prop the provider therefore writes the
|
|
50
|
+
* error to `console.error`, because `app.config.errorHandler` sees it no longer.
|
|
51
|
+
*
|
|
52
|
+
* Element-level catalog component throws are caught upstream by the per-element
|
|
53
|
+
* boundary of @json-render, and reported through `onRenderError`. This boundary is
|
|
54
|
+
* the outer net for everything else.
|
|
55
|
+
*
|
|
56
|
+
* The second parameter is the RESET, for a retry that the host starts — a "Retry"
|
|
57
|
+
* button of its own. It clears the contained error and renders again, and it resolves
|
|
58
|
+
* the view that the actor holds at the MOMENT OF THE CALL, so a host that keeps the
|
|
59
|
+
* callback cannot rewind the screen to the view that failed. A reset that the host
|
|
60
|
+
* calls from inside this handler starts no second retry, because no input changed
|
|
61
|
+
* between the two attempts, and a reset after the provider is unmounted does nothing.
|
|
62
|
+
* The five renderers hold the same three rules.
|
|
63
|
+
*
|
|
64
|
+
* **A handler that THROWS reaches no caller.** The provider contains that throw, and
|
|
65
|
+
* it writes the throw to `console.error`. The errored flag schedules a RENDER, so a
|
|
66
|
+
* throw that left this handler would stop that render and skip the `return false` of
|
|
67
|
+
* `onErrorCaptured`: the subtree that threw stays on the screen AND the error reaches
|
|
68
|
+
* `app.config.errorHandler`.
|
|
69
|
+
*
|
|
70
|
+
* Report a failure from this handler. To ESCALATE one, raise it from a task of your
|
|
71
|
+
* own — `queueMicrotask(() => { throw error; })` — which reaches the global handler of
|
|
72
|
+
* the page and leaves the containment whole. The five renderers hold this one rule.
|
|
73
|
+
*/
|
|
74
|
+
onError?: (error: unknown, reset: () => void) => void;
|
|
14
75
|
}
|
|
15
76
|
/**
|
|
16
77
|
* The props of PlayUIProvider — every ActorProvider prop, and also the props of JSONUIProvider.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEpE
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAEpE;;;;;;;;GAQG;AACH,MAAM,WAAW,kBAAmB,SAAQ,sBAAsB,CAAC,oBAAoB,CAAC;IACvF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAuDG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACtD;AAED;;GAEG;AACH,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIrE;;GAEG;AACH,MAAM,WAAW,uBAAuB;CAAG;AAE3C,MAAM,WAAW,mBAAoB,SAAQ,kBAAkB;IAC9D,uGAAuG;IACvG,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,0EAA0E;IAC1E,mBAAmB,CAAC,EAAE,MAAM,CAC3B,MAAM,EACN,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAC3D,CAAC;IACF,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CACjB,MAAM,EACN,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAC5E,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-vue",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Vue renderer for XMachines Play architecture",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"test:watch": "vitest"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@xmachines/play": "
|
|
49
|
-
"@xmachines/play-actor": "
|
|
50
|
-
"@xmachines/play-signals": "
|
|
48
|
+
"@xmachines/play": "3.0.0",
|
|
49
|
+
"@xmachines/play-actor": "3.0.0",
|
|
50
|
+
"@xmachines/play-signals": "3.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
"@vitejs/plugin-vue": "^6.0.5",
|
|
56
56
|
"@vitest/browser-playwright": "^4.1.11",
|
|
57
57
|
"@vue/test-utils": "^2.4.9",
|
|
58
|
-
"@xmachines/json-render-core": "^0.20.0-xm.
|
|
59
|
-
"@xmachines/json-render-vue": "^0.20.0-xm.
|
|
60
|
-
"@xmachines/json-render-xstate": "^0.20.0-xm.
|
|
58
|
+
"@xmachines/json-render-core": "^0.20.0-xm.4",
|
|
59
|
+
"@xmachines/json-render-vue": "^0.20.0-xm.4",
|
|
60
|
+
"@xmachines/json-render-xstate": "^0.20.0-xm.4",
|
|
61
61
|
"@xstate/store": "^3.17.0",
|
|
62
62
|
"oxfmt": "^0.64.0",
|
|
63
63
|
"oxlint": "^1.79.0",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"zod": "^4.4.1"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@xmachines/json-render-core": "^0.20.0-xm.
|
|
74
|
-
"@xmachines/json-render-vue": "^0.20.0-xm.
|
|
75
|
-
"@xmachines/json-render-xstate": "^0.20.0-xm.
|
|
73
|
+
"@xmachines/json-render-core": "^0.20.0-xm.4",
|
|
74
|
+
"@xmachines/json-render-vue": "^0.20.0-xm.4",
|
|
75
|
+
"@xmachines/json-render-xstate": "^0.20.0-xm.4",
|
|
76
76
|
"@xstate/store": "^3.17.0",
|
|
77
77
|
"vue": "^3.5.0",
|
|
78
78
|
"xstate": "^5.31.0"
|