@xmachines/play-xstate 6.1.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +64 -12
  2. package/dist/capabilities.js +6 -2
  3. package/dist/capabilities.js.map +1 -1
  4. package/dist/define-player.js +8 -8
  5. package/dist/define-player.js.map +1 -1
  6. package/dist/errors.js +262 -259
  7. package/dist/errors.js.map +1 -1
  8. package/dist/index.js +4 -25
  9. package/dist/player-actor.js +318 -448
  10. package/dist/player-actor.js.map +1 -1
  11. package/dist/routing/build-url.js +74 -108
  12. package/dist/routing/build-url.js.map +1 -1
  13. package/dist/routing/derive-current-route.d.ts.map +1 -1
  14. package/dist/routing/derive-current-route.js +46 -67
  15. package/dist/routing/derive-current-route.js.map +1 -1
  16. package/dist/routing/derive-initial-route.js +53 -64
  17. package/dist/routing/derive-initial-route.js.map +1 -1
  18. package/dist/routing/derive-route.js +225 -242
  19. package/dist/routing/derive-route.js.map +1 -1
  20. package/dist/routing/format-play-route-transitions.js +192 -258
  21. package/dist/routing/format-play-route-transitions.js.map +1 -1
  22. package/dist/state-meta.d.ts +56 -25
  23. package/dist/state-meta.d.ts.map +1 -1
  24. package/dist/state-meta.js +164 -105
  25. package/dist/state-meta.js.map +1 -1
  26. package/dist/view/derive-current-view.d.ts +18 -10
  27. package/dist/view/derive-current-view.d.ts.map +1 -1
  28. package/dist/view/derive-current-view.js +243 -221
  29. package/dist/view/derive-current-view.js.map +1 -1
  30. package/dist/with-routing.js +94 -136
  31. package/dist/with-routing.js.map +1 -1
  32. package/dist/with-view.d.ts.map +1 -1
  33. package/dist/with-view.js +82 -152
  34. package/dist/with-view.js.map +1 -1
  35. package/package.json +19 -19
  36. package/dist/index.js.map +0 -1
  37. package/dist/routing/index.js +0 -14
  38. package/dist/routing/index.js.map +0 -1
  39. package/dist/routing/types.js +0 -2
  40. package/dist/routing/types.js.map +0 -1
  41. package/dist/types.js +0 -2
  42. package/dist/types.js.map +0 -1
@@ -1,140 +1,98 @@
1
- import { createAtom } from "@xmachines/play-atom";
2
- import { deriveCurrentRoute, deriveInitialRoute } from "./routing/index.js";
3
1
  import { toError } from "./player-actor.js";
2
+ import { deriveRoute, isAbsoluteRoute } from "./routing/derive-route.js";
3
+ import { buildRouteUrl } from "./routing/build-url.js";
4
+ import { deriveCurrentRoute } from "./routing/derive-current-route.js";
5
+ import { deriveInitialRoute } from "./routing/derive-initial-route.js";
6
+ import { formatPlayRouteTransitions } from "./routing/format-play-route-transitions.js";
7
+ import { createAtom } from "@xmachines/play-atom";
8
+ //#region packages/play-xstate/src/with-routing.ts
4
9
  /**
5
- * Adds the routing capability to an actor class.
6
- *
7
- * The result satisfies `Routable`, so a router bridge accepts it. A class without this
8
- * mixin exposes no `currentRoute`, and a bridge therefore refuses it when it compiles,
9
- * rather than at the first navigation.
10
- *
11
- * The mixin declares NO constructor, and that is deliberate: TypeScript demands the
12
- * signature `...args: any[]` from a mixin over a generic base (TS2545), and this
13
- * workspace writes no `any`. A field initializer of a subclass runs after `super()`
14
- * instead, and it reads the inputs that `PlayerActor` keeps for a capability.
15
- *
16
- * @param Base - The actor class to extend, normally `PlayerActor`.
17
- */
18
- export function withRouting(Base) {
19
- class RoutableActor extends Base {
20
- /**
21
- * A computed atom. It derives the current URL path from the `meta.route`
22
- * template of the active machine state and from the context of the actor.
23
- *
24
- * It returns `null` when the current state has no `meta.route` field, and also when
25
- * a necessary `:param` has no value in the context. The second case is normally
26
- * temporary during a transition, and the atom computes the value again on the next
27
- * snapshot.
28
- *
29
- * It THROWS never. A derivation of this architecture reports a failure through
30
- * `onError` and keeps its last good value, and `currentView` of
31
- * `@xmachines/play-xstate/view` follows the same rule.
32
- *
33
- * The reason is the propagation. A subscriber makes this atom eager, so the
34
- * derivation runs inside the `set` of `state`, which runs inside the XState
35
- * subscription. A throw that left this function would unwind into `actor.send` and
36
- * break the transition for EVERY other observer of the actor.
37
- *
38
- * The containment therefore belongs to the derivation, and not to each subscriber:
39
- * a consumer that writes its own `watchAtom(actor.currentRoute, ...)` is protected
40
- * by the same rule that protects a router bridge.
41
- *
42
- * @example
43
- * ```typescript
44
- * // It returns "/profile/alice" when context.params.userId === "alice",
45
- * // and null while the param is still absent.
46
- * const route = actor.currentRoute.get();
47
- * ```
48
- */
49
- currentRoute = createAtom((previous) => {
50
- try {
51
- return deriveCurrentRoute(this.state.get());
52
- }
53
- catch (error) {
54
- // `deriveCurrentRoute` answers the EXPECTED failure itself: a missing `:param`
55
- // returns `null`, and the next snapshot resolves it. Whatever reaches this line
56
- // is unexpected — a `URIError` of `encodeURIComponent` for a lone surrogate,
57
- // for example. Report it, and keep the last route: a caller that reads `null`
58
- // or a stale route navigates nowhere, which the bridge handles already.
59
- // The derivation runs inside the `set` of `state`, and the engine TRACKS every
60
- // atom that this function reads — the handler of the host included. A report
61
- // from HERE therefore made whatever that handler reads a dependency of this
62
- // atom, and an unrelated write of it re-derived and reported again: three
63
- // reports for one failure. The microtask leaves the evaluation, so the handler
64
- // reads what it likes and this atom depends on none of it.
65
- //
66
- // It leaves the `set` as well, which is what the comment below already needed:
67
- // a throw from the handler lands on the global handler of the runtime, and it
68
- // breaks the transition for no other observer of the actor.
69
- const onError = this.hooks.onError;
70
- if (onError) {
71
- const reported = toError(error);
72
- queueMicrotask(() => {
73
- onError(this, reported);
74
- });
75
- }
76
- return previous ?? null;
77
- }
78
- }, {
79
- // Every WRITE of `state` reaches an observer of this atom, and not the writes
80
- // that change the route alone.
81
- //
82
- // The default `Object.is` stops a notification for a value that the atom holds
83
- // already, and a route is a STRING: a guard that refuses an inbound location and
84
- // moves the actor back to the state it came from derives the same string, so no
85
- // observer heard the answer. `RouterBridgeBase` waits for exactly that answer —
86
- // `syncActorFromRouter` clears `lastActorRoute` before it sends, so the push
87
- // that puts the address bar back on the route of the machine is owed to this
88
- // notification — and the address bar kept the location that the guard refused.
89
- //
90
- // The bridge is written against a notification for each change of the SNAPSHOT,
91
- // and not for each change of the derived string, so the comparison has to answer
92
- // "different" always. A consumer that reads the value pays nothing for it: the
93
- // derivation still runs one time for each transition, and `state` itself compares
94
- // with `Object.is`, so an event that XState ignores delivers the identical
95
- // snapshot and propagates nothing.
96
- compare: () => false,
97
- });
98
- /**
99
- * The route of the initial state of the machine. The construction fixes it, and it
100
- * never changes, also when the code restores the actor from a snapshot.
101
- *
102
- * A router bridge compares it with the browser URL, and it therefore separates a
103
- * deep link (a URL that is not the initial one → the router wins) from a restore
104
- * (the initial URL, and the actor at a different route from the restore → the actor
105
- * wins).
106
- *
107
- * `deriveInitialRoute` derives the value statically from the machine definition,
108
- * with the pure `initialTransition` helper of XState: the chain of the initial states
109
- * and their `meta.route` templates are fixed at the moment of the machine
110
- * definition, and the substitution of a `:param` uses the real initial context of
111
- * the machine for the `input` of this actor. The code makes no second actor, and a
112
- * snapshot of a restore changes the value never: it is always the **default**
113
- * initial route of the machine.
114
- *
115
- * Without a snapshot of a restore, the pre-start snapshot of this actor IS that
116
- * default initial state, so the code derives the route from it directly. A restore
117
- * alone needs `initialTransition`, whose inert actor scope runs the initial
118
- * transition two more times, one of them with an undefined `input`. That is a quirk
119
- * of XState, and it costs too much for every other case.
120
- */
121
- initialRoute = this.playerRestoredSnapshot === undefined
122
- ? deriveCurrentRoute(this.getSnapshot())
123
- : deriveInitialRoute(this.playerMachine, this.playerInput);
124
- }
125
- return RoutableActor;
10
+ * Adds the routing capability to an actor class.
11
+ *
12
+ * The result satisfies `Routable`, so a router bridge accepts it. A class without this
13
+ * mixin exposes no `currentRoute`, and a bridge therefore refuses it when it compiles,
14
+ * rather than at the first navigation.
15
+ *
16
+ * The mixin declares NO constructor, and that is deliberate: TypeScript demands the
17
+ * signature `...args: any[]` from a mixin over a generic base (TS2545), and this
18
+ * workspace writes no `any`. A field initializer of a subclass runs after `super()`
19
+ * instead, and it reads the inputs that `PlayerActor` keeps for a capability.
20
+ *
21
+ * @param Base - The actor class to extend, normally `PlayerActor`.
22
+ */
23
+ function withRouting(Base) {
24
+ class RoutableActor extends Base {
25
+ /**
26
+ * A computed atom. It derives the current URL path from the `meta.route`
27
+ * template of the active machine state and from the context of the actor.
28
+ *
29
+ * It returns `null` when the current state has no `meta.route` field, and also when
30
+ * a necessary `:param` has no value in the context. The second case is normally
31
+ * temporary during a transition, and the atom computes the value again on the next
32
+ * snapshot.
33
+ *
34
+ * It THROWS never. A derivation of this architecture reports a failure through
35
+ * `onError` and keeps its last good value, and `currentView` of
36
+ * `@xmachines/play-xstate/view` follows the same rule.
37
+ *
38
+ * The reason is the propagation. A subscriber makes this atom eager, so the
39
+ * derivation runs inside the `set` of `state`, which runs inside the XState
40
+ * subscription. A throw that left this function would unwind into `actor.send` and
41
+ * break the transition for EVERY other observer of the actor.
42
+ *
43
+ * The containment therefore belongs to the derivation, and not to each subscriber:
44
+ * a consumer that writes its own `watchAtom(actor.currentRoute, ...)` is protected
45
+ * by the same rule that protects a router bridge.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * // It returns "/profile/alice" when context.params.userId === "alice",
50
+ * // and null while the param is still absent.
51
+ * const route = actor.currentRoute.get();
52
+ * ```
53
+ */
54
+ currentRoute = createAtom((previous) => {
55
+ try {
56
+ return deriveCurrentRoute(this.state.get());
57
+ } catch (error) {
58
+ const onError = this.hooks.onError;
59
+ if (onError) {
60
+ const reported = toError(error);
61
+ queueMicrotask(() => {
62
+ onError(this, reported);
63
+ });
64
+ }
65
+ return previous ?? null;
66
+ }
67
+ }, { compare: () => false });
68
+ /**
69
+ * The route of the initial state of the machine. The construction fixes it, and it
70
+ * never changes, also when the code restores the actor from a snapshot.
71
+ *
72
+ * A router bridge compares it with the browser URL, and it therefore separates a
73
+ * deep link (a URL that is not the initial one → the router wins) from a restore
74
+ * (the initial URL, and the actor at a different route from the restore → the actor
75
+ * wins).
76
+ *
77
+ * `deriveInitialRoute` derives the value statically from the machine definition,
78
+ * with the pure `initialTransition` helper of XState: the chain of the initial states
79
+ * and their `meta.route` templates are fixed at the moment of the machine
80
+ * definition, and the substitution of a `:param` uses the real initial context of
81
+ * the machine for the `input` of this actor. The code makes no second actor, and a
82
+ * snapshot of a restore changes the value never: it is always the **default**
83
+ * initial route of the machine.
84
+ *
85
+ * Without a snapshot of a restore, the pre-start snapshot of this actor IS that
86
+ * default initial state, so the code derives the route from it directly. A restore
87
+ * alone needs `initialTransition`, whose inert actor scope runs the initial
88
+ * transition two more times, one of them with an undefined `input`. That is a quirk
89
+ * of XState, and it costs too much for every other case.
90
+ */
91
+ initialRoute = this.playerRestoredSnapshot === void 0 ? deriveCurrentRoute(this.getSnapshot()) : deriveInitialRoute(this.playerMachine, this.playerInput);
92
+ }
93
+ return RoutableActor;
126
94
  }
127
- // The utilities of the route derivation and of the URL construction.
128
- //
129
- // They belong HERE, and no longer on the main entry. Each one names
130
- // `@xmachines/play-router` in its own `.d.ts` — `PlayRouteEvent`, `RouteData`,
131
- // `RouteObject` and `RouteMetadata` all have their single definition there. That
132
- // package is an OPTIONAL peer, so a main entry that reached them gave every consumer
133
- // of `definePlayer` a type graph that names a package their node_modules does not
134
- // hold. `skipLibCheck` hides the fault and does not remove it: the types resolve to
135
- // nothing.
136
- //
137
- // The view half exposes nothing on the main entry, for the same reason. The two
138
- // capabilities hold one shape.
139
- export { deriveRoute, isAbsoluteRoute, buildRouteUrl, formatPlayRouteTransitions, } from "./routing/index.js";
95
+ //#endregion
96
+ export { buildRouteUrl, deriveRoute, formatPlayRouteTransitions, isAbsoluteRoute, withRouting };
97
+
140
98
  //# sourceMappingURL=with-routing.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"with-routing.js","sourceRoot":"","sources":["../src/with-routing.ts"],"names":[],"mappings":"AAyBA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAKlD,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE5C;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAC1B,IAAiC;IAEjC,MAAM,aAAc,SAAQ,IAAI;QAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA4BG;QACa,YAAY,GAAgC,UAAU,CACrE,CAAC,QAAQ,EAAiB,EAAE;YAC3B,IAAI,CAAC;gBACJ,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;YAC7C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBAChB,+EAA+E;gBAC/E,gFAAgF;gBAChF,6EAA6E;gBAC7E,8EAA8E;gBAC9E,wEAAwE;gBACxE,+EAA+E;gBAC/E,6EAA6E;gBAC7E,4EAA4E;gBAC5E,0EAA0E;gBAC1E,+EAA+E;gBAC/E,2DAA2D;gBAC3D,EAAE;gBACF,+EAA+E;gBAC/E,8EAA8E;gBAC9E,4DAA4D;gBAC5D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC;gBACnC,IAAI,OAAO,EAAE,CAAC;oBACb,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;oBAChC,cAAc,CAAC,GAAG,EAAE;wBACnB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;oBACzB,CAAC,CAAC,CAAC;gBACJ,CAAC;gBACD,OAAO,QAAQ,IAAI,IAAI,CAAC;YACzB,CAAC;QACF,CAAC,EACD;YACC,8EAA8E;YAC9E,+BAA+B;YAC/B,EAAE;YACF,+EAA+E;YAC/E,iFAAiF;YACjF,gFAAgF;YAChF,gFAAgF;YAChF,6EAA6E;YAC7E,6EAA6E;YAC7E,+EAA+E;YAC/E,EAAE;YACF,gFAAgF;YAChF,iFAAiF;YACjF,+EAA+E;YAC/E,kFAAkF;YAClF,2EAA2E;YAC3E,mCAAmC;YACnC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK;SACpB,CACD,CAAC;QAEF;;;;;;;;;;;;;;;;;;;;;;WAsBG;QACa,YAAY,GAC3B,IAAI,CAAC,sBAAsB,KAAK,SAAS;YACxC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACxC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;KAC7D;IAED,OAAO,aAAa,CAAC;AACtB,CAAC;AAED,qEAAqE;AACrE,EAAE;AACF,oEAAoE;AACpE,+EAA+E;AAC/E,iFAAiF;AACjF,qFAAqF;AACrF,kFAAkF;AAClF,oFAAoF;AACpF,WAAW;AACX,EAAE;AACF,gFAAgF;AAChF,+BAA+B;AAC/B,OAAO,EACN,WAAW,EACX,eAAe,EACf,aAAa,EACb,0BAA0B,GAC1B,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"with-routing.js","names":[],"sources":["../src/with-routing.ts"],"sourcesContent":["/**\n * The routing capability of an XState actor, as a mixin.\n *\n * `PlayerActor` holds `state` and `send`, and nothing about a route. This module adds\n * `currentRoute` and `initialRoute`, which is what `Routable` of `@xmachines/play-router`\n * asks for and what every router bridge reads.\n *\n * It is a SEPARATE entry point, `@xmachines/play-xstate/routing`, and the core imports it\n * never. `@xmachines/play-router` is an OPTIONAL peer of this package for the same reason:\n * an application that declares no route installs neither that package nor the route map\n * and the bridge that it carries.\n *\n * @example\n * ```typescript\n * import { definePlayer, PlayerActor, compose } from \"@xmachines/play-xstate\";\n * import { withRouting } from \"@xmachines/play-xstate/routing\";\n *\n * const createPlayer = definePlayer({ machine, actor: compose(PlayerActor, withRouting) });\n * const actor = createPlayer();\n * actor.currentRoute.get(); // \"/profile/alice\"\n * ```\n *\n * @packageDocumentation\n */\nimport type { AnyStateMachine } from \"xstate\";\nimport { createAtom } from \"@xmachines/play-atom\";\nimport type { ReadonlyAtom } from \"@xmachines/play-atom\";\nimport type { Routable } from \"@xmachines/play-router\";\n\nimport type { PlayerConstructor } from \"./capabilities.js\";\nimport { deriveCurrentRoute, deriveInitialRoute } from \"./routing/index.js\";\nimport { toError } from \"./player-actor.js\";\n\n/**\n * Adds the routing capability to an actor class.\n *\n * The result satisfies `Routable`, so a router bridge accepts it. A class without this\n * mixin exposes no `currentRoute`, and a bridge therefore refuses it when it compiles,\n * rather than at the first navigation.\n *\n * The mixin declares NO constructor, and that is deliberate: TypeScript demands the\n * signature `...args: any[]` from a mixin over a generic base (TS2545), and this\n * workspace writes no `any`. A field initializer of a subclass runs after `super()`\n * instead, and it reads the inputs that `PlayerActor` keeps for a capability.\n *\n * @param Base - The actor class to extend, normally `PlayerActor`.\n */\nexport function withRouting<TMachine extends AnyStateMachine>(\n\tBase: PlayerConstructor<TMachine>,\n): PlayerConstructor<TMachine, Routable> {\n\tclass RoutableActor extends Base implements Routable {\n\t\t/**\n\t\t * A computed atom. It derives the current URL path from the `meta.route`\n\t\t * template of the active machine state and from the context of the actor.\n\t\t *\n\t\t * It returns `null` when the current state has no `meta.route` field, and also when\n\t\t * a necessary `:param` has no value in the context. The second case is normally\n\t\t * temporary during a transition, and the atom computes the value again on the next\n\t\t * snapshot.\n\t\t *\n\t\t * It THROWS never. A derivation of this architecture reports a failure through\n\t\t * `onError` and keeps its last good value, and `currentView` of\n\t\t * `@xmachines/play-xstate/view` follows the same rule.\n\t\t *\n\t\t * The reason is the propagation. A subscriber makes this atom eager, so the\n\t\t * derivation runs inside the `set` of `state`, which runs inside the XState\n\t\t * subscription. A throw that left this function would unwind into `actor.send` and\n\t\t * break the transition for EVERY other observer of the actor.\n\t\t *\n\t\t * The containment therefore belongs to the derivation, and not to each subscriber:\n\t\t * a consumer that writes its own `watchAtom(actor.currentRoute, ...)` is protected\n\t\t * by the same rule that protects a router bridge.\n\t\t *\n\t\t * @example\n\t\t * ```typescript\n\t\t * // It returns \"/profile/alice\" when context.params.userId === \"alice\",\n\t\t * // and null while the param is still absent.\n\t\t * const route = actor.currentRoute.get();\n\t\t * ```\n\t\t */\n\t\tpublic readonly currentRoute: ReadonlyAtom<string | null> = createAtom(\n\t\t\t(previous): string | null => {\n\t\t\t\ttry {\n\t\t\t\t\treturn deriveCurrentRoute(this.state.get());\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// `deriveCurrentRoute` answers the EXPECTED failure itself: a missing `:param`\n\t\t\t\t\t// returns `null`, and the next snapshot resolves it. Whatever reaches this line\n\t\t\t\t\t// is unexpected — a `URIError` of `encodeURIComponent` for a lone surrogate,\n\t\t\t\t\t// for example. Report it, and keep the last route: a caller that reads `null`\n\t\t\t\t\t// or a stale route navigates nowhere, which the bridge handles already.\n\t\t\t\t\t// The derivation runs inside the `set` of `state`, and the engine TRACKS every\n\t\t\t\t\t// atom that this function reads — the handler of the host included. A report\n\t\t\t\t\t// from HERE therefore made whatever that handler reads a dependency of this\n\t\t\t\t\t// atom, and an unrelated write of it re-derived and reported again: three\n\t\t\t\t\t// reports for one failure. The microtask leaves the evaluation, so the handler\n\t\t\t\t\t// reads what it likes and this atom depends on none of it.\n\t\t\t\t\t//\n\t\t\t\t\t// It leaves the `set` as well, which is what the comment below already needed:\n\t\t\t\t\t// a throw from the handler lands on the global handler of the runtime, and it\n\t\t\t\t\t// breaks the transition for no other observer of the actor.\n\t\t\t\t\tconst onError = this.hooks.onError;\n\t\t\t\t\tif (onError) {\n\t\t\t\t\t\tconst reported = toError(error);\n\t\t\t\t\t\tqueueMicrotask(() => {\n\t\t\t\t\t\t\tonError(this, reported);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn previous ?? null;\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t// Every WRITE of `state` reaches an observer of this atom, and not the writes\n\t\t\t\t// that change the route alone.\n\t\t\t\t//\n\t\t\t\t// The default `Object.is` stops a notification for a value that the atom holds\n\t\t\t\t// already, and a route is a STRING: a guard that refuses an inbound location and\n\t\t\t\t// moves the actor back to the state it came from derives the same string, so no\n\t\t\t\t// observer heard the answer. `RouterBridgeBase` waits for exactly that answer —\n\t\t\t\t// `syncActorFromRouter` clears `lastActorRoute` before it sends, so the push\n\t\t\t\t// that puts the address bar back on the route of the machine is owed to this\n\t\t\t\t// notification — and the address bar kept the location that the guard refused.\n\t\t\t\t//\n\t\t\t\t// The bridge is written against a notification for each change of the SNAPSHOT,\n\t\t\t\t// and not for each change of the derived string, so the comparison has to answer\n\t\t\t\t// \"different\" always. A consumer that reads the value pays nothing for it: the\n\t\t\t\t// derivation still runs one time for each transition, and `state` itself compares\n\t\t\t\t// with `Object.is`, so an event that XState ignores delivers the identical\n\t\t\t\t// snapshot and propagates nothing.\n\t\t\t\tcompare: () => false,\n\t\t\t},\n\t\t);\n\n\t\t/**\n\t\t * The route of the initial state of the machine. The construction fixes it, and it\n\t\t * never changes, also when the code restores the actor from a snapshot.\n\t\t *\n\t\t * A router bridge compares it with the browser URL, and it therefore separates a\n\t\t * deep link (a URL that is not the initial one → the router wins) from a restore\n\t\t * (the initial URL, and the actor at a different route from the restore → the actor\n\t\t * wins).\n\t\t *\n\t\t * `deriveInitialRoute` derives the value statically from the machine definition,\n\t\t * with the pure `initialTransition` helper of XState: the chain of the initial states\n\t\t * and their `meta.route` templates are fixed at the moment of the machine\n\t\t * definition, and the substitution of a `:param` uses the real initial context of\n\t\t * the machine for the `input` of this actor. The code makes no second actor, and a\n\t\t * snapshot of a restore changes the value never: it is always the **default**\n\t\t * initial route of the machine.\n\t\t *\n\t\t * Without a snapshot of a restore, the pre-start snapshot of this actor IS that\n\t\t * default initial state, so the code derives the route from it directly. A restore\n\t\t * alone needs `initialTransition`, whose inert actor scope runs the initial\n\t\t * transition two more times, one of them with an undefined `input`. That is a quirk\n\t\t * of XState, and it costs too much for every other case.\n\t\t */\n\t\tpublic readonly initialRoute: string | null =\n\t\t\tthis.playerRestoredSnapshot === undefined\n\t\t\t\t? deriveCurrentRoute(this.getSnapshot())\n\t\t\t\t: deriveInitialRoute(this.playerMachine, this.playerInput);\n\t}\n\n\treturn RoutableActor;\n}\n\n// The utilities of the route derivation and of the URL construction.\n//\n// They belong HERE, and no longer on the main entry. Each one names\n// `@xmachines/play-router` in its own `.d.ts` — `PlayRouteEvent`, `RouteData`,\n// `RouteObject` and `RouteMetadata` all have their single definition there. That\n// package is an OPTIONAL peer, so a main entry that reached them gave every consumer\n// of `definePlayer` a type graph that names a package their node_modules does not\n// hold. `skipLibCheck` hides the fault and does not remove it: the types resolve to\n// nothing.\n//\n// The view half exposes nothing on the main entry, for the same reason. The two\n// capabilities hold one shape.\nexport {\n\tderiveRoute,\n\tisAbsoluteRoute,\n\tbuildRouteUrl,\n\tformatPlayRouteTransitions,\n} from \"./routing/index.js\";\nexport type {\n\tRouteMachineConfig,\n\tRouteStateNode,\n\tRouteContext,\n\tRouteObject,\n\tRouteMetadata,\n\tRouteData,\n\tRouteDataResolver,\n} from \"./routing/index.js\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,YACf,MACwC;CACxC,MAAM,sBAAsB,KAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8BpD,eAA4D,YAC1D,aAA4B;GAC5B,IAAI;IACH,OAAO,mBAAmB,KAAK,MAAM,IAAI,CAAC;GAC3C,SAAS,OAAO;IAgBf,MAAM,UAAU,KAAK,MAAM;IAC3B,IAAI,SAAS;KACZ,MAAM,WAAW,QAAQ,KAAK;KAC9B,qBAAqB;MACpB,QAAQ,MAAM,QAAQ;KACvB,CAAC;IACF;IACA,OAAO,YAAY;GACpB;EACD,GACA,EAkBC,eAAe,MAChB,CACD;;;;;;;;;;;;;;;;;;;;;;;;EAyBA,eACC,KAAK,2BAA2B,KAAA,IAC7B,mBAAmB,KAAK,YAAY,CAAC,IACrC,mBAAmB,KAAK,eAAe,KAAK,WAAW;CAC5D;CAEA,OAAO;AACR"}
@@ -1 +1 @@
1
- {"version":3,"file":"with-view.d.ts","sourceRoot":"","sources":["../src/with-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAI9C,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAsC3D;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,SAAS,eAAe,EACxD,IAAI,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAC/B,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAyFvC"}
1
+ {"version":3,"file":"with-view.d.ts","sourceRoot":"","sources":["../src/with-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAG9C,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGxF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG3D;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,SAAS,eAAe,EACxD,IAAI,EAAE,iBAAiB,CAAC,QAAQ,CAAC,GAC/B,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAuFvC"}
package/dist/with-view.js CHANGED
@@ -1,158 +1,88 @@
1
- import { createAtom } from "@xmachines/play-atom";
2
- import { shallowEqualExcept } from "@xmachines/core/utils";
3
- import { reuseComposedState } from "@xmachines/play-view";
4
1
  import { toError } from "./player-actor.js";
5
2
  import { deriveCurrentView } from "./view/derive-current-view.js";
3
+ import { createAtom } from "@xmachines/play-atom";
4
+ import { reuseComposedState } from "@xmachines/play-view";
5
+ //#region packages/play-xstate/src/with-view.ts
6
6
  /**
7
- * The structural equality of two derived view specs, with a limit on its depth.
8
- *
9
- * The function walks exactly the shape that `deriveCurrentView` builds: the spec
10
- * fields, then `elements`, then the `props` object of each element. It compares
11
- * each leaf with `Object.is`. It never enters the VALUE of a prop: a new reference
12
- * therefore emits the view again, also when the contents are equal. This design
13
- * keeps two things correct: a prop of a container (a Map, a Set, or an instance of a
14
- * class, which a structural comparison cannot see), and a cyclic value, which gives
15
- * a structural comparison a recursion without an end.
16
- */
17
- const viewSpecsEquivalent = (a, b) => {
18
- if (a === b)
19
- return true;
20
- if (!a || !b)
21
- return false;
22
- if (!shallowEqualExcept(a, b, "elements"))
23
- return false;
24
- const aElements = a.elements ?? {};
25
- const bElements = b.elements ?? {};
26
- // A derived spec spreads the same static meta.view. Therefore the elements
27
- // usually have the same reference, and the walk over each element is then not
28
- // necessary.
29
- if (aElements === bElements)
30
- return true;
31
- const elementKeys = Object.keys(aElements);
32
- if (elementKeys.length !== Object.keys(bElements).length)
33
- return false;
34
- for (const key of elementKeys) {
35
- const aElement = aElements[key]; // nosemgrep: gitlab.eslint.detect-object-injection
36
- const bElement = bElements[key]; // nosemgrep: gitlab.eslint.detect-object-injection
37
- if (aElement === bElement)
38
- continue;
39
- if (!aElement || !bElement)
40
- return false;
41
- if (!shallowEqualExcept(aElement, bElement, "props"))
42
- return false;
43
- if (!shallowEqualExcept(aElement.props ?? {}, bElement.props ?? {}))
44
- return false;
45
- }
46
- return true;
47
- };
48
- /**
49
- * Two WeakMaps stood here, and the derivation below needs neither.
50
- *
51
- * `lastViewSnapshots` skipped the derivation for a snapshot that the pipeline read
52
- * already, because XState notifies each observer on EVERY event that it processes and an
53
- * event that it ignores delivers the identical snapshot again. A computed atom answers
54
- * that by itself: `state.set(snapshot)` compares with `Object.is`, an identical reference
55
- * therefore propagates nothing, and this atom stays valid.
56
- *
57
- * `lastEmittedViews` held the previous emission for the equality gate, because a read of
58
- * `currentView` would have registered it as a dependency of the computation around the
59
- * call and `@xstate/store` exports no `untrack`. The `previous` parameter of a computed
60
- * atom is that same value, and the engine hands it in rather than tracking a read.
61
- */
7
+ * Two WeakMaps stood here, and the derivation below needs neither.
8
+ *
9
+ * `lastViewSnapshots` skipped the derivation for a snapshot that the pipeline read
10
+ * already, because XState notifies each observer on EVERY event that it processes and an
11
+ * event that it ignores delivers the identical snapshot again. A computed atom answers
12
+ * that by itself: `state.set(snapshot)` compares with `Object.is`, an identical reference
13
+ * therefore propagates nothing, and this atom stays valid.
14
+ *
15
+ * `lastEmittedViews` held the previous emission for the equality gate, because a read of
16
+ * `currentView` would have registered it as a dependency of the computation around the
17
+ * call and `@xstate/store` exports no `untrack`. The `previous` parameter of a computed
18
+ * atom is that same value, and the engine hands it in rather than tracking a read.
19
+ */
62
20
  /**
63
- * Adds the view capability to an actor class.
64
- *
65
- * The ORDER of the composition does not matter. `currentView` and `currentRoute` are two
66
- * computed atoms over `state`, and neither reads the other, so the engine evaluates both
67
- * from one write in topological order.
68
- *
69
- * @param Base - The actor class to extend, normally `PlayerActor` or the result of
70
- * another capability.
71
- */
72
- export function withView(Base) {
73
- class ViewableActor extends Base {
74
- /**
75
- * The reactive atom of the current view spec. The atom derives the spec from the
76
- * `meta.view` metadata of the active state.
77
- *
78
- * It emits a **new object reference** on each real change of the view on the screen:
79
- * the view of a different state, or a change of a param or of the context that changes
80
- * the resolved spec. A snapshot that changes no view on the screen, such as an
81
- * assign of the context alone, keeps the previous reference. A provider below the
82
- * atom therefore mounts the UI again not on every event.
83
- *
84
- * The `PlaySpec` of the emission carries the context of the machine in its composed
85
- * `state` field, under the read-only `/context` subtree. A spec therefore reads the
86
- * context, and also each URL param, through the ordinary state grammar
87
- * (`{ $state: "/context/params/section" }`). The context-projection module of
88
- * `@xmachines/play-view` holds the complete contract.
89
- *
90
- * The atom returns `null` when the current state has no `meta.view` metadata.
91
- *
92
- * Two states that each declare a `meta.view` emit two different `viewKey` values,
93
- * also when the two specs are one `typedSpec` constant: the key is the id of the
94
- * state node that declares the view. A transition between those two states therefore
95
- * emits again, and a provider seeds a new store, which removes the ephemeral state.
96
- * The ephemeral state survives a move between the children when the parent holds
97
- * the key: declare the view on the parent and none on the children, or declare
98
- * `outlets` on the parent, which keeps its id as the `viewKey` of the composed view.
99
- *
100
- * It is a COMPUTED atom over `state`, and not a written one. One write for each
101
- * transition therefore reaches the whole actor — `state.set(snapshot)` — and the
102
- * engine evaluates this atom and `currentRoute` in one propagation, in topological
103
- * order. A second write would flush a second time, and an observer that reads two
104
- * atoms would see the new state beside the old view in between.
105
- */
106
- currentView = createAtom((previous) => {
107
- // `previous` is the value that this atom holds already. The engine hands it in,
108
- // so the gate below reads it without a `get()`, and it therefore registers
109
- // `currentView` as a dependency of nothing.
110
- const lastEmittedView = previous ?? null;
111
- try {
112
- const view = deriveCurrentView(this.state.get());
113
- // Emit only after a real change of the view on the screen: deriveCurrentView
114
- // returns a fresh object on each call, and the identity of the reference
115
- // therefore tells nothing. A new reference for a snapshot that changes the view
116
- // not, for example a context-only assign, makes a provider below mount the UI
117
- // again, and that removes the state of the view. A deep equality test is
118
- // deliberately NOT here: it sees nothing inside a Map or a Set, and it
119
- // therefore stops a real change, and it recurses without an end on a cyclic
120
- // prop.
121
- //
122
- // A return of `lastEmittedView` compares equal under `Object.is`, so the atom
123
- // notifies no subscriber and the propagation stops here.
124
- const nextView = reuseComposedState(lastEmittedView, view);
125
- return viewSpecsEquivalent(lastEmittedView, nextView)
126
- ? lastEmittedView
127
- : nextView;
128
- }
129
- catch (error) {
130
- // The derivation runs inside the `set` of `state`, which runs inside the XState
131
- // subscription. A throw that leaves this function therefore leaves that `set`
132
- // and breaks the transition for every other observer of the actor. Report it,
133
- // and keep the last valid view.
134
- //
135
- // The derivation runs inside the `set` of `state`, and the engine TRACKS every
136
- // atom that this function reads — the handler of the host included. A report
137
- // from HERE therefore made whatever that handler reads a dependency of this
138
- // atom, and an unrelated write of it re-derived and reported again: three
139
- // reports for one failure. The microtask leaves the evaluation, so the handler
140
- // reads what it likes and this atom depends on none of it.
141
- //
142
- // It leaves the `set` as well, which is what the comment below already needed:
143
- // a throw from the handler lands on the global handler of the runtime, and it
144
- // breaks the transition for no other observer of the actor.
145
- const onError = this.hooks.onError;
146
- if (onError) {
147
- const reported = toError(error);
148
- queueMicrotask(() => {
149
- onError(this, reported);
150
- });
151
- }
152
- return lastEmittedView;
153
- }
154
- });
155
- }
156
- return ViewableActor;
21
+ * Adds the view capability to an actor class.
22
+ *
23
+ * The ORDER of the composition does not matter. `currentView` and `currentRoute` are two
24
+ * computed atoms over `state`, and neither reads the other, so the engine evaluates both
25
+ * from one write in topological order.
26
+ *
27
+ * @param Base - The actor class to extend, normally `PlayerActor` or the result of
28
+ * another capability.
29
+ */
30
+ function withView(Base) {
31
+ class ViewableActor extends Base {
32
+ /**
33
+ * The reactive atom of the current view spec. The atom derives the spec from the
34
+ * `meta.view` metadata of the active state.
35
+ *
36
+ * It emits a **new object reference** for the view of a different state, and for a
37
+ * change of a value of the context, which the spec reads under `/context`. A snapshot
38
+ * that changes neither keeps the previous reference: an event that the machine
39
+ * ignores, a transition inside a region that no outlet shows, or an assign that
40
+ * writes the same values again. The viewKey of a new emission tells a provider
41
+ * whether to seed its store again or to refresh `/context` in place, so the UI
42
+ * mounts again only for a new viewKey.
43
+ *
44
+ * The `PlaySpec` of the emission carries the context of the machine in its composed
45
+ * `state` field, under the read-only `/context` subtree. A spec therefore reads the
46
+ * context, and also each URL param, through the ordinary state grammar
47
+ * (`{ $state: "/context/params/section" }`). The context-projection module of
48
+ * `@xmachines/play-view` holds the complete contract.
49
+ *
50
+ * The atom returns `null` when the current state has no `meta.view` metadata.
51
+ *
52
+ * Two states that each declare a `meta.view` emit two different `viewKey` values,
53
+ * also when the two specs are one `typedSpec` constant: the key is the id of the
54
+ * state node that declares the view. A transition between those two states therefore
55
+ * emits again, and a provider seeds a new store, which removes the ephemeral state.
56
+ * The ephemeral state survives a move between the children when the parent holds
57
+ * the key: declare the view on the parent and none on the children, or declare
58
+ * `outlets` on the parent, which keeps its id as the `viewKey` of the composed view.
59
+ *
60
+ * It is a COMPUTED atom over `state`, and not a written one. One write for each
61
+ * transition therefore reaches the whole actor — `state.set(snapshot)` — and the
62
+ * engine evaluates this atom and `currentRoute` in one propagation, in topological
63
+ * order. A second write would flush a second time, and an observer that reads two
64
+ * atoms would see the new state beside the old view in between.
65
+ */
66
+ currentView = createAtom((previous) => {
67
+ const lastEmittedView = previous ?? null;
68
+ try {
69
+ const view = deriveCurrentView(this.state.get());
70
+ return reuseComposedState(lastEmittedView, view);
71
+ } catch (error) {
72
+ const onError = this.hooks.onError;
73
+ if (onError) {
74
+ const reported = toError(error);
75
+ queueMicrotask(() => {
76
+ onError(this, reported);
77
+ });
78
+ }
79
+ return lastEmittedView;
80
+ }
81
+ });
82
+ }
83
+ return ViewableActor;
157
84
  }
85
+ //#endregion
86
+ export { withView };
87
+
158
88
  //# sourceMappingURL=with-view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"with-view.js","sourceRoot":"","sources":["../src/with-view.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"with-view.js","names":[],"sources":["../src/with-view.ts"],"sourcesContent":["/**\n * The view capability of an XState actor, as a mixin.\n *\n * `PlayerActor` holds `state` and `send`, and nothing about a view. This module adds\n * `currentView`, which is what `Viewable` of `@xmachines/play-view` asks for and what\n * every renderer reads.\n *\n * It is a SEPARATE entry point, `@xmachines/play-xstate/view`, and the core imports it\n * never. `@xmachines/play-view` is an OPTIONAL peer of this package for the same reason:\n * an application that renders no view installs neither that package nor the json-render\n * types that it names.\n *\n * @example\n * ```typescript\n * import { definePlayer, PlayerActor, compose } from \"@xmachines/play-xstate\";\n * import { withRouting } from \"@xmachines/play-xstate/routing\";\n * import { withView } from \"@xmachines/play-xstate/view\";\n *\n * const createPlayer = definePlayer({\n * machine,\n * actor: compose(PlayerActor, withRouting, withView),\n * });\n * ```\n *\n * @packageDocumentation\n */\nimport type { AnyStateMachine } from \"xstate\";\nimport { createAtom } from \"@xmachines/play-atom\";\nimport type { ReadonlyAtom } from \"@xmachines/play-atom\";\nimport { reuseComposedState, type PlaySpec, type Viewable } from \"@xmachines/play-view\";\n\nimport { toError } from \"./player-actor.js\";\nimport type { PlayerConstructor } from \"./capabilities.js\";\nimport { deriveCurrentView } from \"./view/derive-current-view.js\";\n\n/**\n * Two WeakMaps stood here, and the derivation below needs neither.\n *\n * `lastViewSnapshots` skipped the derivation for a snapshot that the pipeline read\n * already, because XState notifies each observer on EVERY event that it processes and an\n * event that it ignores delivers the identical snapshot again. A computed atom answers\n * that by itself: `state.set(snapshot)` compares with `Object.is`, an identical reference\n * therefore propagates nothing, and this atom stays valid.\n *\n * `lastEmittedViews` held the previous emission for the equality gate, because a read of\n * `currentView` would have registered it as a dependency of the computation around the\n * call and `@xstate/store` exports no `untrack`. The `previous` parameter of a computed\n * atom is that same value, and the engine hands it in rather than tracking a read.\n */\n\n/**\n * Adds the view capability to an actor class.\n *\n * The ORDER of the composition does not matter. `currentView` and `currentRoute` are two\n * computed atoms over `state`, and neither reads the other, so the engine evaluates both\n * from one write in topological order.\n *\n * @param Base - The actor class to extend, normally `PlayerActor` or the result of\n * another capability.\n */\nexport function withView<TMachine extends AnyStateMachine>(\n\tBase: PlayerConstructor<TMachine>,\n): PlayerConstructor<TMachine, Viewable> {\n\tclass ViewableActor extends Base implements Viewable {\n\t\t/**\n\t\t * The reactive atom of the current view spec. The atom derives the spec from the\n\t\t * `meta.view` metadata of the active state.\n\t\t *\n\t\t * It emits a **new object reference** for the view of a different state, and for a\n\t\t * change of a value of the context, which the spec reads under `/context`. A snapshot\n\t\t * that changes neither keeps the previous reference: an event that the machine\n\t\t * ignores, a transition inside a region that no outlet shows, or an assign that\n\t\t * writes the same values again. The viewKey of a new emission tells a provider\n\t\t * whether to seed its store again or to refresh `/context` in place, so the UI\n\t\t * mounts again only for a new viewKey.\n\t\t *\n\t\t * The `PlaySpec` of the emission carries the context of the machine in its composed\n\t\t * `state` field, under the read-only `/context` subtree. A spec therefore reads the\n\t\t * context, and also each URL param, through the ordinary state grammar\n\t\t * (`{ $state: \"/context/params/section\" }`). The context-projection module of\n\t\t * `@xmachines/play-view` holds the complete contract.\n\t\t *\n\t\t * The atom returns `null` when the current state has no `meta.view` metadata.\n\t\t *\n\t\t * Two states that each declare a `meta.view` emit two different `viewKey` values,\n\t\t * also when the two specs are one `typedSpec` constant: the key is the id of the\n\t\t * state node that declares the view. A transition between those two states therefore\n\t\t * emits again, and a provider seeds a new store, which removes the ephemeral state.\n\t\t * The ephemeral state survives a move between the children when the parent holds\n\t\t * the key: declare the view on the parent and none on the children, or declare\n\t\t * `outlets` on the parent, which keeps its id as the `viewKey` of the composed view.\n\t\t *\n\t\t * It is a COMPUTED atom over `state`, and not a written one. One write for each\n\t\t * transition therefore reaches the whole actor — `state.set(snapshot)` — and the\n\t\t * engine evaluates this atom and `currentRoute` in one propagation, in topological\n\t\t * order. A second write would flush a second time, and an observer that reads two\n\t\t * atoms would see the new state beside the old view in between.\n\t\t */\n\t\tpublic readonly currentView: ReadonlyAtom<PlaySpec | null> = createAtom(\n\t\t\t(previous): PlaySpec | null => {\n\t\t\t\t// `previous` is the value that this atom holds already. The engine hands it in,\n\t\t\t\t// so the gate below reads it without a `get()`, and it therefore registers\n\t\t\t\t// `currentView` as a dependency of nothing.\n\t\t\t\tconst lastEmittedView = previous ?? null;\n\n\t\t\t\ttry {\n\t\t\t\t\tconst view = deriveCurrentView(this.state.get());\n\n\t\t\t\t\t// Emit only after a real change: deriveCurrentView returns a fresh object on\n\t\t\t\t\t// each call, and the identity of the reference therefore tells nothing. The\n\t\t\t\t\t// derivation keeps `elements` and `viewParts` for one tree of views, so the\n\t\t\t\t\t// gate compares the top-level fields and the context one level deep. A deep\n\t\t\t\t\t// equality test is deliberately NOT here: it sees nothing inside a Map or a\n\t\t\t\t\t// Set, and it therefore stops a real change, and it recurses without an end\n\t\t\t\t\t// on a cyclic prop.\n\t\t\t\t\t//\n\t\t\t\t\t// A return of `lastEmittedView` compares equal under `Object.is`, so the atom\n\t\t\t\t\t// notifies no subscriber and the propagation stops here.\n\t\t\t\t\treturn reuseComposedState(lastEmittedView, view);\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// The derivation runs inside the `set` of `state`, which runs inside the XState\n\t\t\t\t\t// subscription. A throw that leaves this function therefore leaves that `set`\n\t\t\t\t\t// and breaks the transition for every other observer of the actor. Report it,\n\t\t\t\t\t// and keep the last valid view.\n\t\t\t\t\t//\n\t\t\t\t\t// The derivation runs inside the `set` of `state`, and the engine TRACKS every\n\t\t\t\t\t// atom that this function reads — the handler of the host included. A report\n\t\t\t\t\t// from HERE therefore made whatever that handler reads a dependency of this\n\t\t\t\t\t// atom, and an unrelated write of it re-derived and reported again: three\n\t\t\t\t\t// reports for one failure. The microtask leaves the evaluation, so the handler\n\t\t\t\t\t// reads what it likes and this atom depends on none of it.\n\t\t\t\t\t//\n\t\t\t\t\t// It leaves the `set` as well, which is what the comment below already needed:\n\t\t\t\t\t// a throw from the handler lands on the global handler of the runtime, and it\n\t\t\t\t\t// breaks the transition for no other observer of the actor.\n\t\t\t\t\tconst onError = this.hooks.onError;\n\t\t\t\t\tif (onError) {\n\t\t\t\t\t\tconst reported = toError(error);\n\t\t\t\t\t\tqueueMicrotask(() => {\n\t\t\t\t\t\t\tonError(this, reported);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn lastEmittedView;\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\treturn ViewableActor;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAgB,SACf,MACwC;CACxC,MAAM,sBAAsB,KAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmCpD,cAA6D,YAC3D,aAA8B;GAI9B,MAAM,kBAAkB,YAAY;GAEpC,IAAI;IACH,MAAM,OAAO,kBAAkB,KAAK,MAAM,IAAI,CAAC;IAY/C,OAAO,mBAAmB,iBAAiB,IAAI;GAChD,SAAS,OAAO;IAgBf,MAAM,UAAU,KAAK,MAAM;IAC3B,IAAI,SAAS;KACZ,MAAM,WAAW,QAAQ,KAAK;KAC9B,qBAAqB;MACpB,QAAQ,MAAM,QAAQ;KACvB,CAAC;IACF;IACA,OAAO;GACR;EACD,CACD;CACD;CAEA,OAAO;AACR"}