@xmachines/play-xstate 4.0.0 → 5.0.1
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 +61 -30
- package/dist/capabilities.d.ts +4 -3
- package/dist/capabilities.d.ts.map +1 -1
- package/dist/capabilities.js.map +1 -1
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/player-actor.d.ts +30 -29
- package/dist/player-actor.d.ts.map +1 -1
- package/dist/player-actor.js +51 -41
- package/dist/player-actor.js.map +1 -1
- package/dist/routing/build-url.d.ts +8 -1
- package/dist/routing/build-url.d.ts.map +1 -1
- package/dist/routing/build-url.js +34 -54
- package/dist/routing/build-url.js.map +1 -1
- package/dist/routing/derive-current-route.d.ts +11 -2
- package/dist/routing/derive-current-route.d.ts.map +1 -1
- package/dist/routing/derive-current-route.js +22 -6
- package/dist/routing/derive-current-route.js.map +1 -1
- package/dist/routing/derive-initial-route.d.ts +2 -1
- package/dist/routing/derive-initial-route.d.ts.map +1 -1
- package/dist/routing/derive-initial-route.js +2 -2
- package/dist/routing/derive-initial-route.js.map +1 -1
- package/dist/routing/format-play-route-transitions.js +10 -5
- package/dist/routing/format-play-route-transitions.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/view/derive-current-view.d.ts +6 -5
- package/dist/view/derive-current-view.d.ts.map +1 -1
- package/dist/view/derive-current-view.js +5 -4
- package/dist/view/derive-current-view.js.map +1 -1
- package/dist/with-routing.d.ts.map +1 -1
- package/dist/with-routing.js +66 -4
- package/dist/with-routing.js.map +1 -1
- package/dist/with-view.d.ts +19 -4
- package/dist/with-view.d.ts.map +1 -1
- package/dist/with-view.js +64 -56
- package/dist/with-view.js.map +1 -1
- package/package.json +14 -13
package/dist/player-actor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Actor, } from "xstate";
|
|
2
2
|
import { DISPOSE } from "@xmachines/play";
|
|
3
|
-
import {
|
|
3
|
+
import { createWritableAtom } from "@xmachines/play-atom";
|
|
4
4
|
import { ActorThrewNonErrorError, InvalidEventError, InvalidMachineError } from "./errors.js";
|
|
5
5
|
/**
|
|
6
6
|
* Tells you if a value is an `Error`, by its identity or by its brand: `instanceof`
|
|
@@ -44,15 +44,15 @@ export const toError = (value) => {
|
|
|
44
44
|
return new ActorThrewNonErrorError(value);
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
|
-
* Tells you if a snapshot is worth a propagation to the
|
|
47
|
+
* Tells you if a snapshot is worth a propagation to the atoms: an active
|
|
48
48
|
* snapshot, or a "done" snapshot, which means that the machine reached a final state
|
|
49
49
|
* at the top level. The code skips an error snapshot and a stopped snapshot.
|
|
50
|
-
* Therefore the
|
|
50
|
+
* Therefore the atoms keep the last observable state, and they show no artifact of
|
|
51
51
|
* a teardown or of an error.
|
|
52
52
|
*/
|
|
53
53
|
const isObservableSnapshot = (snapshot) => snapshot.status === "active" || snapshot.status === "done";
|
|
54
54
|
/**
|
|
55
|
-
* The concrete XState actor. It implements the
|
|
55
|
+
* The concrete XState actor. It implements the atom protocol of the Play Architecture
|
|
56
56
|
*
|
|
57
57
|
* The class extends the `Actor` class of XState directly, and it implements
|
|
58
58
|
* {@link @xmachines/play-actor!PlayActor}. It gives you the XState v5 integration, and it keeps the
|
|
@@ -60,7 +60,7 @@ const isObservableSnapshot = (snapshot) => snapshot.status === "active" || snaps
|
|
|
60
60
|
* The constructor of the base class receives the machine. Therefore a `PlayerActor`
|
|
61
61
|
* **is** the XState actor, and it is no wrapper around one: every member of the
|
|
62
62
|
* XState `Actor` class works on the state of this instance, and this class adds the
|
|
63
|
-
* reactive state on the
|
|
63
|
+
* reactive state on the atoms for the observation by the infrastructure.
|
|
64
64
|
*
|
|
65
65
|
* **Capabilities:** the class implements both the
|
|
66
66
|
* {@link @xmachines/play-router!index.Routable} interface and the
|
|
@@ -69,7 +69,7 @@ const isObservableSnapshot = (snapshot) => snapshot.status === "active" || snaps
|
|
|
69
69
|
*
|
|
70
70
|
* **Architectural context:** the class implements **Actor Authority (INV-01)**,
|
|
71
71
|
* because the guards of the XState machine control every decision of the
|
|
72
|
-
* navigation. The infrastructure observes the
|
|
72
|
+
* navigation. The infrastructure observes the atoms of the actor (`state`,
|
|
73
73
|
* `currentRoute`, and `currentView`), but it changes no state directly: every
|
|
74
74
|
* change goes through the event handlers of the state machine.
|
|
75
75
|
*
|
|
@@ -101,31 +101,29 @@ const isObservableSnapshot = (snapshot) => snapshot.status === "active" || snaps
|
|
|
101
101
|
* const actor = createPlayer();
|
|
102
102
|
* actor.start();
|
|
103
103
|
*
|
|
104
|
-
* // Observe the
|
|
104
|
+
* // Observe the atoms
|
|
105
105
|
* console.log(actor.currentRoute.get()); // '/'
|
|
106
106
|
* console.log(actor.currentView.get()?.root); // 'home'
|
|
107
107
|
* ```
|
|
108
108
|
*
|
|
109
109
|
* @example
|
|
110
|
-
* The
|
|
110
|
+
* The atom lifecycle with a watch
|
|
111
111
|
* ```typescript
|
|
112
|
-
* import {
|
|
112
|
+
* import { watchAtom } from "@xmachines/play-atom";
|
|
113
113
|
*
|
|
114
|
-
* const
|
|
115
|
-
*
|
|
116
|
-
* const pending = watcher.getPending();
|
|
117
|
-
* console.log('State changed:', actor.state.get());
|
|
118
|
-
* });
|
|
114
|
+
* const stop = watchAtom(actor.state, (snapshot) => {
|
|
115
|
+
* console.log('State changed:', snapshot.value);
|
|
119
116
|
* });
|
|
120
117
|
*
|
|
121
|
-
* watcher.watch(actor.state);
|
|
122
118
|
* actor.send({ type: 'play.route', to: '#about' });
|
|
123
|
-
* //
|
|
119
|
+
* // watchAtom delivers the new value from a microtask
|
|
120
|
+
*
|
|
121
|
+
* stop();
|
|
124
122
|
* ```
|
|
125
123
|
*
|
|
126
124
|
* @see [Play RFC](../../docs/rfc/play.md)
|
|
127
125
|
* @see {@link definePlayer} for the creation through a factory
|
|
128
|
-
* @see {@link @xmachines/play-actor!PlayActor} for the
|
|
126
|
+
* @see {@link @xmachines/play-actor!PlayActor} for the atom protocol
|
|
129
127
|
* @see {@link @xmachines/play-router!index.Routable} for the routing capability
|
|
130
128
|
* @see {@link @xmachines/play-view!index.Viewable} for the view rendering capability
|
|
131
129
|
*
|
|
@@ -135,10 +133,11 @@ const isObservableSnapshot = (snapshot) => snapshot.status === "active" || snaps
|
|
|
135
133
|
* `meta.route`, which is the Stately pattern, for a URL template, and it substitutes
|
|
136
134
|
* each parameter.
|
|
137
135
|
*
|
|
138
|
-
* **The pattern of the view
|
|
139
|
-
* `
|
|
140
|
-
*
|
|
141
|
-
*
|
|
136
|
+
* **The pattern of the view atom:** `withView` of `@xmachines/play-xstate/view` adds
|
|
137
|
+
* `currentView`, and that atom is a COMPUTED atom over `state`. One write for each
|
|
138
|
+
* transition — `state.set(snapshot)` — therefore reaches the view and the route in one
|
|
139
|
+
* flush, and no subscriber reads the new state beside an old derivation. The atom
|
|
140
|
+
* memoizes the result, so a repeated read computes no view again.
|
|
142
141
|
*/
|
|
143
142
|
export class PlayerActor extends Actor {
|
|
144
143
|
playerOptions;
|
|
@@ -180,7 +179,7 @@ export class PlayerActor extends Actor {
|
|
|
180
179
|
* Tells you if the current state of the actor accepts the given event.
|
|
181
180
|
*
|
|
182
181
|
* The type is the event union of the machine. An unknown event type is therefore a
|
|
183
|
-
* compile error. The method evaluates the event against the snapshot
|
|
182
|
+
* compile error. The method evaluates the event against the snapshot atom.
|
|
184
183
|
*
|
|
185
184
|
* @example
|
|
186
185
|
* ```typescript
|
|
@@ -188,7 +187,7 @@ export class PlayerActor extends Actor {
|
|
|
188
187
|
* ```
|
|
189
188
|
*/
|
|
190
189
|
can(event) {
|
|
191
|
-
// A read of the
|
|
190
|
+
// A read of the atom keeps can() reactive: a computed atom over the atom
|
|
192
191
|
// computes its value again on each transition.
|
|
193
192
|
const snapshot = this.state?.get();
|
|
194
193
|
// The STATUS decides first, and the presence of the method decides second.
|
|
@@ -201,7 +200,7 @@ export class PlayerActor extends Actor {
|
|
|
201
200
|
// `done` actor answers `false` for the same reason.
|
|
202
201
|
//
|
|
203
202
|
// A STOPPED actor is the exception, and it is deliberate: the subscription below
|
|
204
|
-
// updates this
|
|
203
|
+
// updates this atom on a stable state alone, so the atom keeps the last ACTIVE
|
|
205
204
|
// snapshot after a teardown rather than freezing on an artifact of it. `can()`
|
|
206
205
|
// therefore answers for the last live state of a stopped actor.
|
|
207
206
|
if (snapshot?.status !== "active")
|
|
@@ -235,11 +234,15 @@ export class PlayerActor extends Actor {
|
|
|
235
234
|
this.playerInput = input;
|
|
236
235
|
this.playerRestoredSnapshot = restoredSnapshot;
|
|
237
236
|
this.playerOptions = options || {};
|
|
238
|
-
// Initialize the state
|
|
237
|
+
// Initialize the state atom. Each update is synchronous, with no batching in a
|
|
239
238
|
// microtask: XState groups the transitions of one send() call into one
|
|
240
239
|
// subscription callback already, and a synchronous update shows each guard
|
|
241
240
|
// redirect to a router bridge at once.
|
|
242
|
-
|
|
241
|
+
// `createWritableAtom`, and not `createAtom`. The overload pair of `createAtom` reads
|
|
242
|
+
// a function argument as a computed atom, and `ReturnType<TMachine["transition"]>` is
|
|
243
|
+
// an unresolved conditional type here, so TypeScript cannot rule a function out. It
|
|
244
|
+
// picks the computed signature, which returns a `ReadonlyAtom` that carries no `set`.
|
|
245
|
+
this.state = createWritableAtom(this.getSnapshot());
|
|
243
246
|
// Observe the transitions of this actor. The code uses `super`, and not `this`, so
|
|
244
247
|
// that the bookkeeping of the next-only subscriptions in the subscribe override
|
|
245
248
|
// stays about the subscriptions of the user code.
|
|
@@ -247,16 +250,21 @@ export class PlayerActor extends Actor {
|
|
|
247
250
|
next: (snapshot) => {
|
|
248
251
|
// Update on a stable state only: an active snapshot, and the "done" snapshot of a
|
|
249
252
|
// final state at the top level. The code skips an error snapshot and a stopped
|
|
250
|
-
// snapshot. Therefore
|
|
253
|
+
// snapshot. Therefore an atom never freezes on an artifact of a teardown.
|
|
251
254
|
if (isObservableSnapshot(snapshot)) {
|
|
252
255
|
// Each state update is synchronous. Therefore a router bridge sees a guard redirect at once.
|
|
253
256
|
this.state.set(snapshot);
|
|
254
|
-
//
|
|
255
|
-
// `
|
|
256
|
-
//
|
|
257
|
-
//
|
|
257
|
+
// ONE write for each transition, and the line above is it. `currentRoute` and
|
|
258
|
+
// `currentView` are computed atoms over `state`, so that write propagates to
|
|
259
|
+
// both in ONE flush, in topological order. No observer therefore reads the new
|
|
260
|
+
// state beside an old derivation, and the composition order of the capabilities
|
|
261
|
+
// decides nothing.
|
|
262
|
+
//
|
|
263
|
+
// `onSnapshot` remains for a capability that must run an EFFECT on a
|
|
264
|
+
// transition. A capability that publishes a value derives it instead.
|
|
265
|
+
//
|
|
258
266
|
// The whole order of one transition:
|
|
259
|
-
// 1. state receives its new value, and
|
|
267
|
+
// 1. state receives its new value, and every computed atom over it follows
|
|
260
268
|
// 2. onSnapshot runs each capability, in the composition order
|
|
261
269
|
// 3. the onStateChange hook runs
|
|
262
270
|
// 4. send() then calls onTransition
|
|
@@ -332,7 +340,7 @@ export class PlayerActor extends Actor {
|
|
|
332
340
|
// A call that reaches in through the construction window, for example a context
|
|
333
341
|
// factory that stops its own `self`, marks the actor as stopped before the code
|
|
334
342
|
// registers its internal subscription. XState drops each observer of a stopped
|
|
335
|
-
// actor. The
|
|
343
|
+
// actor. The atoms therefore move never again. There is nothing to tear down
|
|
336
344
|
// during the construction, and the code ignores such a call.
|
|
337
345
|
if (!this.constructed) {
|
|
338
346
|
return this;
|
|
@@ -436,17 +444,19 @@ export class PlayerActor extends Actor {
|
|
|
436
444
|
return forward.call(this, options);
|
|
437
445
|
}
|
|
438
446
|
/**
|
|
439
|
-
* The point where a capability derives its own
|
|
447
|
+
* The point where a capability derives its own atoms from a new snapshot.
|
|
440
448
|
*
|
|
441
449
|
* The base derives nothing: `PlayActor` asks for `state` and `send`, and this class
|
|
442
|
-
* gives exactly those.
|
|
443
|
-
*
|
|
450
|
+
* gives exactly those.
|
|
451
|
+
*
|
|
452
|
+
* A capability that PUBLISHES a value overrides this method NOT. It derives the value
|
|
453
|
+
* with a computed atom over `state`, as `withRouting` and `withView` both do, and the
|
|
454
|
+
* engine then evaluates every derivation from the one write of a transition, in
|
|
455
|
+
* topological order. The composition order therefore decides no value.
|
|
444
456
|
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
* therefore derives the route before the view, because `withView` wraps the class that
|
|
449
|
-
* `withRouting` returned.
|
|
457
|
+
* This method remains for a capability that must run an EFFECT on a transition. Such a
|
|
458
|
+
* mixin overrides it and calls `super.onSnapshot(snapshot)` FIRST, so that every
|
|
459
|
+
* capability composed before it runs its own effect first.
|
|
450
460
|
*
|
|
451
461
|
* The method runs after `state` holds the new snapshot and before the `onStateChange`
|
|
452
462
|
* hook of the options.
|
package/dist/player-actor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player-actor.js","sourceRoot":"","sources":["../src/player-actor.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,GAWL,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"player-actor.js","sourceRoot":"","sources":["../src/player-actor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ParseOptions } from "@xmachines/play-pattern";
|
|
1
2
|
import type { RouteContext } from "./types.js";
|
|
2
3
|
/**
|
|
3
4
|
* Builds a complete URL from a route template and the context of the actor.
|
|
@@ -16,12 +17,18 @@ import type { RouteContext } from "./types.js";
|
|
|
16
17
|
* @param context - The context object of the actor. Each route parameter must be in
|
|
17
18
|
* `context.params`, because the function reads no flat context field. An absent
|
|
18
19
|
* `query` field builds a URL without a query, exactly like `query: {}`.
|
|
20
|
+
* @param options - The caches to read, as {@link ParseOptions}. The default is the cache
|
|
21
|
+
* that `@xmachines/play-pattern` shares with every caller of the process. A parse is
|
|
22
|
+
* deterministic, so this changes how often the parser runs and it changes no answer.
|
|
19
23
|
* @returns The complete URL string.
|
|
20
24
|
*
|
|
21
25
|
* @throws {MissingRouteParamError} When a **necessary** `:param` placeholder has no
|
|
22
26
|
* value in the context. The function omits an optional parameter (`:param?`) in
|
|
23
27
|
* silence when its value is absent. Import the class from
|
|
24
28
|
* `@xmachines/play-xstate/errors`.
|
|
29
|
+
* @throws {InvalidRouteParamError} When a `:param` placeholder carries a dot segment,
|
|
30
|
+
* which a URL resolves away. The template matches the built URL back never, so the
|
|
31
|
+
* actor and the address bar would stay out of step.
|
|
25
32
|
*
|
|
26
33
|
* @example
|
|
27
34
|
* ```typescript
|
|
@@ -32,5 +39,5 @@ import type { RouteContext } from "./types.js";
|
|
|
32
39
|
* // → "/settings" (the optional param is absent, and there is no query string)
|
|
33
40
|
* ```
|
|
34
41
|
*/
|
|
35
|
-
export declare const buildRouteUrl: (routeTemplate: string, context?: RouteContext) => string;
|
|
42
|
+
export declare const buildRouteUrl: (routeTemplate: string, context?: RouteContext, options?: ParseOptions) => string;
|
|
36
43
|
//# sourceMappingURL=build-url.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-url.d.ts","sourceRoot":"","sources":["../../src/routing/build-url.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build-url.d.ts","sourceRoot":"","sources":["../../src/routing/build-url.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,eAAO,MAAM,aAAa,GACzB,eAAe,MAAM,EACrB,UAAS,YAA4B,EACrC,UAAU,YAAY,KACpB,MAgEF,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { buildPath } from "@xmachines/play-pattern";
|
|
1
2
|
import { isAbsoluteRoute } from "./derive-route.js";
|
|
2
|
-
import { MissingRouteParamError } from "../errors.js";
|
|
3
|
+
import { InvalidRouteParamError, MissingRouteParamError } from "../errors.js";
|
|
3
4
|
/**
|
|
4
5
|
* Builds a complete URL from a route template and the context of the actor.
|
|
5
6
|
*
|
|
@@ -17,12 +18,18 @@ import { MissingRouteParamError } from "../errors.js";
|
|
|
17
18
|
* @param context - The context object of the actor. Each route parameter must be in
|
|
18
19
|
* `context.params`, because the function reads no flat context field. An absent
|
|
19
20
|
* `query` field builds a URL without a query, exactly like `query: {}`.
|
|
21
|
+
* @param options - The caches to read, as {@link ParseOptions}. The default is the cache
|
|
22
|
+
* that `@xmachines/play-pattern` shares with every caller of the process. A parse is
|
|
23
|
+
* deterministic, so this changes how often the parser runs and it changes no answer.
|
|
20
24
|
* @returns The complete URL string.
|
|
21
25
|
*
|
|
22
26
|
* @throws {MissingRouteParamError} When a **necessary** `:param` placeholder has no
|
|
23
27
|
* value in the context. The function omits an optional parameter (`:param?`) in
|
|
24
28
|
* silence when its value is absent. Import the class from
|
|
25
29
|
* `@xmachines/play-xstate/errors`.
|
|
30
|
+
* @throws {InvalidRouteParamError} When a `:param` placeholder carries a dot segment,
|
|
31
|
+
* which a URL resolves away. The template matches the built URL back never, so the
|
|
32
|
+
* actor and the address bar would stay out of step.
|
|
26
33
|
*
|
|
27
34
|
* @example
|
|
28
35
|
* ```typescript
|
|
@@ -33,7 +40,7 @@ import { MissingRouteParamError } from "../errors.js";
|
|
|
33
40
|
* // → "/settings" (the optional param is absent, and there is no query string)
|
|
34
41
|
* ```
|
|
35
42
|
*/
|
|
36
|
-
export const buildRouteUrl = (routeTemplate, context = { query: {} }) => {
|
|
43
|
+
export const buildRouteUrl = (routeTemplate, context = { query: {} }, options) => {
|
|
37
44
|
// A context without a `query` field builds a URL without a query, in the same way
|
|
38
45
|
// as an absent context. The generated play.route transitions assign `query` on
|
|
39
46
|
// every navigation, and the shape of the initial context has no effect on that.
|
|
@@ -43,10 +50,31 @@ export const buildRouteUrl = (routeTemplate, context = { query: {} }) => {
|
|
|
43
50
|
// Handle a relative path and an absolute path
|
|
44
51
|
const basePath = context.basePath || "";
|
|
45
52
|
const isAbsolute = isAbsoluteRoute(routeTemplate);
|
|
46
|
-
// Build the base URL
|
|
47
|
-
|
|
48
|
-
//
|
|
49
|
-
|
|
53
|
+
// Build the base URL. Join every run of separators into one, exactly as
|
|
54
|
+
// `buildRouteTree` of `@xmachines/play-router` does for the `fullPath` that the route
|
|
55
|
+
// map holds: `deriveRoute` writes `${parent}/${child}`, so a parent route that ends
|
|
56
|
+
// with a `/` gives `/app//sub` while the map holds `/app/sub`. A bridge pushed that
|
|
57
|
+
// location, the router reported `/app/sub` back — `sanitizePathname` collapses it —
|
|
58
|
+
// and the echo test then read its OWN push as a move of the user and sent a
|
|
59
|
+
// `play.route` for the state the actor was in already.
|
|
60
|
+
//
|
|
61
|
+
// The collapse runs on the TEMPLATE, and not on the built path, so a `:path*` value
|
|
62
|
+
// keeps every separator that it carries.
|
|
63
|
+
let url = (isAbsolute ? routeTemplate : joinPaths(basePath, routeTemplate)).replace(/\/+/g, "/");
|
|
64
|
+
// Replace each param with a context value. `buildPath` of `@xmachines/play-pattern`
|
|
65
|
+
// reads the WHOLE pattern grammar, which is the same parse that the match reads, so a
|
|
66
|
+
// route that the router matches derives a URL that the router matches back. That
|
|
67
|
+
// package carries the language alone: no URLPattern, and no polyfill for it.
|
|
68
|
+
const built = buildPath(url, context.params ?? {}, options);
|
|
69
|
+
if (!built.ok) {
|
|
70
|
+
if (built.reason === "unresolvable") {
|
|
71
|
+
throw new InvalidRouteParamError(built.param, built.value, routeTemplate);
|
|
72
|
+
}
|
|
73
|
+
throw new MissingRouteParamError(built.missing, routeTemplate);
|
|
74
|
+
}
|
|
75
|
+
// A template of the root level with optional params only, for example `/:section?`,
|
|
76
|
+
// builds `""`. That value makes an invalid URL, such as `""` or `"?tab=x"`.
|
|
77
|
+
url = built.path === "" ? "/" : built.path;
|
|
50
78
|
// Append the query params of the context. The frequent empty `query: {}` passes the
|
|
51
79
|
// key check first. Therefore each recomputation allocates a URLSearchParams object
|
|
52
80
|
// only when it has something to write.
|
|
@@ -66,54 +94,6 @@ export const buildRouteUrl = (routeTemplate, context = { query: {} }) => {
|
|
|
66
94
|
}
|
|
67
95
|
return url;
|
|
68
96
|
};
|
|
69
|
-
/**
|
|
70
|
-
* Replaces each `:param` placeholder with a context value, and it escapes that
|
|
71
|
-
* value with `encodeURIComponent`.
|
|
72
|
-
*
|
|
73
|
-
* The function supports an optional parameter, in the form :param?.
|
|
74
|
-
* - It removes an optional parameter without a value completely, and it also removes the /
|
|
75
|
-
* - It writes a warning for a necessary parameter without a value
|
|
76
|
-
*
|
|
77
|
-
* The lookup of a parameter:
|
|
78
|
-
* - The function reads context.params[param] only. It reads no flat context field.
|
|
79
|
-
*
|
|
80
|
-
* @param template - The URL template, with the :param or :param? syntax
|
|
81
|
-
* @param context - The context with the parameter values. It can have a params field
|
|
82
|
-
* @returns The URL with the parameters in place, and without a double slash
|
|
83
|
-
* @throws {MissingRouteParamError} When a necessary route parameter is absent
|
|
84
|
-
*/
|
|
85
|
-
const substituteParams = (template, context) => {
|
|
86
|
-
// Replace each parameter, and handle the optional syntax
|
|
87
|
-
let hasOptionalRemoval = false;
|
|
88
|
-
const result = template.replace(/:(\w+)(\?)?/g, (_match, param, optional) => {
|
|
89
|
-
const value = context.params?.[param]; // nosemgrep: gitlab.eslint.detect-object-injection
|
|
90
|
-
// The parameter has a value that is not empty: put it in place.
|
|
91
|
-
// For an optional param, an empty string means "no value".
|
|
92
|
-
if (value !== undefined && value !== null && value !== "") {
|
|
93
|
-
return encodeURIComponent(String(value));
|
|
94
|
-
}
|
|
95
|
-
// An optional parameter without a value, or with an empty string: remove the segment
|
|
96
|
-
if (optional === "?") {
|
|
97
|
-
hasOptionalRemoval = true;
|
|
98
|
-
return ""; // This leaves // in the path. The code below removes it
|
|
99
|
-
}
|
|
100
|
-
throw new MissingRouteParamError(param, template);
|
|
101
|
-
});
|
|
102
|
-
// Remove each double slash
|
|
103
|
-
let cleaned = result.replace(/\/+/g, "/");
|
|
104
|
-
// Remove a trailing slash only after the code removed an optional parameter.
|
|
105
|
-
// A trailing slash of a necessary param stays, because it shows an error.
|
|
106
|
-
if (hasOptionalRemoval && cleaned.endsWith("/")) {
|
|
107
|
-
cleaned = cleaned.slice(0, -1);
|
|
108
|
-
}
|
|
109
|
-
// Return an empty path never: a template of the root level with optional params
|
|
110
|
-
// only, for example "/:section?", becomes "" after the trim of the trailing slash.
|
|
111
|
-
// That value makes an invalid URL, such as "" or "?tab=x". Normalize it to "/".
|
|
112
|
-
if (cleaned === "") {
|
|
113
|
-
cleaned = "/";
|
|
114
|
-
}
|
|
115
|
-
return cleaned;
|
|
116
|
-
};
|
|
117
97
|
/**
|
|
118
98
|
* Joins a base path and a relative path
|
|
119
99
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-url.js","sourceRoot":"","sources":["../../src/routing/build-url.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build-url.js","sourceRoot":"","sources":["../../src/routing/build-url.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AnyMachineSnapshot } from "xstate";
|
|
2
|
+
import type { ParseOptions } from "@xmachines/play-pattern";
|
|
2
3
|
export { activeStateMeta, firstActiveBranchMeta } from "../state-meta.js";
|
|
3
4
|
/**
|
|
4
5
|
* Derives the current URL of the actor from the state metadata and the context.
|
|
@@ -11,7 +12,7 @@ export { activeStateMeta, firstActiveBranchMeta } from "../state-meta.js";
|
|
|
11
12
|
* - The snapshot has no route metadata, which means a state without a route
|
|
12
13
|
* - The context does not hold a necessary route parameter (`MissingRouteParamError`)
|
|
13
14
|
*
|
|
14
|
-
* The `null` value for an absent param is deliberate: the computed
|
|
15
|
+
* The `null` value for an absent param is deliberate: the computed atom therefore
|
|
15
16
|
* stays stable during a temporary state. Such a state appears during a transition,
|
|
16
17
|
* before the context is complete, and also after a logout, when `context.username`
|
|
17
18
|
* is `null` and the router bridge did not reach the new state yet. The router bridge
|
|
@@ -19,7 +20,15 @@ export { activeStateMeta, firstActiveBranchMeta } from "../state-meta.js";
|
|
|
19
20
|
* context is complete.
|
|
20
21
|
*
|
|
21
22
|
* @param snapshot - The current snapshot of the XState machine.
|
|
23
|
+
* @param options - The caches that the parse of the route TEMPLATE reads. The default is
|
|
24
|
+
* the cache that `@xmachines/play-pattern` shares with every caller of the process.
|
|
25
|
+
*
|
|
26
|
+
* `withRouting` calls this function with no options, and that is deliberate: it parses
|
|
27
|
+
* the `meta.route` template of a state, and a machine declares a small and static set of
|
|
28
|
+
* those. The unbounded key space that a cache of the caller answers is the one of
|
|
29
|
+
* `RouterBridgeBase`, which parses a CONCRETE location — one for each value that an
|
|
30
|
+
* application puts in a param — and a bridge takes its caches already.
|
|
22
31
|
* @returns The resolved URL string, or `null` when the function cannot resolve the route.
|
|
23
32
|
*/
|
|
24
|
-
export declare const deriveCurrentRoute: (snapshot: AnyMachineSnapshot) => string | null;
|
|
33
|
+
export declare const deriveCurrentRoute: (snapshot: AnyMachineSnapshot, options?: ParseOptions) => string | null;
|
|
25
34
|
//# sourceMappingURL=derive-current-route.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive-current-route.d.ts","sourceRoot":"","sources":["../../src/routing/derive-current-route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"derive-current-route.d.ts","sourceRoot":"","sources":["../../src/routing/derive-current-route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAMjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG5D,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,kBAAkB,GAC9B,UAAU,kBAAkB,EAC5B,UAAU,YAAY,KACpB,MAAM,GAAG,IAsCX,CAAC"}
|
|
@@ -14,7 +14,7 @@ export { activeStateMeta, firstActiveBranchMeta } from "../state-meta.js";
|
|
|
14
14
|
* - The snapshot has no route metadata, which means a state without a route
|
|
15
15
|
* - The context does not hold a necessary route parameter (`MissingRouteParamError`)
|
|
16
16
|
*
|
|
17
|
-
* The `null` value for an absent param is deliberate: the computed
|
|
17
|
+
* The `null` value for an absent param is deliberate: the computed atom therefore
|
|
18
18
|
* stays stable during a temporary state. Such a state appears during a transition,
|
|
19
19
|
* before the context is complete, and also after a logout, when `context.username`
|
|
20
20
|
* is `null` and the router bridge did not reach the new state yet. The router bridge
|
|
@@ -22,9 +22,17 @@ export { activeStateMeta, firstActiveBranchMeta } from "../state-meta.js";
|
|
|
22
22
|
* context is complete.
|
|
23
23
|
*
|
|
24
24
|
* @param snapshot - The current snapshot of the XState machine.
|
|
25
|
+
* @param options - The caches that the parse of the route TEMPLATE reads. The default is
|
|
26
|
+
* the cache that `@xmachines/play-pattern` shares with every caller of the process.
|
|
27
|
+
*
|
|
28
|
+
* `withRouting` calls this function with no options, and that is deliberate: it parses
|
|
29
|
+
* the `meta.route` template of a state, and a machine declares a small and static set of
|
|
30
|
+
* those. The unbounded key space that a cache of the caller answers is the one of
|
|
31
|
+
* `RouterBridgeBase`, which parses a CONCRETE location — one for each value that an
|
|
32
|
+
* application puts in a param — and a bridge takes its caches already.
|
|
25
33
|
* @returns The resolved URL string, or `null` when the function cannot resolve the route.
|
|
26
34
|
*/
|
|
27
|
-
export const deriveCurrentRoute = (snapshot) => {
|
|
35
|
+
export const deriveCurrentRoute = (snapshot, options) => {
|
|
28
36
|
const meta = activeStateMeta(snapshot);
|
|
29
37
|
if (!meta) {
|
|
30
38
|
return null;
|
|
@@ -34,7 +42,7 @@ export const deriveCurrentRoute = (snapshot) => {
|
|
|
34
42
|
return null;
|
|
35
43
|
}
|
|
36
44
|
try {
|
|
37
|
-
return buildRouteUrl(routeTemplate, (snapshot.context ?? {}));
|
|
45
|
+
return buildRouteUrl(routeTemplate, (snapshot.context ?? {}), options);
|
|
38
46
|
}
|
|
39
47
|
catch (error) {
|
|
40
48
|
// MissingRouteParamError: the condition is temporary, and it resolves itself. A
|
|
@@ -43,14 +51,22 @@ export const deriveCurrentRoute = (snapshot) => {
|
|
|
43
51
|
// context.params, and also after a logout, before the bridge redirects. A `null`
|
|
44
52
|
// value is correct here:
|
|
45
53
|
// - a router bridge reads null and skips the navigation (syncRouterFromActor, line 242)
|
|
46
|
-
// - the
|
|
47
|
-
// - a
|
|
48
|
-
//
|
|
54
|
+
// - the atom computes its value again on the next snapshot, when the params arrive
|
|
55
|
+
// - a report of a condition that resolves itself is worse than a null value for
|
|
56
|
+
// one tick.
|
|
49
57
|
// Do NOT convert this into a throw.
|
|
50
58
|
if (error instanceof MissingRouteParamError) {
|
|
51
59
|
return null;
|
|
52
60
|
}
|
|
53
61
|
// Each unexpected error goes to the caller without a change.
|
|
62
|
+
//
|
|
63
|
+
// This function is PURE, and it stays that way: it has no actor, so it holds no
|
|
64
|
+
// error channel and no previous value. The CALLER decides what an unexpected error
|
|
65
|
+
// means. `currentRoute` of `with-routing.ts` is that caller for the reactive path —
|
|
66
|
+
// it reports through `onError` of the actor and keeps the last route, because a
|
|
67
|
+
// throw from there would unwind into the transition. `initialRoute` is the other
|
|
68
|
+
// caller, and a throw from it fails the construction, which is where a developer
|
|
69
|
+
// wants to see a route template that cannot be built.
|
|
54
70
|
throw error;
|
|
55
71
|
}
|
|
56
72
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive-current-route.js","sourceRoot":"","sources":["../../src/routing/derive-current-route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"derive-current-route.js","sourceRoot":"","sources":["../../src/routing/derive-current-route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CACjC,QAA4B,EAC5B,OAAsB,EACN,EAAE;IAClB,MAAM,IAAI,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,aAAa,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IACxC,IAAI,CAAC,aAAa,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,IAAI,CAAC;QACJ,OAAO,aAAa,CAAC,aAAa,EAAE,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAiB,EAAE,OAAO,CAAC,CAAC;IACxF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,gFAAgF;QAChF,oFAAoF;QACpF,gFAAgF;QAChF,iFAAiF;QACjF,yBAAyB;QACzB,0FAA0F;QAC1F,qFAAqF;QACrF,kFAAkF;QAClF,gBAAgB;QAChB,oCAAoC;QACpC,IAAI,KAAK,YAAY,sBAAsB,EAAE,CAAC;YAC7C,OAAO,IAAI,CAAC;QACb,CAAC;QACD,6DAA6D;QAC7D,EAAE;QACF,gFAAgF;QAChF,mFAAmF;QACnF,oFAAoF;QACpF,gFAAgF;QAChF,iFAAiF;QACjF,iFAAiF;QACjF,sDAAsD;QACtD,MAAM,KAAK,CAAC;IACb,CAAC;AACF,CAAC,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AnyStateMachine } from "xstate";
|
|
2
|
+
import type { ParseOptions } from "@xmachines/play-pattern";
|
|
2
3
|
/**
|
|
3
4
|
* Derives the initial route of the machine directly from the machine definition.
|
|
4
5
|
*
|
|
@@ -39,5 +40,5 @@ import { type AnyStateMachine } from "xstate";
|
|
|
39
40
|
* deriveInitialRoute(machine, { username: "alice" }); // "/profile/alice"
|
|
40
41
|
* ```
|
|
41
42
|
*/
|
|
42
|
-
export declare const deriveInitialRoute: (machine: AnyStateMachine, input?: unknown) => string | null;
|
|
43
|
+
export declare const deriveInitialRoute: (machine: AnyStateMachine, input?: unknown, options?: ParseOptions) => string | null;
|
|
43
44
|
//# sourceMappingURL=derive-initial-route.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive-initial-route.d.ts","sourceRoot":"","sources":["../../src/routing/derive-initial-route.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"derive-initial-route.d.ts","sourceRoot":"","sources":["../../src/routing/derive-initial-route.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8C,KAAK,eAAe,EAAE,MAAM,QAAQ,CAAC;AAG1F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,eAAO,MAAM,kBAAkB,GAC9B,SAAS,eAAe,EACxB,QAAQ,OAAO,EACf,UAAU,YAAY,KACpB,MAAM,GAAG,IAwBX,CAAC"}
|
|
@@ -40,7 +40,7 @@ import { deriveCurrentRoute } from "./derive-current-route.js";
|
|
|
40
40
|
* deriveInitialRoute(machine, { username: "alice" }); // "/profile/alice"
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
|
-
export const deriveInitialRoute = (machine, input) => {
|
|
43
|
+
export const deriveInitialRoute = (machine, input, options) => {
|
|
44
44
|
let initialSnapshot;
|
|
45
45
|
try {
|
|
46
46
|
[initialSnapshot] = initialTransition(machine, input);
|
|
@@ -63,6 +63,6 @@ export const deriveInitialRoute = (machine, input) => {
|
|
|
63
63
|
// question, and it holds whichever way a later release reports the failure.
|
|
64
64
|
if (initialSnapshot.status === "error")
|
|
65
65
|
return null;
|
|
66
|
-
return deriveCurrentRoute(initialSnapshot);
|
|
66
|
+
return deriveCurrentRoute(initialSnapshot, options);
|
|
67
67
|
};
|
|
68
68
|
//# sourceMappingURL=derive-initial-route.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive-initial-route.js","sourceRoot":"","sources":["../../src/routing/derive-initial-route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAiD,MAAM,QAAQ,CAAC;AAE1F,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"derive-initial-route.js","sourceRoot":"","sources":["../../src/routing/derive-initial-route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAiD,MAAM,QAAQ,CAAC;AAE1F,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CACjC,OAAwB,EACxB,KAAe,EACf,OAAsB,EACN,EAAE;IAClB,IAAI,eAAmC,CAAC;IACxC,IAAI,CAAC;QACJ,CAAC,eAAe,CAAC,GAAG,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACR,gFAAgF;QAChF,oFAAoF;QACpF,qFAAqF;QACrF,iFAAiF;QACjF,OAAO,IAAI,CAAC;IACb,CAAC;IAED,qEAAqE;IACrE,6EAA6E;IAC7E,sFAAsF;IACtF,sFAAsF;IACtF,sFAAsF;IACtF,6BAA6B;IAC7B,EAAE;IACF,kFAAkF;IAClF,4EAA4E;IAC5E,IAAI,eAAe,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAEpD,OAAO,kBAAkB,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AACrD,CAAC,CAAC"}
|
|
@@ -28,6 +28,13 @@ const asTransitionArray = (value) => value === undefined ? [] : Array.isArray(va
|
|
|
28
28
|
* event: the declaration of a state is static.
|
|
29
29
|
*/
|
|
30
30
|
const declaresRouteData = (route) => typeof route === "object" && route !== null && route.data !== undefined;
|
|
31
|
+
/**
|
|
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 || {});
|
|
31
38
|
/**
|
|
32
39
|
* Builds the assign action of a generated `play.route` transition.
|
|
33
40
|
*
|
|
@@ -41,13 +48,11 @@ const declaresRouteData = (route) => typeof route === "object" && route !== null
|
|
|
41
48
|
* received one with no value, and the `/context` projection of every view carried it.
|
|
42
49
|
*/
|
|
43
50
|
const makeRouteAssign = (routeMeta) => {
|
|
44
|
-
const params = ({ context, event }) => keepEqualContainer(context.params, event.params || {});
|
|
45
|
-
const query = ({ context, event }) => keepEqualContainer(context.query, event.query || {});
|
|
46
51
|
if (!declaresRouteData(routeMeta))
|
|
47
|
-
return assign({ params, query });
|
|
52
|
+
return assign({ params: assignParams, query: assignQuery });
|
|
48
53
|
return assign({
|
|
49
|
-
params,
|
|
50
|
-
query,
|
|
54
|
+
params: assignParams,
|
|
55
|
+
query: assignQuery,
|
|
51
56
|
// A resolver that answers `undefined` leaves the field of the context as it stands,
|
|
52
57
|
// and it therefore writes `undefined` over the data of an earlier route never.
|
|
53
58
|
data: ({ context, event }) => {
|