@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
package/dist/player-actor.js
CHANGED
|
@@ -1,454 +1,324 @@
|
|
|
1
|
-
import { Actor, } from "xstate";
|
|
2
|
-
import { createWritableAtom } from "@xmachines/play-atom";
|
|
3
|
-
import { isError } from "@xmachines/core/utils";
|
|
4
1
|
import { ActorThrewNonErrorError, InvalidEventError, InvalidMachineError } from "./errors.js";
|
|
2
|
+
import { isError } from "@xmachines/core/utils";
|
|
3
|
+
import { Actor } from "xstate";
|
|
4
|
+
import { createWritableAtom } from "@xmachines/play-atom";
|
|
5
|
+
//#region packages/play-xstate/src/player-actor.ts
|
|
5
6
|
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
// The classification itself can throw for a hostile value, because a revoked
|
|
24
|
-
// Proxy traps instanceof and also the inspection of the brand. Continue, and wrap
|
|
25
|
-
// the value.
|
|
26
|
-
}
|
|
27
|
-
return new ActorThrewNonErrorError(value);
|
|
7
|
+
* Normalizes a failure of the actor for `onError`.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*
|
|
11
|
+
* The function gives an `Error` to the handler without a change. The error of the
|
|
12
|
+
* machine therefore keeps its identity: an `instanceof` test of a consumer still
|
|
13
|
+
* works, and the path without an `onError` throws that same object again. Every
|
|
14
|
+
* other value is ours to build, and it becomes a `PlayError` with a code. That
|
|
15
|
+
* error carries the value from the throw as its `cause`.
|
|
16
|
+
*/
|
|
17
|
+
var toError = (value) => {
|
|
18
|
+
try {
|
|
19
|
+
if (isError(value)) return value;
|
|
20
|
+
} catch {}
|
|
21
|
+
return new ActorThrewNonErrorError(value);
|
|
28
22
|
};
|
|
29
23
|
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
* Tells you if a snapshot is worth a propagation to the atoms: an active
|
|
25
|
+
* snapshot, or a "done" snapshot, which means that the machine reached a final state
|
|
26
|
+
* at the top level. The code skips an error snapshot and a stopped snapshot.
|
|
27
|
+
* Therefore the atoms keep the last observable state, and they show no artifact of
|
|
28
|
+
* a teardown or of an error.
|
|
29
|
+
*/
|
|
30
|
+
var isObservableSnapshot = (snapshot) => snapshot.status === "active" || snapshot.status === "done";
|
|
37
31
|
/**
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
super.stop();
|
|
332
|
-
const wasRunning = this.lifecycle === "running";
|
|
333
|
-
this.lifecycle = "stopped";
|
|
334
|
-
const onStop = this.hooks.onStop;
|
|
335
|
-
if (wasRunning && onStop) {
|
|
336
|
-
onStop(this);
|
|
337
|
-
}
|
|
338
|
-
return this;
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Sends an event to this actor.
|
|
342
|
-
*
|
|
343
|
-
* The guards of the state machine of the actor decide if the event causes a
|
|
344
|
-
* transition. Give any event of the event union of the machine: a domain event, a
|
|
345
|
-
* routing event, and so on.
|
|
346
|
-
*
|
|
347
|
-
* @param event - An event of the `EventFromLogic<TMachine>` union of the machine.
|
|
348
|
-
*
|
|
349
|
-
* @throws {InvalidEventError} When `event` is not a plain object, for example
|
|
350
|
-
* `null`, `undefined`, a string, or a number. Import the class from
|
|
351
|
-
* `@xmachines/play-xstate/errors`.
|
|
352
|
-
*
|
|
353
|
-
* @example
|
|
354
|
-
* ```typescript
|
|
355
|
-
* // A domain event, with the type of the event union of the machine
|
|
356
|
-
* actor.send({ type: "auth.login", userId: "123" });
|
|
357
|
-
*
|
|
358
|
-
* // A routing event
|
|
359
|
-
* actor.send({ type: "play.route", to: "#home" });
|
|
360
|
-
* ```
|
|
361
|
-
*/
|
|
362
|
-
send(event) {
|
|
363
|
-
// A defensive check: the event must not be null and not undefined
|
|
364
|
-
if (!event || typeof event !== "object") {
|
|
365
|
-
throw new InvalidEventError(event);
|
|
366
|
-
}
|
|
367
|
-
// Inside the construction window there is no readable snapshot, and no hook can
|
|
368
|
-
// exist yet: deliver the event and return.
|
|
369
|
-
if (!this.constructed) {
|
|
370
|
-
Actor.prototype.send.call(this, event);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
// The code captures the snapshot always, because getSnapshot() is one property
|
|
374
|
-
// read, and because the options object is live: an onTransition handler that
|
|
375
|
-
// arrives during the processing of this event, for example from onStateChange,
|
|
376
|
-
// must still run for it, with the correct snapshot from before the send.
|
|
377
|
-
const prevSnapshot = this.getSnapshot();
|
|
378
|
-
// Send the event to the XState actor.
|
|
379
|
-
// The class declares send() to narrow the
|
|
380
|
-
// event type, and TypeScript forbids a super call to an abstract member.
|
|
381
|
-
// Therefore the code reaches the implementation of XState directly. `this` IS the
|
|
382
|
-
// actor. This call is therefore exactly the call of `super.send(event)`: the relay
|
|
383
|
-
// that emits the @xstate.event inspection event and puts the event in the mailbox
|
|
384
|
-
// of this actor.
|
|
385
|
-
Actor.prototype.send.call(this, event);
|
|
386
|
-
// Call the onTransition hook
|
|
387
|
-
const onTransition = this.hooks.onTransition;
|
|
388
|
-
if (onTransition) {
|
|
389
|
-
const nextSnapshot = this.getSnapshot();
|
|
390
|
-
onTransition(this, prevSnapshot, nextSnapshot);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
subscribe(nextListenerOrObserver, errorListener, completeListener) {
|
|
394
|
-
// The subscribe() method of XState accepts a function and also an observer, and it
|
|
395
|
-
// normalizes them internally. The cast joins the two overload signatures only.
|
|
396
|
-
const subscription = super.subscribe(nextListenerOrObserver, errorListener, completeListener);
|
|
397
|
-
// An observer without an error listener makes XState throw each actor error again,
|
|
398
|
-
// globally, for that observer. The code counts those observers. Therefore the loud
|
|
399
|
-
// default of the internal listener, which also throws again, stands down while one
|
|
400
|
-
// of them is active. See the error listener of the constructor.
|
|
401
|
-
const hasErrorListener = typeof nextListenerOrObserver === "object" && nextListenerOrObserver !== null
|
|
402
|
-
? typeof nextListenerOrObserver.error === "function"
|
|
403
|
-
: typeof errorListener === "function";
|
|
404
|
-
if (hasErrorListener) {
|
|
405
|
-
return subscription;
|
|
406
|
-
}
|
|
407
|
-
this.nextOnlySubscriptions = (this.nextOnlySubscriptions ?? 0) + 1;
|
|
408
|
-
let counted = true;
|
|
409
|
-
return {
|
|
410
|
-
unsubscribe: () => {
|
|
411
|
-
if (counted) {
|
|
412
|
-
counted = false;
|
|
413
|
-
this.nextOnlySubscriptions = (this.nextOnlySubscriptions ?? 1) - 1;
|
|
414
|
-
}
|
|
415
|
-
subscription.unsubscribe();
|
|
416
|
-
},
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Returns the persisted snapshot of this actor.
|
|
421
|
-
*
|
|
422
|
-
* Use it to serialize the state, and to restore it later with the
|
|
423
|
-
* `restore.snapshot` option of the factory.
|
|
424
|
-
*/
|
|
425
|
-
getPersistedSnapshot(options) {
|
|
426
|
-
const forward = super.getPersistedSnapshot;
|
|
427
|
-
return forward.call(this, options);
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* The point where a capability derives its own atoms from a new snapshot.
|
|
431
|
-
*
|
|
432
|
-
* The base derives nothing: `PlayActor` asks for `state` and `send`, and this class
|
|
433
|
-
* gives exactly those.
|
|
434
|
-
*
|
|
435
|
-
* A capability that PUBLISHES a value overrides this method NOT. It derives the value
|
|
436
|
-
* with a computed atom over `state`, as `withRouting` and `withView` both do, and the
|
|
437
|
-
* engine then evaluates every derivation from the one write of a transition, in
|
|
438
|
-
* topological order. The composition order therefore decides no value.
|
|
439
|
-
*
|
|
440
|
-
* This method remains for a capability that must run an EFFECT on a transition. Such a
|
|
441
|
-
* mixin overrides it and calls `super.onSnapshot(snapshot)` FIRST, so that every
|
|
442
|
-
* capability composed before it runs its own effect first.
|
|
443
|
-
*
|
|
444
|
-
* The method runs after `state` holds the new snapshot and before the `onStateChange`
|
|
445
|
-
* hook of the options.
|
|
446
|
-
*
|
|
447
|
-
* @param snapshot - The stable snapshot of this transition.
|
|
448
|
-
*/
|
|
449
|
-
onSnapshot(snapshot) {
|
|
450
|
-
// The base class holds no capability. A mixin overrides this method.
|
|
451
|
-
void snapshot;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
32
|
+
* The concrete XState actor. It implements the atom protocol of the Play Architecture
|
|
33
|
+
*
|
|
34
|
+
* The class extends the `Actor` class of XState directly, and it implements
|
|
35
|
+
* {@link @xmachines/play-actor!PlayActor}. It gives you the XState v5 integration, and it keeps the
|
|
36
|
+
* compatibility with the ecosystem, such as the XState inspection and the devtools.
|
|
37
|
+
* The constructor of the base class receives the machine. Therefore a `PlayerActor`
|
|
38
|
+
* **is** the XState actor, and it is no wrapper around one: every member of the
|
|
39
|
+
* XState `Actor` class works on the state of this instance, and this class adds the
|
|
40
|
+
* reactive state on the atoms for the observation by the infrastructure.
|
|
41
|
+
*
|
|
42
|
+
* **Capabilities:** the class implements both the
|
|
43
|
+
* {@link @xmachines/play-router!index.Routable} interface and the
|
|
44
|
+
* {@link @xmachines/play-view!index.Viewable} interface. It therefore supports the
|
|
45
|
+
* routing and the view rendering.
|
|
46
|
+
*
|
|
47
|
+
* **Architectural context:** the class implements **Actor Authority (INV-01)**,
|
|
48
|
+
* because the guards of the XState machine control every decision of the
|
|
49
|
+
* navigation. The infrastructure observes the atoms of the actor (`state`,
|
|
50
|
+
* `currentRoute`, and `currentView`), but it changes no state directly: every
|
|
51
|
+
* change goes through the event handlers of the state machine.
|
|
52
|
+
*
|
|
53
|
+
* @typeParam TMachine - The type of the XState v5 state machine
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* The creation of an actor, and its lifecycle
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import { setup } from "xstate";
|
|
59
|
+
* import { definePlayer, compose, PlayerActor } from "@xmachines/play-xstate";
|
|
60
|
+
*
|
|
61
|
+
* const machine = setup({}).createMachine({
|
|
62
|
+
* initial: 'idle',
|
|
63
|
+
* states: {
|
|
64
|
+
* idle: {
|
|
65
|
+
* meta: {
|
|
66
|
+
* route: '/',
|
|
67
|
+
* // A view spec needs `root` and `elements`. Every other shape derives null.
|
|
68
|
+
* view: {
|
|
69
|
+
* root: 'home',
|
|
70
|
+
* elements: { home: { type: 'HomePage', props: {}, children: [] } },
|
|
71
|
+
* },
|
|
72
|
+
* },
|
|
73
|
+
* }
|
|
74
|
+
* }
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* const createPlayer = definePlayer({ machine });
|
|
78
|
+
* const actor = createPlayer();
|
|
79
|
+
* actor.start();
|
|
80
|
+
*
|
|
81
|
+
* // Observe the atoms
|
|
82
|
+
* console.log(actor.currentRoute.get()); // '/'
|
|
83
|
+
* console.log(actor.currentView.get()?.root); // 'home'
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* The atom lifecycle with a watch
|
|
88
|
+
* ```typescript
|
|
89
|
+
* import { watchAtom } from "@xmachines/play-atom";
|
|
90
|
+
*
|
|
91
|
+
* const stop = watchAtom(actor.state, (snapshot) => {
|
|
92
|
+
* console.log('State changed:', snapshot.value);
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* actor.send({ type: 'play.route', to: '#about' });
|
|
96
|
+
* // watchAtom delivers the new value from a microtask
|
|
97
|
+
*
|
|
98
|
+
* stop();
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* @see [Play RFC](../../docs/rfc/play.md)
|
|
102
|
+
* @see {@link definePlayer} for the creation through a factory
|
|
103
|
+
* @see {@link @xmachines/play-actor!PlayActor} for the atom protocol
|
|
104
|
+
* @see {@link @xmachines/play-router!index.Routable} for the routing capability
|
|
105
|
+
* @see {@link @xmachines/play-view!index.Viewable} for the view rendering capability
|
|
106
|
+
*
|
|
107
|
+
* @remarks
|
|
108
|
+
* **The routing:** this actor supports the `route: {}` config pattern of XState and
|
|
109
|
+
* also a `play.route` event with parameters. The `deriveRoute()` function reads
|
|
110
|
+
* `meta.route`, which is the Stately pattern, for a URL template, and it substitutes
|
|
111
|
+
* each parameter.
|
|
112
|
+
*
|
|
113
|
+
* **The pattern of the view atom:** `withView` of `@xmachines/play-xstate/view` adds
|
|
114
|
+
* `currentView`, and that atom is a COMPUTED atom over `state`. One write for each
|
|
115
|
+
* transition — `state.set(snapshot)` — therefore reaches the view and the route in one
|
|
116
|
+
* flush, and no subscriber reads the new state beside an old derivation. The atom
|
|
117
|
+
* memoizes the result, so a repeated read computes no view again.
|
|
118
|
+
*/
|
|
119
|
+
var PlayerActor = class extends Actor {
|
|
120
|
+
playerOptions;
|
|
121
|
+
/**
|
|
122
|
+
* The three inputs of the constructor that a CAPABILITY needs.
|
|
123
|
+
*
|
|
124
|
+
* A mixin over a generic base cannot declare a constructor: TypeScript then demands the
|
|
125
|
+
* signature `...args: any[]` (TS2545), which this workspace does not write. A field
|
|
126
|
+
* initializer of a subclass runs after `super()` instead, and it reads these. The
|
|
127
|
+
* routing capability derives `initialRoute` from all three.
|
|
128
|
+
*/
|
|
129
|
+
playerMachine;
|
|
130
|
+
/** @see {@link playerMachine} */
|
|
131
|
+
playerInput;
|
|
132
|
+
/** @see {@link playerMachine} */
|
|
133
|
+
playerRestoredSnapshot;
|
|
134
|
+
/**
|
|
135
|
+
* The live options object of the caller, or an empty object during the construction.
|
|
136
|
+
*
|
|
137
|
+
* XState gives this actor to a context factory as `self`, and to an `inspect`
|
|
138
|
+
* observer as `actorRef`, from inside its own constructor, before the code assigns
|
|
139
|
+
* any field here. A read of a hook through this accessor therefore does not throw
|
|
140
|
+
* in that window. A throw goes to the initialization of XState, which parks it as
|
|
141
|
+
* an error snapshot. The accessor also keeps the behavior that the documentation of
|
|
142
|
+
* the object promises: a read at the moment of the delivery.
|
|
143
|
+
*
|
|
144
|
+
* The fields below that this window touches have a `declare` modifier for the same
|
|
145
|
+
* reason. This target compiles a class field into `Object.defineProperty`, which
|
|
146
|
+
* runs after `super()` returns. A plain declaration, with an initializer or without
|
|
147
|
+
* one, therefore resets each value of the window to `undefined`. A `declare`
|
|
148
|
+
* modifier emits nothing, and those values survive.
|
|
149
|
+
*/
|
|
150
|
+
get hooks() {
|
|
151
|
+
return this.playerOptions ?? {};
|
|
152
|
+
}
|
|
153
|
+
state;
|
|
154
|
+
/**
|
|
155
|
+
* Tells you if the current state of the actor accepts the given event.
|
|
156
|
+
*
|
|
157
|
+
* The type is the event union of the machine. An unknown event type is therefore a
|
|
158
|
+
* compile error. The method evaluates the event against the snapshot atom.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* if (actor.can({ type: "auth.logout" })) { ... }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
can(event) {
|
|
166
|
+
const snapshot = this.state?.get();
|
|
167
|
+
if (snapshot?.status !== "active") return false;
|
|
168
|
+
return typeof snapshot.can === "function" ? snapshot.can(event) : false;
|
|
169
|
+
}
|
|
170
|
+
constructor(machine, options, input, restoredSnapshot) {
|
|
171
|
+
if (!machine || typeof machine !== "object") throw new InvalidMachineError();
|
|
172
|
+
super(machine, {
|
|
173
|
+
input,
|
|
174
|
+
snapshot: restoredSnapshot,
|
|
175
|
+
inspect: options?.inspect
|
|
176
|
+
});
|
|
177
|
+
this.playerMachine = machine;
|
|
178
|
+
this.playerInput = input;
|
|
179
|
+
this.playerRestoredSnapshot = restoredSnapshot;
|
|
180
|
+
this.playerOptions = options || {};
|
|
181
|
+
this.state = createWritableAtom(this.getSnapshot());
|
|
182
|
+
super.subscribe({
|
|
183
|
+
next: (snapshot) => {
|
|
184
|
+
if (isObservableSnapshot(snapshot)) {
|
|
185
|
+
this.state.set(snapshot);
|
|
186
|
+
this.onSnapshot(snapshot);
|
|
187
|
+
const onStateChange = this.hooks.onStateChange;
|
|
188
|
+
if (onStateChange) onStateChange(this, snapshot);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
error: (error) => {
|
|
192
|
+
const handler = this.hooks.onError;
|
|
193
|
+
if (handler) {
|
|
194
|
+
handler(this, toError(error));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if ((this.nextOnlySubscriptions ?? 0) > 0) return;
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
this.constructed = true;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Starts the actor.
|
|
205
|
+
*
|
|
206
|
+
* The method fires `onStart` on each real start, which is every transition from
|
|
207
|
+
* "not running" to "running". A start after a stop is such a transition, and XState
|
|
208
|
+
* permits it: its own `start()` stops only while the actor RUNS already. A second
|
|
209
|
+
* call while the actor runs fires no hook. Therefore a defensive double mount runs
|
|
210
|
+
* the side effects of `onStart` one time for one real start.
|
|
211
|
+
*/
|
|
212
|
+
start() {
|
|
213
|
+
if (!this.constructed) return this;
|
|
214
|
+
super.start();
|
|
215
|
+
if (this.lifecycle !== "running") {
|
|
216
|
+
this.lifecycle = "running";
|
|
217
|
+
const onStart = this.hooks.onStart;
|
|
218
|
+
if (onStart) onStart(this);
|
|
219
|
+
}
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Stops the actor and cleans up.
|
|
224
|
+
*
|
|
225
|
+
* The method fires `onStop` only when the actor ran. This matches XState, where a
|
|
226
|
+
* stop of an actor that never started, or of an actor that stopped already, does
|
|
227
|
+
* nothing and tears nothing down. Therefore the paired cleanup runs never two
|
|
228
|
+
* times, and it runs never against a resource that `onStart` did not take. A stop
|
|
229
|
+
* does not close the actor for ever: a later `start()` is a new lifecycle, and it
|
|
230
|
+
* fires `onStart` again.
|
|
231
|
+
*/
|
|
232
|
+
stop() {
|
|
233
|
+
if (!this.constructed) return this;
|
|
234
|
+
super.stop();
|
|
235
|
+
const wasRunning = this.lifecycle === "running";
|
|
236
|
+
this.lifecycle = "stopped";
|
|
237
|
+
const onStop = this.hooks.onStop;
|
|
238
|
+
if (wasRunning && onStop) onStop(this);
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Sends an event to this actor.
|
|
243
|
+
*
|
|
244
|
+
* The guards of the state machine of the actor decide if the event causes a
|
|
245
|
+
* transition. Give any event of the event union of the machine: a domain event, a
|
|
246
|
+
* routing event, and so on.
|
|
247
|
+
*
|
|
248
|
+
* @param event - An event of the `EventFromLogic<TMachine>` union of the machine.
|
|
249
|
+
*
|
|
250
|
+
* @throws {InvalidEventError} When `event` is not a plain object, for example
|
|
251
|
+
* `null`, `undefined`, a string, or a number. Import the class from
|
|
252
|
+
* `@xmachines/play-xstate/errors`.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* // A domain event, with the type of the event union of the machine
|
|
257
|
+
* actor.send({ type: "auth.login", userId: "123" });
|
|
258
|
+
*
|
|
259
|
+
* // A routing event
|
|
260
|
+
* actor.send({ type: "play.route", to: "#home" });
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
send(event) {
|
|
264
|
+
if (!event || typeof event !== "object") throw new InvalidEventError(event);
|
|
265
|
+
if (!this.constructed) {
|
|
266
|
+
Actor.prototype.send.call(this, event);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const prevSnapshot = this.getSnapshot();
|
|
270
|
+
Actor.prototype.send.call(this, event);
|
|
271
|
+
const onTransition = this.hooks.onTransition;
|
|
272
|
+
if (onTransition) {
|
|
273
|
+
const nextSnapshot = this.getSnapshot();
|
|
274
|
+
onTransition(this, prevSnapshot, nextSnapshot);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
subscribe(nextListenerOrObserver, errorListener, completeListener) {
|
|
278
|
+
const subscription = super.subscribe(nextListenerOrObserver, errorListener, completeListener);
|
|
279
|
+
if (typeof nextListenerOrObserver === "object" && nextListenerOrObserver !== null ? typeof nextListenerOrObserver.error === "function" : typeof errorListener === "function") return subscription;
|
|
280
|
+
this.nextOnlySubscriptions = (this.nextOnlySubscriptions ?? 0) + 1;
|
|
281
|
+
let counted = true;
|
|
282
|
+
return { unsubscribe: () => {
|
|
283
|
+
if (counted) {
|
|
284
|
+
counted = false;
|
|
285
|
+
this.nextOnlySubscriptions = (this.nextOnlySubscriptions ?? 1) - 1;
|
|
286
|
+
}
|
|
287
|
+
subscription.unsubscribe();
|
|
288
|
+
} };
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Returns the persisted snapshot of this actor.
|
|
292
|
+
*
|
|
293
|
+
* Use it to serialize the state, and to restore it later with the
|
|
294
|
+
* `restore.snapshot` option of the factory.
|
|
295
|
+
*/
|
|
296
|
+
getPersistedSnapshot(options) {
|
|
297
|
+
return super.getPersistedSnapshot.call(this, options);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The point where a capability derives its own atoms from a new snapshot.
|
|
301
|
+
*
|
|
302
|
+
* The base derives nothing: `PlayActor` asks for `state` and `send`, and this class
|
|
303
|
+
* gives exactly those.
|
|
304
|
+
*
|
|
305
|
+
* A capability that PUBLISHES a value overrides this method NOT. It derives the value
|
|
306
|
+
* with a computed atom over `state`, as `withRouting` and `withView` both do, and the
|
|
307
|
+
* engine then evaluates every derivation from the one write of a transition, in
|
|
308
|
+
* topological order. The composition order therefore decides no value.
|
|
309
|
+
*
|
|
310
|
+
* This method remains for a capability that must run an EFFECT on a transition. Such a
|
|
311
|
+
* mixin overrides it and calls `super.onSnapshot(snapshot)` FIRST, so that every
|
|
312
|
+
* capability composed before it runs its own effect first.
|
|
313
|
+
*
|
|
314
|
+
* The method runs after `state` holds the new snapshot and before the `onStateChange`
|
|
315
|
+
* hook of the options.
|
|
316
|
+
*
|
|
317
|
+
* @param snapshot - The stable snapshot of this transition.
|
|
318
|
+
*/
|
|
319
|
+
onSnapshot(snapshot) {}
|
|
320
|
+
};
|
|
321
|
+
//#endregion
|
|
322
|
+
export { PlayerActor, toError };
|
|
323
|
+
|
|
454
324
|
//# sourceMappingURL=player-actor.js.map
|