@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.
- package/README.md +64 -12
- package/dist/capabilities.js +6 -2
- package/dist/capabilities.js.map +1 -1
- package/dist/define-player.js +8 -8
- package/dist/define-player.js.map +1 -1
- package/dist/errors.js +262 -259
- package/dist/errors.js.map +1 -1
- package/dist/index.js +4 -25
- package/dist/player-actor.js +318 -448
- package/dist/player-actor.js.map +1 -1
- package/dist/routing/build-url.js +74 -108
- package/dist/routing/build-url.js.map +1 -1
- package/dist/routing/derive-current-route.d.ts.map +1 -1
- package/dist/routing/derive-current-route.js +46 -67
- package/dist/routing/derive-current-route.js.map +1 -1
- package/dist/routing/derive-initial-route.js +53 -64
- package/dist/routing/derive-initial-route.js.map +1 -1
- package/dist/routing/derive-route.js +225 -242
- package/dist/routing/derive-route.js.map +1 -1
- package/dist/routing/format-play-route-transitions.js +192 -258
- package/dist/routing/format-play-route-transitions.js.map +1 -1
- package/dist/state-meta.d.ts +56 -25
- package/dist/state-meta.d.ts.map +1 -1
- package/dist/state-meta.js +164 -105
- package/dist/state-meta.js.map +1 -1
- package/dist/view/derive-current-view.d.ts +18 -10
- package/dist/view/derive-current-view.d.ts.map +1 -1
- package/dist/view/derive-current-view.js +243 -221
- package/dist/view/derive-current-view.js.map +1 -1
- package/dist/with-routing.js +94 -136
- package/dist/with-routing.js.map +1 -1
- package/dist/with-view.d.ts.map +1 -1
- package/dist/with-view.js +82 -152
- package/dist/with-view.js.map +1 -1
- package/package.json +19 -19
- package/dist/index.js.map +0 -1
- package/dist/routing/index.js +0 -14
- package/dist/routing/index.js.map +0 -1
- package/dist/routing/types.js +0 -2
- package/dist/routing/types.js.map +0 -1
- package/dist/types.js +0 -2
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
173
|
+
var ROUTE_HANDLERS = /* @__PURE__ */ new Set([
|
|
174
|
+
"root",
|
|
175
|
+
"local",
|
|
176
|
+
"both"
|
|
177
|
+
]);
|
|
194
178
|
/**
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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"}
|