@xmachines/play-vue 4.0.0 → 5.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
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# `@xmachines/play-vue`
|
|
2
2
|
|
|
3
|
-
> Vue 3 renderer for the XMachines Play Architecture. It observes the actor
|
|
3
|
+
> Vue 3 renderer for the XMachines Play Architecture. It observes the actor atoms 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
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
11
|
-
`@xmachines/play-vue` is the Vue 3 rendering layer of XMachines Play. It connects the
|
|
11
|
+
`@xmachines/play-vue` is the Vue 3 rendering layer of XMachines Play. It connects the atoms (the actor state) to the Vue reactivity, and it renders the components through `@xmachines/json-render-vue`.
|
|
12
12
|
|
|
13
13
|
**The architecture invariants that this package keeps:**
|
|
14
14
|
|
|
15
|
-
- **Passive Infrastructure** — the components observe the actor
|
|
16
|
-
- **
|
|
15
|
+
- **Passive Infrastructure** — the components observe the actor atoms. They never decide a state transition.
|
|
16
|
+
- **Atom-Only Reactivity** — the atoms are the source of truth. Vue reactivity only triggers the re-render.
|
|
17
17
|
- **Actor Authority** — the actor controls the view selection. The renderer reflects it.
|
|
18
18
|
|
|
19
19
|
---
|
|
@@ -27,9 +27,13 @@ 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/store@^
|
|
30
|
+
pnpm add vue@^3.5.0 @xmachines/play @xmachines/play-atom @xstate/store@^4.2.3 @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
|
+
`@xstate/store` is the peer of [`@xmachines/play-atom`](../play-atom/README.md), and not of this
|
|
34
|
+
package: this package reads the atom through [`@xmachines/play-atom`](../play-atom/README.md) and
|
|
35
|
+
imports the engine in no source file. Install ONE copy of it.
|
|
36
|
+
|
|
33
37
|
---
|
|
34
38
|
|
|
35
39
|
## Quick Start
|
|
@@ -118,7 +122,7 @@ The leaf component without props. It reads the current `spec` and `registry` fro
|
|
|
118
122
|
|
|
119
123
|
#### `<ActorProvider>`
|
|
120
124
|
|
|
121
|
-
The low-level provider for a custom provider composition. It owns the complete actor lifecycle: the
|
|
125
|
+
The low-level provider for a custom provider composition. It owns the complete actor lifecycle: the atom subscription, the state store of each view, the handler resolution, and the Vue context. Use `<PlayUIProvider>` when you do not need this control.
|
|
122
126
|
|
|
123
127
|
| Prop | Type | Required | Description |
|
|
124
128
|
| ---------------- | ---------------------- | -------- | ------------------------------- |
|
|
@@ -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\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-view\";\nimport type { Viewable, PlaySpec, ViewActor } from \"@xmachines/play-view\";\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 } from \"./actor-provider-context.js\";\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 ViewActor));\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 ViewActor),\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
|
+
{"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 atom subscription (watchAtom), which connects the atoms 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 atoms, and it sends the events\n * @invariant Atom-Only Reactivity - the state of the business logic is in the actor atoms\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 { createAtom, watchAtom } from \"@xmachines/play-atom\";\nimport { canRaiseAgain, failsTheRender, isComponentUpdate } from \"./error-source.js\";\nimport {\n\tattachRenderErrorHandler,\n\tcreateFailureLatch,\n\tcreateReportGuard,\n\tcreateViewStoreLifecycle,\n} from \"@xmachines/play-view\";\nimport type { Viewable, PlaySpec, ViewActor } from \"@xmachines/play-view\";\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 { 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 } from \"./actor-provider-context.js\";\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// watchAtom 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 Atom objects.\n\t\t// A shallowRef holds the actor. Therefore the injected value and the atom watcher\n\t\t// can follow `props.actor` when the prop changes after the mount.\n\t\tconst actorRef = shallowRef(toRaw(props.actor as ViewActor));\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 atom watcher connects the atoms 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 = watchAtom(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 atom. 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 ViewActor),\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 = watchAtom(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":""}
|
|
@@ -3,16 +3,15 @@ import { canRaiseAgain, failsTheRender, isComponentUpdate } from "./error-source
|
|
|
3
3
|
import { provideActor } from "./useActor.js";
|
|
4
4
|
import { computed, defineComponent, getCurrentInstance, h, markRaw, onErrorCaptured, onUnmounted, provide, ref, shallowRef, toRaw, watch } from "vue";
|
|
5
5
|
import { ActionProvider, StateProvider, VisibilityProvider, useStateStore } from "@xmachines/json-render-vue";
|
|
6
|
-
import {
|
|
6
|
+
import { createAtom, watchAtom } from "@xmachines/play-atom";
|
|
7
7
|
import { attachRenderErrorHandler, createFailureLatch, createReportGuard, createViewStoreLifecycle } from "@xmachines/play-view";
|
|
8
|
-
import { createAtom } from "@xstate/store";
|
|
9
8
|
import { xstateStoreStateStore } from "@xmachines/json-render-xstate";
|
|
10
9
|
//#region packages/play-vue/src/ActorProvider.vue?vue&type=script&lang.ts
|
|
11
10
|
/**
|
|
12
11
|
* ActorProvider — the low-level provider of the XMachines Vue render architecture.
|
|
13
12
|
*
|
|
14
13
|
* It owns the complete actor lifecycle:
|
|
15
|
-
* - the
|
|
14
|
+
* - the atom subscription (watchAtom), which connects the atoms to the Vue reactivity
|
|
16
15
|
* - the StateStore lifecycle of each view, controlled and uncontrolled
|
|
17
16
|
* - the resolution of the handlers, through ActorProviderInner, which must be inside StateProvider
|
|
18
17
|
* - the ViewContextValue, which it provides through the ViewKey injection key
|
|
@@ -23,8 +22,8 @@ import { xstateStoreStateStore } from "@xmachines/json-render-xstate";
|
|
|
23
22
|
* An end user takes <PlayUIProvider> instead.
|
|
24
23
|
*
|
|
25
24
|
* @invariant Actor Authority - the Actor decides each state transition, with its guards
|
|
26
|
-
* @invariant Passive Infrastructure - the component observes the
|
|
27
|
-
* @invariant
|
|
25
|
+
* @invariant Passive Infrastructure - the component observes the atoms, and it sends the events
|
|
26
|
+
* @invariant Atom-Only Reactivity - the state of the business logic is in the actor atoms
|
|
28
27
|
*/
|
|
29
28
|
var ActorProviderInner = defineComponent({
|
|
30
29
|
name: "ActorProviderInner",
|
|
@@ -216,7 +215,7 @@ var ActorProvider_vue_vue_type_script_lang_default = defineComponent({
|
|
|
216
215
|
const view = ref(actorRef.value.currentView.get());
|
|
217
216
|
const storeLifecycle = createViewStoreLifecycle((seed) => xstateStoreStateStore({ atom: createAtom(seed) }));
|
|
218
217
|
let storeKey = 0;
|
|
219
|
-
let unwatch =
|
|
218
|
+
let unwatch = watchAtom(actorRef.value.currentView, (nextView) => {
|
|
220
219
|
errored.value = false;
|
|
221
220
|
failureLatch.clear();
|
|
222
221
|
view.value = nextView;
|
|
@@ -233,7 +232,7 @@ var ActorProvider_vue_vue_type_script_lang_default = defineComponent({
|
|
|
233
232
|
errored.value = false;
|
|
234
233
|
failureLatch.clear();
|
|
235
234
|
view.value = nextActor.currentView.get();
|
|
236
|
-
unwatch =
|
|
235
|
+
unwatch = watchAtom(nextActor.currentView, (nextView) => {
|
|
237
236
|
errored.value = false;
|
|
238
237
|
failureLatch.clear();
|
|
239
238
|
view.value = nextView;
|
|
@@ -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\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-view\";\nimport type { Viewable, PlaySpec, ViewActor } from \"@xmachines/play-view\";\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 } from \"./actor-provider-context.js\";\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 ViewActor));\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 ViewActor),\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":"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"}
|
|
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 atom subscription (watchAtom), which connects the atoms 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 atoms, and it sends the events\n * @invariant Atom-Only Reactivity - the state of the business logic is in the actor atoms\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 { createAtom, watchAtom } from \"@xmachines/play-atom\";\nimport { canRaiseAgain, failsTheRender, isComponentUpdate } from \"./error-source.js\";\nimport {\n\tattachRenderErrorHandler,\n\tcreateFailureLatch,\n\tcreateReportGuard,\n\tcreateViewStoreLifecycle,\n} from \"@xmachines/play-view\";\nimport type { Viewable, PlaySpec, ViewActor } from \"@xmachines/play-view\";\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 { 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 } from \"./actor-provider-context.js\";\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// watchAtom 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 Atom objects.\n\t\t// A shallowRef holds the actor. Therefore the injected value and the atom watcher\n\t\t// can follow `props.actor` when the prop changes after the mount.\n\t\tconst actorRef = shallowRef(toRaw(props.actor as ViewActor));\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 atom watcher connects the atoms 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 = watchAtom(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 atom. 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 ViewActor),\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 = watchAtom(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":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @xmachines/play-vue - Vue 3 renderer for the XMachines Play architecture
|
|
3
3
|
*
|
|
4
|
-
* This package is a thin Vue rendering layer. It observes the actor
|
|
4
|
+
* This package is a thin Vue rendering layer. It observes the actor atoms and
|
|
5
5
|
* renders the UI components with @xmachines/json-render-vue. Vue reactivity only
|
|
6
|
-
* triggers the re-render. The
|
|
6
|
+
* triggers the re-render. The atoms are the source of truth.
|
|
7
7
|
*
|
|
8
8
|
* The package re-exports `defineRegistry`, which knows an SFC and wraps each `.vue`
|
|
9
9
|
* SFC with `h()`. It also re-exports `useBoundProp`, `ComponentFn`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xmachines/play-vue",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Vue renderer for XMachines Play architecture",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"test:watch": "vitest"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@xmachines/play-actor": "
|
|
50
|
-
"@xmachines/play-view": "
|
|
49
|
+
"@xmachines/play-actor": "5.0.0",
|
|
50
|
+
"@xmachines/play-view": "5.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@testing-library/jest-dom": "^7.0.1",
|
|
@@ -58,11 +58,10 @@
|
|
|
58
58
|
"@xmachines/json-render-core": "^0.20.0-xm.4",
|
|
59
59
|
"@xmachines/json-render-vue": "^0.20.0-xm.4",
|
|
60
60
|
"@xmachines/json-render-xstate": "^0.20.0-xm.4",
|
|
61
|
-
"@xmachines/play": "
|
|
62
|
-
"@xmachines/play-
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"oxlint": "^1.83.0",
|
|
61
|
+
"@xmachines/play": "5.0.0",
|
|
62
|
+
"@xmachines/play-atom": "5.0.0",
|
|
63
|
+
"oxfmt": "^0.70.0",
|
|
64
|
+
"oxlint": "^1.85.0",
|
|
66
65
|
"typescript": "^5.9.3 || ^6.0.3",
|
|
67
66
|
"vite": "^8.3.0",
|
|
68
67
|
"vite-plugin-dts": "^5.1.0",
|
|
@@ -75,9 +74,8 @@
|
|
|
75
74
|
"@xmachines/json-render-core": "^0.20.0-xm.4",
|
|
76
75
|
"@xmachines/json-render-vue": "^0.20.0-xm.4",
|
|
77
76
|
"@xmachines/json-render-xstate": "^0.20.0-xm.4",
|
|
78
|
-
"@xmachines/play": "
|
|
79
|
-
"@xmachines/play-
|
|
80
|
-
"@xstate/store": "^3.17.0",
|
|
77
|
+
"@xmachines/play": "5.0.0",
|
|
78
|
+
"@xmachines/play-atom": "5.0.0",
|
|
81
79
|
"vue": "^3.5.0"
|
|
82
80
|
},
|
|
83
81
|
"engines": {
|