@xmachines/play-xstate 6.0.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 (45) hide show
  1. package/README.md +87 -28
  2. package/dist/capabilities.d.ts +2 -2
  3. package/dist/capabilities.js +6 -2
  4. package/dist/capabilities.js.map +1 -1
  5. package/dist/define-player.js +8 -8
  6. package/dist/define-player.js.map +1 -1
  7. package/dist/errors.js +262 -259
  8. package/dist/errors.js.map +1 -1
  9. package/dist/index.js +4 -25
  10. package/dist/player-actor.js +318 -448
  11. package/dist/player-actor.js.map +1 -1
  12. package/dist/routing/build-url.js +74 -108
  13. package/dist/routing/build-url.js.map +1 -1
  14. package/dist/routing/derive-current-route.d.ts.map +1 -1
  15. package/dist/routing/derive-current-route.js +46 -67
  16. package/dist/routing/derive-current-route.js.map +1 -1
  17. package/dist/routing/derive-initial-route.js +53 -64
  18. package/dist/routing/derive-initial-route.js.map +1 -1
  19. package/dist/routing/derive-route.js +225 -242
  20. package/dist/routing/derive-route.js.map +1 -1
  21. package/dist/routing/format-play-route-transitions.d.ts.map +1 -1
  22. package/dist/routing/format-play-route-transitions.js +192 -257
  23. package/dist/routing/format-play-route-transitions.js.map +1 -1
  24. package/dist/state-meta.d.ts +84 -28
  25. package/dist/state-meta.d.ts.map +1 -1
  26. package/dist/state-meta.js +170 -72
  27. package/dist/state-meta.js.map +1 -1
  28. package/dist/view/derive-current-view.d.ts +18 -10
  29. package/dist/view/derive-current-view.d.ts.map +1 -1
  30. package/dist/view/derive-current-view.js +249 -128
  31. package/dist/view/derive-current-view.js.map +1 -1
  32. package/dist/with-routing.js +94 -136
  33. package/dist/with-routing.js.map +1 -1
  34. package/dist/with-view.d.ts +1 -3
  35. package/dist/with-view.d.ts.map +1 -1
  36. package/dist/with-view.js +82 -152
  37. package/dist/with-view.js.map +1 -1
  38. package/package.json +19 -19
  39. package/dist/index.js.map +0 -1
  40. package/dist/routing/index.js +0 -14
  41. package/dist/routing/index.js.map +0 -1
  42. package/dist/routing/types.js +0 -2
  43. package/dist/routing/types.js.map +0 -1
  44. package/dist/types.js +0 -2
  45. package/dist/types.js.map +0 -1
@@ -1,270 +1,205 @@
1
- import { assign } from "xstate";
2
- import { shallowEqualExcept } from "@xmachines/core/utils";
3
1
  import { MissingStateIdError } from "../errors.js";
4
2
  import { normalizeRoute, routeData, routeHandler, routeReenter } from "./derive-route.js";
3
+ import { shallowEqualExcept } from "@xmachines/core/utils";
4
+ import { assign } from "xstate";
5
+ //#region packages/play-xstate/src/routing/format-play-route-transitions.ts
5
6
  /**
6
- * Reuses the previous container of the params and of the query when the new
7
- * container is equal to it in a shallow comparison.
8
- *
9
- * The params and the query of a route arrive in a FRESH object on every `play.route`
10
- * event: a bridge builds them again for each parse of a URL, and `|| {}` allocates
11
- * an object also for the empty case. The emit gate of the view compares the context
12
- * field by field, with `Object.is`. Without this reuse, a navigation to the same
13
- * values, such as a popstate event to the current URL or a second send from the
14
- * code, emits the view again for nothing.
15
- */
16
- const keepEqualContainer = (prev, next) => prev !== undefined && shallowEqualExcept(prev, next) ? prev : next;
7
+ * Reuses the previous container of the params and of the query when the new
8
+ * container is equal to it in a shallow comparison.
9
+ *
10
+ * The params and the query of a route arrive in a FRESH object on every `play.route`
11
+ * event: a bridge builds them again for each parse of a URL, and `|| {}` allocates
12
+ * an object also for the empty case. The emit gate of the view compares the context
13
+ * field by field, with `Object.is`. Without this reuse, a navigation to the same
14
+ * values, such as a popstate event to the current URL or a second send from the
15
+ * code, emits the view again for nothing.
16
+ */
17
+ var keepEqualContainer = (prev, next) => prev !== void 0 && shallowEqualExcept(prev, next) ? prev : next;
17
18
  /**
18
- * Normalizes the `play.route` entry of an `on` record into an array.
19
- *
20
- * A user writes one transition or an array of them, and XState accepts both forms.
21
- * The merge therefore needs one form.
22
- */
23
- const asTransitionArray = (value) => value === undefined ? [] : Array.isArray(value) ? value : [value];
19
+ * Normalizes the `play.route` entry of an `on` record into an array.
20
+ *
21
+ * A user writes one transition or an array of them, and XState accepts both forms.
22
+ * The merge therefore needs one form.
23
+ */
24
+ var asTransitionArray = (value) => value === void 0 ? [] : Array.isArray(value) ? value : [value];
24
25
  /**
25
- * True when the route declares a `data` field.
26
- *
27
- * The assigner below reads this ONE time, when it builds the action, and not on every
28
- * event: the declaration of a state is static.
29
- */
30
- const declaresRouteData = (route) => typeof route === "object" && route !== null && route.data !== undefined;
26
+ * True when the route declares a `data` field.
27
+ *
28
+ * The assigner below reads this ONE time, when it builds the action, and not on every
29
+ * event: the declaration of a state is static.
30
+ */
31
+ var declaresRouteData = (route) => typeof route === "object" && route !== null && route.data !== void 0;
31
32
  /**
32
- * The `params` and the `query` of the event, each beside the container that the context
33
- * holds already. Neither one reads the metadata of the route, so both stand here rather
34
- * than inside the factory, where a call rebuilt them for every generated transition.
35
- */
36
- const assignParams = ({ context, event }) => keepEqualContainer(context.params, event.params || {});
37
- const assignQuery = ({ context, event }) => keepEqualContainer(context.query, event.query || {});
33
+ * The `params` and the `query` of the event, each beside the container that the context
34
+ * holds already. Neither one reads the metadata of the route, so both stand here rather
35
+ * than inside the factory, where a call rebuilt them for every generated transition.
36
+ */
37
+ var assignParams = ({ context, event }) => keepEqualContainer(context.params, event.params || {});
38
+ var assignQuery = ({ context, event }) => keepEqualContainer(context.query, event.query || {});
38
39
  /**
39
- * Builds the assign action of a generated `play.route` transition.
40
- *
41
- * Every generated transition gets its OWN action object, from this one factory. The
42
- * root transition and the local transition of the same state therefore hold no shared
43
- * object, and the body of the assign lives in one place.
44
- *
45
- * A route that declares no `data` gets an assign with no `data` KEY. A key that writes
46
- * the previous value back is not the same thing: `assign` puts an own property on the
47
- * context for each key that it carries, so a machine that declares no `data` field
48
- * received one with no value, and the `/context` projection of every view carried it.
49
- */
50
- const makeRouteAssign = (routeMeta) => {
51
- if (!declaresRouteData(routeMeta))
52
- return assign({ params: assignParams, query: assignQuery });
53
- return assign({
54
- params: assignParams,
55
- query: assignQuery,
56
- // A resolver that answers `undefined` leaves the field of the context as it stands,
57
- // and it therefore writes `undefined` over the data of an earlier route never.
58
- data: ({ context, event }) => {
59
- const previous = context.data;
60
- const next = routeData(routeMeta, { context, event });
61
- return next === undefined ? previous : keepEqualContainer(previous, next);
62
- },
63
- });
40
+ * Builds the assign action of a generated `play.route` transition.
41
+ *
42
+ * Every generated transition gets its OWN action object, from this one factory. The
43
+ * root transition and the local transition of the same state therefore hold no shared
44
+ * object, and the body of the assign lives in one place.
45
+ *
46
+ * A route that declares no `data` gets an assign with no `data` KEY. A key that writes
47
+ * the previous value back is not the same thing: `assign` puts an own property on the
48
+ * context for each key that it carries, so a machine that declares no `data` field
49
+ * received one with no value, and the `/context` projection of every view carried it.
50
+ */
51
+ var makeRouteAssign = (routeMeta) => {
52
+ if (!declaresRouteData(routeMeta)) return assign({
53
+ params: assignParams,
54
+ query: assignQuery
55
+ });
56
+ return assign({
57
+ params: assignParams,
58
+ query: assignQuery,
59
+ data: ({ context, event }) => {
60
+ const previous = context.data;
61
+ const next = routeData(routeMeta, {
62
+ context,
63
+ event
64
+ });
65
+ return next === void 0 ? previous : keepEqualContainer(previous, next);
66
+ }
67
+ });
64
68
  };
65
69
  /**
66
- * Rebuilds one state node with its local `play.route` transitions in place.
67
- *
68
- * The transitions of the user stay, and they come AFTER the generated ones: XState
69
- * reads the candidates in their order, so a generated guard wins when it matches, and
70
- * the transitions of the user are the fallback. This is the rule that the root of the
71
- * machine follows too.
72
- *
73
- * The function writes into NO object of the caller. It returns a new node.
74
- */
75
- const withLocalTransitions = (node, states, transitions) => {
76
- if (transitions.length === 0)
77
- return { ...node, states };
78
- const existingOn = node.on ?? {};
79
- return {
80
- ...node,
81
- states,
82
- on: {
83
- ...existingOn,
84
- "play.route": [...transitions, ...asTransitionArray(existingOn["play.route"])],
85
- },
86
- };
70
+ * Rebuilds one state node with its local `play.route` transitions in place.
71
+ *
72
+ * The transitions of the user stay, and they come AFTER the generated ones: XState
73
+ * reads the candidates in their order, so a generated guard wins when it matches, and
74
+ * the transitions of the user are the fallback. This is the rule that the root of the
75
+ * machine follows too.
76
+ *
77
+ * The function writes into NO object of the caller. It returns a new node.
78
+ */
79
+ var withLocalTransitions = (node, states, transitions) => {
80
+ if (transitions.length === 0) return {
81
+ ...node,
82
+ states
83
+ };
84
+ const existingOn = node.on ?? {};
85
+ return {
86
+ ...node,
87
+ states,
88
+ on: {
89
+ ...existingOn,
90
+ "play.route": [...transitions, ...asTransitionArray(existingOn["play.route"])]
91
+ }
92
+ };
87
93
  };
88
94
  /**
89
- * Makes the play.route transitions from the declarative route configs
90
- *
91
- * The function walks the machine states. It looks for each state with a `meta.route`
92
- * field, and it generates the transitions that handle a `play.route` event: each
93
- * transition matches event.to against a state ID.
94
- *
95
- * The internal formatRouteTransitions function of XState was the model for this
96
- * function (stateUtils.ts, line 391).
97
- *
98
- * @example
99
- * ```typescript
100
- * const machineConfig = {
101
- * id: "myMachine",
102
- * states: {
103
- * home: { id: "home", meta: { route: "/home" } },
104
- * dashboard: { id: "dashboard", meta: { route: "/dashboard" } }
105
- * }
106
- * };
107
- *
108
- * const machine = createMachine(formatPlayRouteTransitions(machineConfig));
109
- * ```
110
- *
111
- * The function generates the `play.route` handlers at the root level. Those
112
- * handlers:
113
- * - match event.to against a state ID, for example event.to === "#home"
114
- * - target the correct state
115
- * - assign the params and the query of the event to the context
116
- *
117
- * @param machineConfig - The XState machine config, before `createMachine`. It must extend `RouteMachineConfig`.
118
- * @returns The same machine config, with the generated `play.route` handlers in it. The original type `T` stays.
119
- */
120
- export function formatPlayRouteTransitions(machineConfig) {
121
- const routeTransitions = [];
122
- const collectRoutes = (states, parentPath = "") => {
123
- const localTransitions = [];
124
- let changed = false;
125
- // The walk collects PAIRS, and `Object.fromEntries` builds the record at the end.
126
- // A write through `nextStates[key]` would reach the `__proto__` setter of
127
- // `Object.prototype` for a state whose key is `"__proto__"`, and it would set the
128
- // prototype of the record instead of adding a state to it. `fromEntries` defines
129
- // an own property for every key, so no key of a config is special.
130
- const nextEntries = Object.entries(states).map(([key, stateConfig]) => {
131
- const node = stateConfig;
132
- // Build the complete path of the keys, from the root, for the target of the
133
- // transition. An XState target uses the hierarchy of the state keys, and not the
134
- // explicit IDs.
135
- const statePath = parentPath ? `${parentPath}.${key}` : key;
136
- // The function checks the metadata, and a malformed object route therefore throws an
137
- // InvalidRouteMetadataError. It then normalizes the value into the string of the
138
- // path. An empty path marks the state as a state without a route, in the string form
139
- // ("") and also in the object form ({ path: "" }).
140
- const routeMeta = node.meta?.route;
141
- const routePath = routeMeta === undefined
142
- ? ""
143
- : normalizeRoute(routeMeta, "formatPlayRouteTransitions");
144
- // Check the placement for EVERY declared route, and not only for one that
145
- // produces a transition. A state with an empty path generates none, and a typo
146
- // in its `handler` would otherwise wait silently until somebody gives the state
147
- // a path.
148
- const placement = routeHandler(routeMeta);
149
- if (routePath && !node.id) {
150
- throw new MissingStateIdError(key, routePath);
151
- }
152
- if (routePath && node.id) {
153
- // The declaration of the state decides the flag, and the default is `false`,
154
- // which is the default of XState. The flag spares the DOMAIN of the
155
- // transition: under the root placement it spares the root alone, and each
156
- // ancestor between the root and the target still runs its exit and its entry
157
- // actions. `handler` is the field that spares those.
158
- const reenter = routeReenter(routeMeta);
159
- // A parent path of "" means that the parent IS the root. The root transition of
160
- // such a state already targets `.${key}`, so it IS the local transition, and
161
- // every placement therefore collapses to the root transition.
162
- const parentIsRoot = parentPath === "";
163
- const wantsRoot = parentIsRoot || placement === "root" || placement === "both";
164
- const wantsLocal = !parentIsRoot && (placement === "local" || placement === "both");
165
- // The ROOT transition. XState installs every route transition on the root, and
166
- // this function keeps that placement as its default: a route from ANY state
167
- // therefore reaches this state.
168
- if (wantsRoot) {
169
- routeTransitions.push({
170
- target: `.${statePath}`,
171
- guard: ({ event }) => event.to === `#${node.id}`,
172
- reenter,
173
- actions: makeRouteAssign(routeMeta),
174
- });
175
- }
176
- // The LOCAL transition. It sits on the PARENT of this state, and it targets
177
- // this state relatively. XState offers the transitions of the deepest active
178
- // state node first, so this transition answers the event while the parent is
179
- // active, and the parent therefore runs its exit and its entry actions NOT
180
- // again.
181
- //
182
- // Under `"local"` this is the ONLY transition of the state, so a route that
183
- // arrives while the parent is inactive matches nothing and the actor does not
184
- // move. That is the declared intent of `"local"`: it scopes the route.
185
- if (wantsLocal) {
186
- localTransitions.push({
187
- target: `.${key}`,
188
- guard: ({ event }) => event.to === `#${node.id}`,
189
- reenter,
190
- actions: makeRouteAssign(routeMeta),
191
- });
192
- }
193
- }
194
- let nextNode = stateConfig;
195
- if (node.states) {
196
- const child = collectRoutes(node.states, statePath);
197
- if (child.changed || child.localTransitions.length > 0) {
198
- // Rebuild this node, and do NOT write into the config of the caller. The
199
- // function returns a new config, and the original must stay untouched.
200
- nextNode = withLocalTransitions(node, child.states, child.localTransitions);
201
- changed = true;
202
- }
203
- }
204
- return [key, nextNode];
205
- });
206
- return { states: Object.fromEntries(nextEntries), localTransitions, changed };
207
- };
208
- const machineStates = machineConfig.states;
209
- // `states` comes back rebuilt where a descendant asked for a local handler, and it
210
- // comes back as the original record where none did. The walk returns no local
211
- // transition for the top level: the parent of a top-level state IS the root, and the
212
- // root transition of such a state is already local to its parent.
213
- const walk = machineStates ? collectRoutes(machineStates) : undefined;
214
- // The ROOT of the machine can declare a route of its own, which a chart does when
215
- // its whole document owns a path prefix, for example `/doc` with `/doc/a` under it.
216
- // `collectRoutes` walks `states` only, so the root got no transition, and a URL for
217
- // the root moved the machine nowhere: a press on BACK to `/doc` left the actor on
218
- // the child it already held, and the URL and the actor diverged.
219
- //
220
- // The transition targets the root by its ID. XState enters the `initial` child of
221
- // an ID target, which is the state that a URL for the root means. The `reenter`
222
- // flag does NOT decide that: a route to the root lands on the initial child of the
223
- // root under `false` and under `true`. The flag decides only whether the root runs
224
- // its own `exit` and `entry` actions again. The root therefore reads the flag from
225
- // its `meta.route` like every other state, and it defaults to `false`.
226
- const rootNode = machineConfig;
227
- const rootRouteMeta = rootNode.meta?.route;
228
- const rootRoutePath = rootRouteMeta === undefined
229
- ? ""
230
- : normalizeRoute(rootRouteMeta, "formatPlayRouteTransitions");
231
- // The placement of the ROOT has no meaning, because the root has no parent to hold a
232
- // local transition. A value that the routing layer does not know must still fail
233
- // here, so that one typo behaves the same wherever an author writes it.
234
- routeHandler(rootRouteMeta);
235
- if (rootRoutePath) {
236
- if (!rootNode.id)
237
- throw new MissingStateIdError("(the root of the machine)", rootRoutePath);
238
- routeTransitions.push({
239
- target: `#${rootNode.id}`,
240
- guard: ({ event }) => event.to === `#${rootNode.id}`,
241
- reenter: routeReenter(rootRouteMeta),
242
- actions: makeRouteAssign(rootRouteMeta),
243
- });
244
- }
245
- const rebuiltStates = walk?.changed === true;
246
- // A machine whose every routable state declares `handler: "local"` produces NO root
247
- // transition, and its transitions live on the rebuilt states alone. The rebuilt
248
- // states must therefore survive on their own, and not only beside a root
249
- // transition.
250
- if (routeTransitions.length === 0 && !rebuiltStates) {
251
- return machineConfig;
252
- }
253
- const next = {
254
- ...machineConfig,
255
- ...(rebuiltStates ? { states: walk?.states } : {}),
256
- };
257
- if (routeTransitions.length > 0) {
258
- const existingOn = machineConfig.on || {};
259
- // Keep each play.route transition of the user, for example a 404 fallback that a
260
- // person wrote, and append those transitions AFTER the generated ones: XState
261
- // evaluates the candidate transitions in their order. Therefore a generated guard
262
- // wins when it matches, and the transitions of the user are the fallback.
263
- next["on"] = {
264
- ...existingOn,
265
- "play.route": [...routeTransitions, ...asTransitionArray(existingOn["play.route"])],
266
- };
267
- }
268
- return next;
95
+ * Makes the play.route transitions from the declarative route configs
96
+ *
97
+ * The function walks the machine states. It looks for each state with a `meta.route`
98
+ * field, and it generates the transitions that handle a `play.route` event: each
99
+ * transition matches event.to against a state ID.
100
+ *
101
+ * The internal formatRouteTransitions function of XState was the model for this
102
+ * function (stateUtils.ts, line 391).
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const machineConfig = {
107
+ * id: "myMachine",
108
+ * states: {
109
+ * home: { id: "home", meta: { route: "/home" } },
110
+ * dashboard: { id: "dashboard", meta: { route: "/dashboard" } }
111
+ * }
112
+ * };
113
+ *
114
+ * const machine = createMachine(formatPlayRouteTransitions(machineConfig));
115
+ * ```
116
+ *
117
+ * The function generates the `play.route` handlers at the root level. Those
118
+ * handlers:
119
+ * - match event.to against a state ID, for example event.to === "#home"
120
+ * - target the correct state
121
+ * - assign the params and the query of the event to the context
122
+ *
123
+ * @param machineConfig - The XState machine config, before `createMachine`. It must extend `RouteMachineConfig`.
124
+ * @returns The same machine config, with the generated `play.route` handlers in it. The original type `T` stays.
125
+ */
126
+ function formatPlayRouteTransitions(machineConfig) {
127
+ const routeTransitions = [];
128
+ const collectRoutes = (states, parentPath = "") => {
129
+ const localTransitions = [];
130
+ let changed = false;
131
+ const nextEntries = Object.entries(states).map(([key, stateConfig]) => {
132
+ const node = stateConfig;
133
+ const statePath = parentPath ? `${parentPath}.${key}` : key;
134
+ const routeMeta = node.meta?.route;
135
+ const routePath = routeMeta === void 0 ? "" : normalizeRoute(routeMeta, "formatPlayRouteTransitions");
136
+ const placement = routeHandler(routeMeta);
137
+ if (routePath && !node.id) throw new MissingStateIdError(key, routePath);
138
+ if (routePath && node.id) {
139
+ const reenter = routeReenter(routeMeta);
140
+ const parentIsRoot = parentPath === "";
141
+ const wantsRoot = parentIsRoot || placement === "root" || placement === "both";
142
+ const wantsLocal = !parentIsRoot && (placement === "local" || placement === "both");
143
+ if (wantsRoot) routeTransitions.push({
144
+ target: `.${statePath}`,
145
+ guard: ({ event }) => event.to === `#${node.id}`,
146
+ reenter,
147
+ actions: makeRouteAssign(routeMeta)
148
+ });
149
+ if (wantsLocal) localTransitions.push({
150
+ target: `.${key}`,
151
+ guard: ({ event }) => event.to === `#${node.id}`,
152
+ reenter,
153
+ actions: makeRouteAssign(routeMeta)
154
+ });
155
+ }
156
+ let nextNode = stateConfig;
157
+ if (node.states) {
158
+ const child = collectRoutes(node.states, statePath);
159
+ if (child.changed || child.localTransitions.length > 0) {
160
+ nextNode = withLocalTransitions(node, child.states, child.localTransitions);
161
+ changed = true;
162
+ }
163
+ }
164
+ return [key, nextNode];
165
+ });
166
+ return {
167
+ states: Object.fromEntries(nextEntries),
168
+ localTransitions,
169
+ changed
170
+ };
171
+ };
172
+ const machineStates = machineConfig.states;
173
+ const walk = machineStates ? collectRoutes(machineStates) : void 0;
174
+ const rootNode = machineConfig;
175
+ const rootRouteMeta = rootNode.meta?.route;
176
+ const rootRoutePath = rootRouteMeta === void 0 ? "" : normalizeRoute(rootRouteMeta, "formatPlayRouteTransitions");
177
+ routeHandler(rootRouteMeta);
178
+ if (rootRoutePath) {
179
+ if (!rootNode.id) throw new MissingStateIdError("(the root of the machine)", rootRoutePath);
180
+ routeTransitions.push({
181
+ target: `#${rootNode.id}`,
182
+ guard: ({ event }) => event.to === `#${rootNode.id}`,
183
+ reenter: routeReenter(rootRouteMeta),
184
+ actions: makeRouteAssign(rootRouteMeta)
185
+ });
186
+ }
187
+ const rebuiltStates = walk?.changed === true;
188
+ if (routeTransitions.length === 0 && !rebuiltStates) return machineConfig;
189
+ const next = {
190
+ ...machineConfig,
191
+ ...rebuiltStates ? { states: walk?.states } : {}
192
+ };
193
+ if (routeTransitions.length > 0) {
194
+ const existingOn = machineConfig.on || {};
195
+ next["on"] = {
196
+ ...existingOn,
197
+ "play.route": [...routeTransitions, ...asTransitionArray(existingOn["play.route"])]
198
+ };
199
+ }
200
+ return next;
269
201
  }
202
+ //#endregion
203
+ export { formatPlayRouteTransitions };
204
+
270
205
  //# sourceMappingURL=format-play-route-transitions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format-play-route-transitions.js","sourceRoot":"","sources":["../../src/routing/format-play-route-transitions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"format-play-route-transitions.js","names":[],"sources":["../../src/routing/format-play-route-transitions.ts"],"sourcesContent":["import { assign } from \"xstate\";\nimport type { PlayRouteEvent, RouteData } from \"@xmachines/play-router\";\nimport { shallowEqualExcept } from \"@xmachines/core/utils\";\nimport { MissingStateIdError } from \"../errors.js\";\nimport { normalizeRoute, routeData, routeHandler, routeReenter } from \"./derive-route.js\";\nimport type { RouteMetadata, RouteObject } from \"./types.js\";\n\n/**\n * Reuses the previous container of the params and of the query when the new\n * container is equal to it in a shallow comparison.\n *\n * The params and the query of a route arrive in a FRESH object on every `play.route`\n * event: a bridge builds them again for each parse of a URL, and `|| {}` allocates\n * an object also for the empty case. The emit gate of the view compares the context\n * field by field, with `Object.is`. Without this reuse, a navigation to the same\n * values, such as a popstate event to the current URL or a second send from the\n * code, emits the view again for nothing.\n */\nconst keepEqualContainer = <T extends Record<string, unknown>>(prev: T | undefined, next: T): T =>\n\tprev !== undefined && shallowEqualExcept(prev, next) ? prev : next;\n\n/**\n * The minimal structural shape of one XState state node, as\n * `formatPlayRouteTransitions` reads it during its walk over the machine config.\n *\n * This type holds only the fields that the function reads. Every other field of a\n * state node, such as `on`, `entry`, and `after`, passes through the index\n * signature without a change.\n */\nexport type RouteStateNode = {\n\t/**\n\t * The optional explicit state ID, for example `\"home\"` or `\"settings\"`. It is the `#id` target of a `play.route` event.\n\t */\n\tid?: string;\n\t/** The state metadata. A `meta.route` field gives the state a route. */\n\tmeta?: {\n\t\t/**\n\t\t * The template of the URL path: the string form, for example\n\t\t * `\"/profile/:username\"`, or the object form (`{ path, reenter, title }`). Both\n\t\t * match {@link RouteMetadata}. The object form declares the `reenter` flag of the\n\t\t * generated transition, and that flag defaults to `false`.\n\t\t */\n\t\troute?: RouteMetadata;\n\t};\n\t/** The nested child states. The function walks them for each further route declaration. */\n\tstates?: Record<string, RouteStateNode>;\n\t/**\n\t * The event handlers of this state. The function reads them where a child asks for a\n\t * local `play.route` handler, so that the transitions of the user survive the merge.\n\t */\n\ton?: Record<string, unknown> | undefined;\n\t[key: string]: unknown;\n};\n\n/**\n * Normalizes the `play.route` entry of an `on` record into an array.\n *\n * A user writes one transition or an array of them, and XState accepts both forms.\n * The merge therefore needs one form.\n */\nconst asTransitionArray = (value: unknown): unknown[] =>\n\tvalue === undefined ? [] : Array.isArray(value) ? value : [value];\n\n/** The arguments that XState gives to the assigner of a generated transition. */\ntype RouteAssignArgs = { context: unknown; event: PlayRouteEvent };\n\n/**\n * True when the route declares a `data` field.\n *\n * The assigner below reads this ONE time, when it builds the action, and not on every\n * event: the declaration of a state is static.\n */\nconst declaresRouteData = (route: RouteMetadata | undefined): boolean =>\n\ttypeof route === \"object\" && route !== null && (route as RouteObject).data !== undefined;\n\n/**\n * The `params` and the `query` of the event, each beside the container that the context\n * holds already. Neither one reads the metadata of the route, so both stand here rather\n * than inside the factory, where a call rebuilt them for every generated transition.\n */\nconst assignParams = ({ context, event }: RouteAssignArgs) =>\n\tkeepEqualContainer((context as { params?: Record<string, string> }).params, event.params || {});\n\nconst assignQuery = ({ context, event }: RouteAssignArgs) =>\n\tkeepEqualContainer((context as { query?: Record<string, string> }).query, event.query || {});\n\n/**\n * Builds the assign action of a generated `play.route` transition.\n *\n * Every generated transition gets its OWN action object, from this one factory. The\n * root transition and the local transition of the same state therefore hold no shared\n * object, and the body of the assign lives in one place.\n *\n * A route that declares no `data` gets an assign with no `data` KEY. A key that writes\n * the previous value back is not the same thing: `assign` puts an own property on the\n * context for each key that it carries, so a machine that declares no `data` field\n * received one with no value, and the `/context` projection of every view carried it.\n */\nconst makeRouteAssign = (routeMeta: RouteMetadata | undefined) => {\n\tif (!declaresRouteData(routeMeta)) return assign({ params: assignParams, query: assignQuery });\n\n\treturn assign({\n\t\tparams: assignParams,\n\t\tquery: assignQuery,\n\t\t// A resolver that answers `undefined` leaves the field of the context as it stands,\n\t\t// and it therefore writes `undefined` over the data of an earlier route never.\n\t\tdata: ({ context, event }: RouteAssignArgs) => {\n\t\t\tconst previous = (context as { data?: RouteData }).data;\n\t\t\tconst next = routeData(routeMeta, { context, event });\n\t\t\treturn next === undefined ? previous : keepEqualContainer(previous, next);\n\t\t},\n\t});\n};\n\n/**\n * Rebuilds one state node with its local `play.route` transitions in place.\n *\n * The transitions of the user stay, and they come AFTER the generated ones: XState\n * reads the candidates in their order, so a generated guard wins when it matches, and\n * the transitions of the user are the fallback. This is the rule that the root of the\n * machine follows too.\n *\n * The function writes into NO object of the caller. It returns a new node.\n */\nconst withLocalTransitions = (\n\tnode: RouteStateNode,\n\tstates: Record<string, unknown>,\n\ttransitions: RouteTransition[],\n): unknown => {\n\tif (transitions.length === 0) return { ...node, states };\n\n\tconst existingOn = node.on ?? {};\n\treturn {\n\t\t...node,\n\t\tstates,\n\t\ton: {\n\t\t\t...existingOn,\n\t\t\t\"play.route\": [...transitions, ...asTransitionArray(existingOn[\"play.route\"])],\n\t\t},\n\t};\n};\n\ntype RouteGuardArgs = {\n\tevent: PlayRouteEvent;\n};\n\ntype RouteTransition = {\n\ttarget: string;\n\tguard: (args: RouteGuardArgs) => boolean;\n\t/**\n\t * The `reenter` flag of this transition. `meta.route` declares it, and the default\n\t * is `false`. The type is `boolean` and not the literal `true`, because the\n\t * declaration of the state, and not this function, decides the value.\n\t */\n\treenter: boolean;\n\t/**\n\t * The assign action of this transition. The type comes from\n\t * {@link makeRouteAssign}, and not from the bare `ReturnType<typeof assign>`: the\n\t * bare form widens the event of the action to `AnyEventObject`, and `event.params`\n\t * is then `any`. This form keeps `PlayRouteEvent`, and the compiler therefore\n\t * checks the body of the assign.\n\t */\n\tactions: ReturnType<typeof makeRouteAssign>;\n};\n\n/**\n * The minimal structural constraint of a machine config that\n * `formatPlayRouteTransitions` accepts.\n *\n * The constraint is loose on purpose. The function therefore accepts the bare config\n * object of `createMachine` and also the stricter config of\n * `setup().createMachine`, and the call site needs no type cast. The generic\n * parameter `T extends RouteMachineConfig` of `formatPlayRouteTransitions` keeps the\n * original concrete type through the transform. Therefore\n * `setup().createMachine()` accepts the return value directly.\n */\nexport type RouteMachineConfig = {\n\tcontext?: unknown;\n\tstates?: Record<string, unknown> | undefined;\n\ton?: Record<string, unknown> | undefined;\n\t[key: string]: unknown;\n};\n\n/**\n * Makes the play.route transitions from the declarative route configs\n *\n * The function walks the machine states. It looks for each state with a `meta.route`\n * field, and it generates the transitions that handle a `play.route` event: each\n * transition matches event.to against a state ID.\n *\n * The internal formatRouteTransitions function of XState was the model for this\n * function (stateUtils.ts, line 391).\n *\n * @example\n * ```typescript\n * const machineConfig = {\n * id: \"myMachine\",\n * states: {\n * home: { id: \"home\", meta: { route: \"/home\" } },\n * dashboard: { id: \"dashboard\", meta: { route: \"/dashboard\" } }\n * }\n * };\n *\n * const machine = createMachine(formatPlayRouteTransitions(machineConfig));\n * ```\n *\n * The function generates the `play.route` handlers at the root level. Those\n * handlers:\n * - match event.to against a state ID, for example event.to === \"#home\"\n * - target the correct state\n * - assign the params and the query of the event to the context\n *\n * @param machineConfig - The XState machine config, before `createMachine`. It must extend `RouteMachineConfig`.\n * @returns The same machine config, with the generated `play.route` handlers in it. The original type `T` stays.\n */\nexport function formatPlayRouteTransitions<T extends RouteMachineConfig>(machineConfig: T): T {\n\tconst routeTransitions: RouteTransition[] = [];\n\n\t/**\n\t * The result of the walk over one `states` record.\n\t *\n\t * `localTransitions` holds the transitions that belong on the node that OWNS the\n\t * record, and not on the nodes inside it. `states` is the record again, rebuilt\n\t * where a descendant asked for a local handler. `changed` tells the caller whether\n\t * it must keep the rebuilt record.\n\t */\n\ttype WalkResult = {\n\t\tstates: Record<string, unknown>;\n\t\tlocalTransitions: RouteTransition[];\n\t\tchanged: boolean;\n\t};\n\n\tconst collectRoutes = (states: Record<string, unknown>, parentPath = \"\"): WalkResult => {\n\t\tconst localTransitions: RouteTransition[] = [];\n\t\tlet changed = false;\n\n\t\t// The walk collects PAIRS, and `Object.fromEntries` builds the record at the end.\n\t\t// A write through `nextStates[key]` would reach the `__proto__` setter of\n\t\t// `Object.prototype` for a state whose key is `\"__proto__\"`, and it would set the\n\t\t// prototype of the record instead of adding a state to it. `fromEntries` defines\n\t\t// an own property for every key, so no key of a config is special.\n\t\tconst nextEntries = Object.entries(states).map(([key, stateConfig]): [string, unknown] => {\n\t\t\tconst node = stateConfig as RouteStateNode;\n\t\t\t// Build the complete path of the keys, from the root, for the target of the\n\t\t\t// transition. An XState target uses the hierarchy of the state keys, and not the\n\t\t\t// explicit IDs.\n\t\t\tconst statePath = parentPath ? `${parentPath}.${key}` : key;\n\t\t\t// The function checks the metadata, and a malformed object route therefore throws an\n\t\t\t// InvalidRouteMetadataError. It then normalizes the value into the string of the\n\t\t\t// path. An empty path marks the state as a state without a route, in the string form\n\t\t\t// (\"\") and also in the object form ({ path: \"\" }).\n\t\t\tconst routeMeta = node.meta?.route;\n\t\t\tconst routePath =\n\t\t\t\trouteMeta === undefined\n\t\t\t\t\t? \"\"\n\t\t\t\t\t: normalizeRoute(routeMeta, \"formatPlayRouteTransitions\");\n\n\t\t\t// Check the placement for EVERY declared route, and not only for one that\n\t\t\t// produces a transition. A state with an empty path generates none, and a typo\n\t\t\t// in its `handler` would otherwise wait silently until somebody gives the state\n\t\t\t// a path.\n\t\t\tconst placement = routeHandler(routeMeta);\n\n\t\t\tif (routePath && !node.id) {\n\t\t\t\tthrow new MissingStateIdError(key, routePath);\n\t\t\t}\n\n\t\t\tif (routePath && node.id) {\n\t\t\t\t// The declaration of the state decides the flag, and the default is `false`,\n\t\t\t\t// which is the default of XState. The flag spares the DOMAIN of the\n\t\t\t\t// transition: under the root placement it spares the root alone, and each\n\t\t\t\t// ancestor between the root and the target still runs its exit and its entry\n\t\t\t\t// actions. `handler` is the field that spares those.\n\t\t\t\tconst reenter = routeReenter(routeMeta);\n\n\t\t\t\t// A parent path of \"\" means that the parent IS the root. The root transition of\n\t\t\t\t// such a state already targets `.${key}`, so it IS the local transition, and\n\t\t\t\t// every placement therefore collapses to the root transition.\n\t\t\t\tconst parentIsRoot = parentPath === \"\";\n\t\t\t\tconst wantsRoot = parentIsRoot || placement === \"root\" || placement === \"both\";\n\t\t\t\tconst wantsLocal = !parentIsRoot && (placement === \"local\" || placement === \"both\");\n\n\t\t\t\t// The ROOT transition. XState installs every route transition on the root, and\n\t\t\t\t// this function keeps that placement as its default: a route from ANY state\n\t\t\t\t// therefore reaches this state.\n\t\t\t\tif (wantsRoot) {\n\t\t\t\t\trouteTransitions.push({\n\t\t\t\t\t\ttarget: `.${statePath}`,\n\t\t\t\t\t\tguard: ({ event }) => event.to === `#${node.id}`,\n\t\t\t\t\t\treenter,\n\t\t\t\t\t\tactions: makeRouteAssign(routeMeta),\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// The LOCAL transition. It sits on the PARENT of this state, and it targets\n\t\t\t\t// this state relatively. XState offers the transitions of the deepest active\n\t\t\t\t// state node first, so this transition answers the event while the parent is\n\t\t\t\t// active. The parent therefore runs its exit and its entry actions again only\n\t\t\t\t// where the state declares `reenter: true`.\n\t\t\t\t//\n\t\t\t\t// Under `\"local\"` this is the ONLY transition of the state, so a route that\n\t\t\t\t// arrives while the parent is inactive matches nothing and the actor does not\n\t\t\t\t// move. That is the declared intent of `\"local\"`: it scopes the route.\n\t\t\t\tif (wantsLocal) {\n\t\t\t\t\tlocalTransitions.push({\n\t\t\t\t\t\ttarget: `.${key}`,\n\t\t\t\t\t\tguard: ({ event }) => event.to === `#${node.id}`,\n\t\t\t\t\t\treenter,\n\t\t\t\t\t\tactions: makeRouteAssign(routeMeta),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet nextNode: unknown = stateConfig;\n\n\t\t\tif (node.states) {\n\t\t\t\tconst child = collectRoutes(node.states, statePath);\n\t\t\t\tif (child.changed || child.localTransitions.length > 0) {\n\t\t\t\t\t// Rebuild this node, and do NOT write into the config of the caller. The\n\t\t\t\t\t// function returns a new config, and the original must stay untouched.\n\t\t\t\t\tnextNode = withLocalTransitions(node, child.states, child.localTransitions);\n\t\t\t\t\tchanged = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn [key, nextNode];\n\t\t});\n\n\t\treturn { states: Object.fromEntries(nextEntries), localTransitions, changed };\n\t};\n\n\tconst machineStates = machineConfig.states;\n\t// `states` comes back rebuilt where a descendant asked for a local handler, and it\n\t// comes back as the original record where none did. The walk returns no local\n\t// transition for the top level: the parent of a top-level state IS the root, and the\n\t// root transition of such a state is already local to its parent.\n\tconst walk = machineStates ? collectRoutes(machineStates) : undefined;\n\n\t// The ROOT of the machine can declare a route of its own, which a chart does when\n\t// its whole document owns a path prefix, for example `/doc` with `/doc/a` under it.\n\t// `collectRoutes` walks `states` only, so without this block the root gets no\n\t// transition, and a URL for the root moves the machine nowhere: a press on BACK to\n\t// `/doc` leaves the actor on the child it already holds, and the URL and the actor\n\t// diverge.\n\t//\n\t// The transition targets the root by its ID. XState enters the `initial` child of\n\t// a compound target, which is the state that a URL for the root means. The `reenter`\n\t// flag does NOT decide that: a route to the root lands on the initial child of the\n\t// root under `false` and under `true`. The flag decides only whether the root runs\n\t// its own `exit` and `entry` actions again. The root therefore reads the flag from\n\t// its `meta.route` like every other state, and it defaults to `false`.\n\tconst rootNode = machineConfig as RouteStateNode;\n\tconst rootRouteMeta = rootNode.meta?.route;\n\tconst rootRoutePath =\n\t\trootRouteMeta === undefined\n\t\t\t? \"\"\n\t\t\t: normalizeRoute(rootRouteMeta, \"formatPlayRouteTransitions\");\n\n\t// The placement of the ROOT has no meaning, because the root has no parent to hold a\n\t// local transition. A value that the routing layer does not know must still fail\n\t// here, so that one typo behaves the same wherever an author writes it.\n\trouteHandler(rootRouteMeta);\n\n\tif (rootRoutePath) {\n\t\tif (!rootNode.id) throw new MissingStateIdError(\"(the root of the machine)\", rootRoutePath);\n\n\t\trouteTransitions.push({\n\t\t\ttarget: `#${rootNode.id}`,\n\t\t\tguard: ({ event }) => event.to === `#${rootNode.id}`,\n\t\t\treenter: routeReenter(rootRouteMeta),\n\t\t\tactions: makeRouteAssign(rootRouteMeta),\n\t\t});\n\t}\n\n\tconst rebuiltStates = walk?.changed === true;\n\n\t// A machine whose every routable state declares `handler: \"local\"` produces NO root\n\t// transition, and its transitions live on the rebuilt states alone. The rebuilt\n\t// states must therefore survive on their own, and not only beside a root\n\t// transition.\n\tif (routeTransitions.length === 0 && !rebuiltStates) {\n\t\treturn machineConfig;\n\t}\n\n\tconst next: Record<string, unknown> = {\n\t\t...machineConfig,\n\t\t...(rebuiltStates ? { states: walk?.states } : {}),\n\t};\n\n\tif (routeTransitions.length > 0) {\n\t\tconst existingOn = machineConfig.on || {};\n\n\t\t// Keep each play.route transition of the user, for example a 404 fallback that a\n\t\t// person wrote, and append those transitions AFTER the generated ones: XState\n\t\t// evaluates the candidate transitions in their order. Therefore a generated guard\n\t\t// wins when it matches, and the transitions of the user are the fallback.\n\t\tnext[\"on\"] = {\n\t\t\t...existingOn,\n\t\t\t\"play.route\": [...routeTransitions, ...asTransitionArray(existingOn[\"play.route\"])],\n\t\t};\n\t}\n\n\treturn next as T;\n}\n"],"mappings":"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"}