@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,256 +1,239 @@
1
1
  import { InvalidRouteHandlerError, InvalidRouteMetadataError } from "../errors.js";
2
+ //#region packages/play-xstate/src/routing/derive-route.ts
2
3
  /**
3
- * Derives the route from the metadata of an XState state
4
- *
5
- * The function reads the URL template of the route from the `meta.route` field in
6
- * the metadata of the active state. It supports a string route (`"/about"`) and also
7
- * an object route with a path property (`{ path: "/about" }`). It returns null for a
8
- * state without route metadata, because not every state needs a route.
9
- *
10
- * **Architectural context:** the function implements **Actor Authority (INV-01)**,
11
- * because it reads the routing information from the state machine definition, and
12
- * not from an external configuration. The current state of the Actor decides the
13
- * route, and no decision of the infrastructure decides it.
14
- *
15
- * @param stateMeta - The state metadata from snapshot.getMeta()
16
- * @returns The template of the route path, which can hold a :param, or null when the function finds no route
17
- *
18
- * @example
19
- * The basic read of a route
20
- * ```typescript
21
- * import { deriveRoute } from "@xmachines/play-xstate/routing";
22
- * import { setup } from "xstate";
23
- *
24
- * const machine = setup({}).createMachine({
25
- * states: {
26
- * about: {
27
- * meta: { route: "/about", view: { component: "AboutPage" } }
28
- * }
29
- * }
30
- * });
31
- *
32
- * const actor = createActor(machine);
33
- * actor.start();
34
- * const snapshot = actor.getSnapshot();
35
- * const meta = snapshot.getMeta();
36
- *
37
- * const route = deriveRoute(meta);
38
- * console.log(route); // "/about"
39
- * ```
40
- *
41
- * @example
42
- * A route with a parameter
43
- * ```typescript
44
- * const machine = setup({}).createMachine({
45
- * states: {
46
- * profile: {
47
- * meta: {
48
- * route: "/profile/:userId",
49
- * view: { component: "ProfilePage", userId: (ctx) => ctx.userId }
50
- * }
51
- * }
52
- * }
53
- * });
54
- *
55
- * const route = deriveRoute(snapshot.getMeta());
56
- * console.log(route); // "/profile/:userId" — the template, before the substitution
57
- * ```
58
- *
59
- * @example
60
- * The object form of a route
61
- * ```typescript
62
- * const machine = setup({}).createMachine({
63
- * states: {
64
- * dashboard: {
65
- * meta: {
66
- * route: { path: "/dashboard" },
67
- * view: { component: "Dashboard" }
68
- * }
69
- * }
70
- * }
71
- * });
72
- *
73
- * const route = deriveRoute(snapshot.getMeta());
74
- * console.log(route); // "/dashboard"
75
- * ```
76
- *
77
- * @see [Play RFC](../../../docs/rfc/play.md)
78
- * @see {@link buildRouteUrl} for the construction of a URL, with the substitution of each parameter
79
- * @see {@link isAbsoluteRoute} for the test of an absolute path
80
- *
81
- * @remarks
82
- * This function reads the route definition from `meta.route`. A state with a
83
- * `route: {}` config has a route. {@link buildRouteUrl} substitutes each parameter,
84
- * and this function does not: deriveRoute returns a template.
85
- *
86
- * **A state without a route:** a state without a `meta.route` field gives `null`.
87
- * This is deliberate, because not every state needs a route. An intermediate loading
88
- * state and a substate, for example, often have no URL of their own.
89
- */
90
- export const deriveRoute = (stateMeta) => {
91
- // snapshot.getMeta() puts the keys of the ancestors first. Fold each entry with a
92
- // route from the top to the bottom. The meta.route of the deepest active state
93
- // therefore wins over the route of an ancestor, and a RELATIVE route resolves
94
- // against its nearest ancestors with a route. The tree resolution of play-router
95
- // does the same: the fullPath of a child is the fullPath of its parent, then "/",
96
- // then its own path.
97
- let resolved = null;
98
- for (const meta of Object.values(stateMeta)) {
99
- if (typeof meta !== "object" || meta === null)
100
- continue;
101
- if (!("route" in meta) || !meta.route)
102
- continue;
103
- const route = normalizeRoute(meta.route);
104
- if (typeof route !== "string" || route === "")
105
- continue;
106
- if (isAbsoluteRoute(route)) {
107
- resolved = route;
108
- }
109
- else {
110
- const base = resolved === null || resolved === "/" ? "" : resolved;
111
- resolved = `${base}/${route}`;
112
- }
113
- }
114
- return resolved;
4
+ * Derives the route from the metadata of an XState state
5
+ *
6
+ * The function reads the URL template of the route from the `meta.route` field in
7
+ * the metadata of the active state. It supports a string route (`"/about"`) and also
8
+ * an object route with a path property (`{ path: "/about" }`). It returns null for a
9
+ * state without route metadata, because not every state needs a route.
10
+ *
11
+ * **Architectural context:** the function implements **Actor Authority (INV-01)**,
12
+ * because it reads the routing information from the state machine definition, and
13
+ * not from an external configuration. The current state of the Actor decides the
14
+ * route, and no decision of the infrastructure decides it.
15
+ *
16
+ * @param stateMeta - The state metadata from snapshot.getMeta()
17
+ * @returns The template of the route path, which can hold a :param, or null when the function finds no route
18
+ *
19
+ * @example
20
+ * The basic read of a route
21
+ * ```typescript
22
+ * import { deriveRoute } from "@xmachines/play-xstate/routing";
23
+ * import { setup } from "xstate";
24
+ *
25
+ * const machine = setup({}).createMachine({
26
+ * states: {
27
+ * about: {
28
+ * meta: { route: "/about", view: { component: "AboutPage" } }
29
+ * }
30
+ * }
31
+ * });
32
+ *
33
+ * const actor = createActor(machine);
34
+ * actor.start();
35
+ * const snapshot = actor.getSnapshot();
36
+ * const meta = snapshot.getMeta();
37
+ *
38
+ * const route = deriveRoute(meta);
39
+ * console.log(route); // "/about"
40
+ * ```
41
+ *
42
+ * @example
43
+ * A route with a parameter
44
+ * ```typescript
45
+ * const machine = setup({}).createMachine({
46
+ * states: {
47
+ * profile: {
48
+ * meta: {
49
+ * route: "/profile/:userId",
50
+ * view: { component: "ProfilePage", userId: (ctx) => ctx.userId }
51
+ * }
52
+ * }
53
+ * }
54
+ * });
55
+ *
56
+ * const route = deriveRoute(snapshot.getMeta());
57
+ * console.log(route); // "/profile/:userId" — the template, before the substitution
58
+ * ```
59
+ *
60
+ * @example
61
+ * The object form of a route
62
+ * ```typescript
63
+ * const machine = setup({}).createMachine({
64
+ * states: {
65
+ * dashboard: {
66
+ * meta: {
67
+ * route: { path: "/dashboard" },
68
+ * view: { component: "Dashboard" }
69
+ * }
70
+ * }
71
+ * }
72
+ * });
73
+ *
74
+ * const route = deriveRoute(snapshot.getMeta());
75
+ * console.log(route); // "/dashboard"
76
+ * ```
77
+ *
78
+ * @see [Play RFC](../../../docs/rfc/play.md)
79
+ * @see {@link buildRouteUrl} for the construction of a URL, with the substitution of each parameter
80
+ * @see {@link isAbsoluteRoute} for the test of an absolute path
81
+ *
82
+ * @remarks
83
+ * This function reads the route definition from `meta.route`. A state with a
84
+ * `route: {}` config has a route. {@link buildRouteUrl} substitutes each parameter,
85
+ * and this function does not: deriveRoute returns a template.
86
+ *
87
+ * **A state without a route:** a state without a `meta.route` field gives `null`.
88
+ * This is deliberate, because not every state needs a route. An intermediate loading
89
+ * state and a substate, for example, often have no URL of their own.
90
+ */
91
+ var deriveRoute = (stateMeta) => {
92
+ let resolved = null;
93
+ for (const meta of Object.values(stateMeta)) {
94
+ if (typeof meta !== "object" || meta === null) continue;
95
+ if (!("route" in meta) || !meta.route) continue;
96
+ const route = normalizeRoute(meta.route);
97
+ if (typeof route !== "string" || route === "") continue;
98
+ if (isAbsoluteRoute(route)) resolved = route;
99
+ else resolved = `${resolved === null || resolved === "/" ? "" : resolved}/${route}`;
100
+ }
101
+ return resolved;
115
102
  };
116
103
  /**
117
- * Normalizes the route metadata into a string path
118
- *
119
- * The function handles the string form and the object form of a route definition.
120
- * The output is therefore always a string, for the construction of the URL.
121
- *
122
- * @param route - The route metadata: a string, or an object with a path property
123
- * @param source - The `PlayError.scope` value to report for invalid metadata. The
124
- * default is `"deriveRoute"`
125
- * @returns The normalized string of the route path
126
- * @throws {InvalidRouteMetadataError} When the form of the route is invalid
127
- */
128
- export const normalizeRoute = (route, source = "deriveRoute") => {
129
- if (typeof route === "string") {
130
- return route;
131
- }
132
- // A route object, with a path property
133
- if (route && typeof route === "object" && "path" in route) {
134
- return route.path;
135
- }
136
- throw new InvalidRouteMetadataError(route, source);
104
+ * Normalizes the route metadata into a string path
105
+ *
106
+ * The function handles the string form and the object form of a route definition.
107
+ * The output is therefore always a string, for the construction of the URL.
108
+ *
109
+ * @param route - The route metadata: a string, or an object with a path property
110
+ * @param source - The `PlayError.scope` value to report for invalid metadata. The
111
+ * default is `"deriveRoute"`
112
+ * @returns The normalized string of the route path
113
+ * @throws {InvalidRouteMetadataError} When the form of the route is invalid
114
+ */
115
+ var normalizeRoute = (route, source = "deriveRoute") => {
116
+ if (typeof route === "string") return route;
117
+ if (route && typeof route === "object" && "path" in route) return route.path;
118
+ throw new InvalidRouteMetadataError(route, source);
137
119
  };
138
120
  /**
139
- * Reads the `reenter` flag of the route metadata.
140
- *
141
- * The string form of the metadata declares no flag, and the object form declares the
142
- * flag only when it wants one. Both therefore give `false`, which is the default of
143
- * XState for a transition. A state without a route gives `false` too, so the caller
144
- * needs no test of its own for that case.
145
- *
146
- * The function does NOT check the form of the metadata. `normalizeRoute` runs first at
147
- * every call site and throws `InvalidRouteMetadataError` on a malformed value.
148
- *
149
- * @param route - The route metadata: a string, an object with a `path` property, or
150
- * `undefined` for a state that declares no route
151
- * @returns The declared flag, or `false` where the metadata declares none
152
- *
153
- * @example
154
- * ```typescript
155
- * routeReenter("/dashboard"); // false
156
- * routeReenter({ path: "/dashboard" }); // false
157
- * routeReenter({ path: "/dashboard", reenter: true }); // true
158
- * routeReenter(undefined); // false
159
- * ```
160
- */
161
- export const routeReenter = (route) => typeof route === "object" && route !== null && route.reenter === true;
121
+ * Reads the `reenter` flag of the route metadata.
122
+ *
123
+ * The string form of the metadata declares no flag, and the object form declares the
124
+ * flag only when it wants one. Both therefore give `false`, which is the default of
125
+ * XState for a transition. A state without a route gives `false` too, so the caller
126
+ * needs no test of its own for that case.
127
+ *
128
+ * The function does NOT check the form of the metadata. `normalizeRoute` runs first at
129
+ * every call site and throws `InvalidRouteMetadataError` on a malformed value.
130
+ *
131
+ * @param route - The route metadata: a string, an object with a `path` property, or
132
+ * `undefined` for a state that declares no route
133
+ * @returns The declared flag, or `false` where the metadata declares none
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * routeReenter("/dashboard"); // false
138
+ * routeReenter({ path: "/dashboard" }); // false
139
+ * routeReenter({ path: "/dashboard", reenter: true }); // true
140
+ * routeReenter(undefined); // false
141
+ * ```
142
+ */
143
+ var routeReenter = (route) => typeof route === "object" && route !== null && route.reenter === true;
162
144
  /**
163
- * Reads the extra data that a route declares, resolved for one event.
164
- *
165
- * The field holds a literal value or a function of `{ context, event }`, which is the
166
- * `WithDynamicParams` shape of XState. This function collapses the two into the value.
167
- *
168
- * The function does NOT check the form of the metadata. `normalizeRoute` runs first at
169
- * every call site and throws `InvalidRouteMetadataError` on a malformed value.
170
- *
171
- * @param route - The route metadata: a string, an object with a `path` property, or
172
- * `undefined` for a state that declares no route
173
- * @param args - The context of the machine and the `play.route` event that asks for the
174
- * route. The resolver reads them.
175
- * @returns The data, or `undefined` where the route declares none
176
- *
177
- * @example
178
- * ```typescript
179
- * routeData({ path: "/a", data: { titleKey: "a" } }, args); // { titleKey: "a" }
180
- * routeData({ path: "/a", data: ({ event }) => ({ to: event.to }) }, args);
181
- * routeData("/a", args); // undefined
182
- * ```
183
- */
184
- export const routeData = (route, args) => {
185
- if (typeof route !== "object" || route === null)
186
- return undefined;
187
- const data = route.data;
188
- if (data === undefined)
189
- return undefined;
190
- return typeof data === "function" ? data(args) : data;
145
+ * Reads the extra data that a route declares, resolved for one event.
146
+ *
147
+ * The field holds a literal value or a function of `{ context, event }`, which is the
148
+ * `WithDynamicParams` shape of XState. This function collapses the two into the value.
149
+ *
150
+ * The function does NOT check the form of the metadata. `normalizeRoute` runs first at
151
+ * every call site and throws `InvalidRouteMetadataError` on a malformed value.
152
+ *
153
+ * @param route - The route metadata: a string, an object with a `path` property, or
154
+ * `undefined` for a state that declares no route
155
+ * @param args - The context of the machine and the `play.route` event that asks for the
156
+ * route. The resolver reads them.
157
+ * @returns The data, or `undefined` where the route declares none
158
+ *
159
+ * @example
160
+ * ```typescript
161
+ * routeData({ path: "/a", data: { titleKey: "a" } }, args); // { titleKey: "a" }
162
+ * routeData({ path: "/a", data: ({ event }) => ({ to: event.to }) }, args);
163
+ * routeData("/a", args); // undefined
164
+ * ```
165
+ */
166
+ var routeData = (route, args) => {
167
+ if (typeof route !== "object" || route === null) return void 0;
168
+ const data = route.data;
169
+ if (data === void 0) return void 0;
170
+ return typeof data === "function" ? data(args) : data;
191
171
  };
192
172
  /** The placements that a `meta.route` object can declare. */
193
- const ROUTE_HANDLERS = new Set(["root", "local", "both"]);
173
+ var ROUTE_HANDLERS = /* @__PURE__ */ new Set([
174
+ "root",
175
+ "local",
176
+ "both"
177
+ ]);
194
178
  /**
195
- * Reads the placement of the generated `play.route` transition.
196
- *
197
- * `"root"` is the default, and it is the placement of XState: every route transition
198
- * sits on the root of the machine. `"local"` moves the transition to the parent of the
199
- * state, and `"both"` puts one transition in each place.
200
- *
201
- * The function does NOT check the form of the metadata. `normalizeRoute` runs first at
202
- * every call site and throws `InvalidRouteMetadataError` on a malformed value.
203
- *
204
- * @param route - The route metadata: a string, an object with a `path` property, or
205
- * `undefined` for a state that declares no route
206
- * @param source - The `PlayError.scope` value to report for an unknown placement
207
- * @returns The declared placement, and `"root"` where the metadata declares none
208
- * @throws {InvalidRouteHandlerError} When the object declares a `handler` that is not
209
- * `"root"`, `"local"`, or `"both"`. A typo would otherwise fall back to a placement
210
- * that the author did not ask for, and it would change which states a route reaches.
211
- *
212
- * @example
213
- * ```typescript
214
- * routeHandler("/dashboard"); // "root"
215
- * routeHandler({ path: "/dashboard" }); // "root"
216
- * routeHandler({ path: "/dashboard", handler: "local" }); // "local"
217
- * routeHandler({ path: "/dashboard", handler: "both" }); // "both"
218
- * routeHandler({ path: "/dashboard", handler: "locale" }); // throws
219
- * ```
220
- */
221
- export const routeHandler = (route, source = "formatPlayRouteTransitions") => {
222
- if (typeof route !== "object" || route === null)
223
- return "root";
224
- const handler = route.handler;
225
- if (handler === undefined)
226
- return "root";
227
- if (typeof handler !== "string" || !ROUTE_HANDLERS.has(handler)) {
228
- throw new InvalidRouteHandlerError(handler, source);
229
- }
230
- return handler;
179
+ * Reads the placement of the generated `play.route` transition.
180
+ *
181
+ * `"root"` is the default, and it is the placement of XState: every route transition
182
+ * sits on the root of the machine. `"local"` moves the transition to the parent of the
183
+ * state, and `"both"` puts one transition in each place.
184
+ *
185
+ * The function does NOT check the form of the metadata. `normalizeRoute` runs first at
186
+ * every call site and throws `InvalidRouteMetadataError` on a malformed value.
187
+ *
188
+ * @param route - The route metadata: a string, an object with a `path` property, or
189
+ * `undefined` for a state that declares no route
190
+ * @param source - The `PlayError.scope` value to report for an unknown placement
191
+ * @returns The declared placement, and `"root"` where the metadata declares none
192
+ * @throws {InvalidRouteHandlerError} When the object declares a `handler` that is not
193
+ * `"root"`, `"local"`, or `"both"`. A typo would otherwise fall back to a placement
194
+ * that the author did not ask for, and it would change which states a route reaches.
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * routeHandler("/dashboard"); // "root"
199
+ * routeHandler({ path: "/dashboard" }); // "root"
200
+ * routeHandler({ path: "/dashboard", handler: "local" }); // "local"
201
+ * routeHandler({ path: "/dashboard", handler: "both" }); // "both"
202
+ * routeHandler({ path: "/dashboard", handler: "locale" }); // throws
203
+ * ```
204
+ */
205
+ var routeHandler = (route, source = "formatPlayRouteTransitions") => {
206
+ if (typeof route !== "object" || route === null) return "root";
207
+ const handler = route.handler;
208
+ if (handler === void 0) return "root";
209
+ if (typeof handler !== "string" || !ROUTE_HANDLERS.has(handler)) throw new InvalidRouteHandlerError(handler, source);
210
+ return handler;
231
211
  };
232
212
  /**
233
- * Tells you if the route path is absolute
234
- *
235
- * The function decides if a route path is absolute, which means that it starts with
236
- * `/`, or relative. An absolute path inherits nothing from a parent route. A
237
- * relative path joins a parent path, for a nested routing.
238
- *
239
- * @param path - The route path to test
240
- * @returns true when the path starts with '/'. In every other case, false
241
- *
242
- * @example
243
- * ```typescript
244
- * import { isAbsoluteRoute } from "@xmachines/play-xstate/routing";
245
- *
246
- * console.log(isAbsoluteRoute("/dashboard")); // true
247
- * console.log(isAbsoluteRoute("settings")); // false
248
- * console.log(isAbsoluteRoute("./about")); // false
249
- * ```
250
- *
251
- * @see {@link deriveRoute} for the read of a route
252
- */
253
- export const isAbsoluteRoute = (path) => {
254
- return path.startsWith("/");
213
+ * Tells you if the route path is absolute
214
+ *
215
+ * The function decides if a route path is absolute, which means that it starts with
216
+ * `/`, or relative. An absolute path inherits nothing from a parent route. A
217
+ * relative path joins a parent path, for a nested routing.
218
+ *
219
+ * @param path - The route path to test
220
+ * @returns true when the path starts with '/'. In every other case, false
221
+ *
222
+ * @example
223
+ * ```typescript
224
+ * import { isAbsoluteRoute } from "@xmachines/play-xstate/routing";
225
+ *
226
+ * console.log(isAbsoluteRoute("/dashboard")); // true
227
+ * console.log(isAbsoluteRoute("settings")); // false
228
+ * console.log(isAbsoluteRoute("./about")); // false
229
+ * ```
230
+ *
231
+ * @see {@link deriveRoute} for the read of a route
232
+ */
233
+ var isAbsoluteRoute = (path) => {
234
+ return path.startsWith("/");
255
235
  };
236
+ //#endregion
237
+ export { deriveRoute, isAbsoluteRoute, normalizeRoute, routeData, routeHandler, routeReenter };
238
+
256
239
  //# sourceMappingURL=derive-route.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"derive-route.js","sourceRoot":"","sources":["../../src/routing/derive-route.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"derive-route.js","names":[],"sources":["../../src/routing/derive-route.ts"],"sourcesContent":["import type { PlayRouteEvent, RouteData } from \"@xmachines/play-router\";\nimport type { RouteMetadata, RouteObject } from \"./types.js\";\nimport { InvalidRouteHandlerError, InvalidRouteMetadataError } from \"../errors.js\";\n\n/**\n * Derives the route from the metadata of an XState state\n *\n * The function reads the URL template of the route from the `meta.route` field in\n * the metadata of the active state. It supports a string route (`\"/about\"`) and also\n * an object route with a path property (`{ path: \"/about\" }`). It returns null for a\n * state without route metadata, because not every state needs a route.\n *\n * **Architectural context:** the function implements **Actor Authority (INV-01)**,\n * because it reads the routing information from the state machine definition, and\n * not from an external configuration. The current state of the Actor decides the\n * route, and no decision of the infrastructure decides it.\n *\n * @param stateMeta - The state metadata from snapshot.getMeta()\n * @returns The template of the route path, which can hold a :param, or null when the function finds no route\n *\n * @example\n * The basic read of a route\n * ```typescript\n * import { deriveRoute } from \"@xmachines/play-xstate/routing\";\n * import { setup } from \"xstate\";\n *\n * const machine = setup({}).createMachine({\n * states: {\n * about: {\n * meta: { route: \"/about\", view: { component: \"AboutPage\" } }\n * }\n * }\n * });\n *\n * const actor = createActor(machine);\n * actor.start();\n * const snapshot = actor.getSnapshot();\n * const meta = snapshot.getMeta();\n *\n * const route = deriveRoute(meta);\n * console.log(route); // \"/about\"\n * ```\n *\n * @example\n * A route with a parameter\n * ```typescript\n * const machine = setup({}).createMachine({\n * states: {\n * profile: {\n * meta: {\n * route: \"/profile/:userId\",\n * view: { component: \"ProfilePage\", userId: (ctx) => ctx.userId }\n * }\n * }\n * }\n * });\n *\n * const route = deriveRoute(snapshot.getMeta());\n * console.log(route); // \"/profile/:userId\" — the template, before the substitution\n * ```\n *\n * @example\n * The object form of a route\n * ```typescript\n * const machine = setup({}).createMachine({\n * states: {\n * dashboard: {\n * meta: {\n * route: { path: \"/dashboard\" },\n * view: { component: \"Dashboard\" }\n * }\n * }\n * }\n * });\n *\n * const route = deriveRoute(snapshot.getMeta());\n * console.log(route); // \"/dashboard\"\n * ```\n *\n * @see [Play RFC](../../../docs/rfc/play.md)\n * @see {@link buildRouteUrl} for the construction of a URL, with the substitution of each parameter\n * @see {@link isAbsoluteRoute} for the test of an absolute path\n *\n * @remarks\n * This function reads the route definition from `meta.route`. A state with a\n * `route: {}` config has a route. {@link buildRouteUrl} substitutes each parameter,\n * and this function does not: deriveRoute returns a template.\n *\n * **A state without a route:** a state without a `meta.route` field gives `null`.\n * This is deliberate, because not every state needs a route. An intermediate loading\n * state and a substate, for example, often have no URL of their own.\n */\nexport const deriveRoute = (stateMeta: Record<string, unknown>): string | null => {\n\t// snapshot.getMeta() puts the keys of the ancestors first. Fold each entry with a\n\t// route from the top to the bottom. The meta.route of the deepest active state\n\t// therefore wins over the route of an ancestor, and a RELATIVE route resolves\n\t// against its nearest ancestors with a route. The tree resolution of play-router\n\t// does the same: the fullPath of a child is the fullPath of its parent, then \"/\",\n\t// then its own path.\n\tlet resolved: string | null = null;\n\tfor (const meta of Object.values(stateMeta)) {\n\t\tif (typeof meta !== \"object\" || meta === null) continue;\n\t\tif (!(\"route\" in meta) || !meta.route) continue;\n\n\t\tconst route = normalizeRoute(meta.route as RouteMetadata);\n\t\tif (typeof route !== \"string\" || route === \"\") continue;\n\t\tif (isAbsoluteRoute(route)) {\n\t\t\tresolved = route;\n\t\t} else {\n\t\t\tconst base: string = resolved === null || resolved === \"/\" ? \"\" : resolved;\n\t\t\tresolved = `${base}/${route}`;\n\t\t}\n\t}\n\n\treturn resolved;\n};\n\n/**\n * Normalizes the route metadata into a string path\n *\n * The function handles the string form and the object form of a route definition.\n * The output is therefore always a string, for the construction of the URL.\n *\n * @param route - The route metadata: a string, or an object with a path property\n * @param source - The `PlayError.scope` value to report for invalid metadata. The\n * default is `\"deriveRoute\"`\n * @returns The normalized string of the route path\n * @throws {InvalidRouteMetadataError} When the form of the route is invalid\n */\nexport const normalizeRoute = (route: RouteMetadata, source = \"deriveRoute\"): string => {\n\tif (typeof route === \"string\") {\n\t\treturn route;\n\t}\n\n\t// A route object, with a path property\n\tif (route && typeof route === \"object\" && \"path\" in route) {\n\t\treturn (route as RouteObject).path;\n\t}\n\n\tthrow new InvalidRouteMetadataError(route, source);\n};\n\n/**\n * Reads the `reenter` flag of the route metadata.\n *\n * The string form of the metadata declares no flag, and the object form declares the\n * flag only when it wants one. Both therefore give `false`, which is the default of\n * XState for a transition. A state without a route gives `false` too, so the caller\n * needs no test of its own for that case.\n *\n * The function does NOT check the form of the metadata. `normalizeRoute` runs first at\n * every call site and throws `InvalidRouteMetadataError` on a malformed value.\n *\n * @param route - The route metadata: a string, an object with a `path` property, or\n * `undefined` for a state that declares no route\n * @returns The declared flag, or `false` where the metadata declares none\n *\n * @example\n * ```typescript\n * routeReenter(\"/dashboard\"); // false\n * routeReenter({ path: \"/dashboard\" }); // false\n * routeReenter({ path: \"/dashboard\", reenter: true }); // true\n * routeReenter(undefined); // false\n * ```\n */\nexport const routeReenter = (route: RouteMetadata | undefined): boolean =>\n\ttypeof route === \"object\" && route !== null && (route as RouteObject).reenter === true;\n\n/**\n * Reads the extra data that a route declares, resolved for one event.\n *\n * The field holds a literal value or a function of `{ context, event }`, which is the\n * `WithDynamicParams` shape of XState. This function collapses the two into the value.\n *\n * The function does NOT check the form of the metadata. `normalizeRoute` runs first at\n * every call site and throws `InvalidRouteMetadataError` on a malformed value.\n *\n * @param route - The route metadata: a string, an object with a `path` property, or\n * `undefined` for a state that declares no route\n * @param args - The context of the machine and the `play.route` event that asks for the\n * route. The resolver reads them.\n * @returns The data, or `undefined` where the route declares none\n *\n * @example\n * ```typescript\n * routeData({ path: \"/a\", data: { titleKey: \"a\" } }, args); // { titleKey: \"a\" }\n * routeData({ path: \"/a\", data: ({ event }) => ({ to: event.to }) }, args);\n * routeData(\"/a\", args); // undefined\n * ```\n */\nexport const routeData = (\n\troute: RouteMetadata | undefined,\n\targs: { context: unknown; event: PlayRouteEvent },\n): RouteData | undefined => {\n\tif (typeof route !== \"object\" || route === null) return undefined;\n\n\tconst data = (route as RouteObject).data;\n\tif (data === undefined) return undefined;\n\n\treturn typeof data === \"function\" ? data(args) : data;\n};\n\n/** The placements that a `meta.route` object can declare. */\nconst ROUTE_HANDLERS = new Set([\"root\", \"local\", \"both\"]);\n\n/**\n * Reads the placement of the generated `play.route` transition.\n *\n * `\"root\"` is the default, and it is the placement of XState: every route transition\n * sits on the root of the machine. `\"local\"` moves the transition to the parent of the\n * state, and `\"both\"` puts one transition in each place.\n *\n * The function does NOT check the form of the metadata. `normalizeRoute` runs first at\n * every call site and throws `InvalidRouteMetadataError` on a malformed value.\n *\n * @param route - The route metadata: a string, an object with a `path` property, or\n * `undefined` for a state that declares no route\n * @param source - The `PlayError.scope` value to report for an unknown placement\n * @returns The declared placement, and `\"root\"` where the metadata declares none\n * @throws {InvalidRouteHandlerError} When the object declares a `handler` that is not\n * `\"root\"`, `\"local\"`, or `\"both\"`. A typo would otherwise fall back to a placement\n * that the author did not ask for, and it would change which states a route reaches.\n *\n * @example\n * ```typescript\n * routeHandler(\"/dashboard\"); // \"root\"\n * routeHandler({ path: \"/dashboard\" }); // \"root\"\n * routeHandler({ path: \"/dashboard\", handler: \"local\" }); // \"local\"\n * routeHandler({ path: \"/dashboard\", handler: \"both\" }); // \"both\"\n * routeHandler({ path: \"/dashboard\", handler: \"locale\" }); // throws\n * ```\n */\nexport const routeHandler = (\n\troute: RouteMetadata | undefined,\n\tsource = \"formatPlayRouteTransitions\",\n): \"root\" | \"local\" | \"both\" => {\n\tif (typeof route !== \"object\" || route === null) return \"root\";\n\n\tconst handler = (route as RouteObject).handler;\n\tif (handler === undefined) return \"root\";\n\tif (typeof handler !== \"string\" || !ROUTE_HANDLERS.has(handler)) {\n\t\tthrow new InvalidRouteHandlerError(handler, source);\n\t}\n\n\treturn handler as \"root\" | \"local\" | \"both\";\n};\n\n/**\n * Tells you if the route path is absolute\n *\n * The function decides if a route path is absolute, which means that it starts with\n * `/`, or relative. An absolute path inherits nothing from a parent route. A\n * relative path joins a parent path, for a nested routing.\n *\n * @param path - The route path to test\n * @returns true when the path starts with '/'. In every other case, false\n *\n * @example\n * ```typescript\n * import { isAbsoluteRoute } from \"@xmachines/play-xstate/routing\";\n *\n * console.log(isAbsoluteRoute(\"/dashboard\")); // true\n * console.log(isAbsoluteRoute(\"settings\")); // false\n * console.log(isAbsoluteRoute(\"./about\")); // false\n * ```\n *\n * @see {@link deriveRoute} for the read of a route\n */\nexport const isAbsoluteRoute = (path: string): boolean => {\n\treturn path.startsWith(\"/\");\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4FA,IAAa,eAAe,cAAsD;CAOjF,IAAI,WAA0B;CAC9B,KAAK,MAAM,QAAQ,OAAO,OAAO,SAAS,GAAG;EAC5C,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM;EAC/C,IAAI,EAAE,WAAW,SAAS,CAAC,KAAK,OAAO;EAEvC,MAAM,QAAQ,eAAe,KAAK,KAAsB;EACxD,IAAI,OAAO,UAAU,YAAY,UAAU,IAAI;EAC/C,IAAI,gBAAgB,KAAK,GACxB,WAAW;OAGX,WAAW,GADU,aAAa,QAAQ,aAAa,MAAM,KAAK,SAC/C,GAAG;CAExB;CAEA,OAAO;AACR;;;;;;;;;;;;;AAcA,IAAa,kBAAkB,OAAsB,SAAS,kBAA0B;CACvF,IAAI,OAAO,UAAU,UACpB,OAAO;CAIR,IAAI,SAAS,OAAO,UAAU,YAAY,UAAU,OACnD,OAAQ,MAAsB;CAG/B,MAAM,IAAI,0BAA0B,OAAO,MAAM;AAClD;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,gBAAgB,UAC5B,OAAO,UAAU,YAAY,UAAU,QAAS,MAAsB,YAAY;;;;;;;;;;;;;;;;;;;;;;;AAwBnF,IAAa,aACZ,OACA,SAC2B;CAC3B,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO,KAAA;CAExD,MAAM,OAAQ,MAAsB;CACpC,IAAI,SAAS,KAAA,GAAW,OAAO,KAAA;CAE/B,OAAO,OAAO,SAAS,aAAa,KAAK,IAAI,IAAI;AAClD;;AAGA,IAAM,iCAAiB,IAAI,IAAI;CAAC;CAAQ;CAAS;AAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BxD,IAAa,gBACZ,OACA,SAAS,iCACsB;CAC/B,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CAExD,MAAM,UAAW,MAAsB;CACvC,IAAI,YAAY,KAAA,GAAW,OAAO;CAClC,IAAI,OAAO,YAAY,YAAY,CAAC,eAAe,IAAI,OAAO,GAC7D,MAAM,IAAI,yBAAyB,SAAS,MAAM;CAGnD,OAAO;AACR;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAa,mBAAmB,SAA0B;CACzD,OAAO,KAAK,WAAW,GAAG;AAC3B"}