@xstate/effect 1.0.0-alpha.9 → 1.0.0-beta.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 +20 -9
- package/dist/declarations/src/actor.d.ts +33 -7
- package/dist/declarations/src/actorScope.d.ts +7 -1
- package/dist/declarations/src/atom.d.ts +11 -9
- package/dist/declarations/src/createEffectActor.d.ts +16 -5
- package/dist/declarations/src/effectActor.d.ts +30 -13
- package/dist/declarations/src/effectLogic.d.ts +30 -10
- package/dist/declarations/src/errors.d.ts +37 -4
- package/dist/declarations/src/index.d.ts +4 -2
- package/dist/declarations/src/oxlint.d.ts +3 -0
- package/dist/declarations/src/persistence.d.ts +45 -0
- package/dist/declarations/src/schema.d.ts +10 -0
- package/dist/declarations/src/sendAndAwait.d.ts +14 -0
- package/dist/declarations/src/setupEffect.d.ts +56 -13
- package/dist/declarations/src/state.d.ts +11 -1
- package/dist/declarations/src/test.d.ts +1 -0
- package/dist/declarations/src/types.d.ts +3 -2
- package/dist/sendAndAwait-bf1d4117.js +14 -0
- package/dist/{state-a2e5cb31.js → state-83513b45.js} +325 -105
- package/dist/xstate-effect-atom.js +14 -8
- package/dist/xstate-effect-oxlint.d.ts +3 -0
- package/dist/xstate-effect-oxlint.js +126 -0
- package/dist/xstate-effect-test.d.ts +2 -0
- package/dist/xstate-effect-test.js +1 -0
- package/dist/xstate-effect.js +293 -35
- package/package.json +17 -5
- package/dist/declarations/src/brands.d.ts +0 -1
|
@@ -1,13 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { stopActor, terminateActor, deliverEvent, isMachineSnapshot } from 'xstate';
|
|
1
|
+
import { Cause, Data, Queue, Effect, Exit, Fiber, Scope, Context, Clock, Duration } from 'effect';
|
|
2
|
+
import { StateMachine, stopActor, terminateActor, deliverEvent, isMachineSnapshot } from 'xstate';
|
|
3
3
|
import { createDurable } from 'xstate/durable';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
function _checkPrivateRedeclaration(e, t) {
|
|
6
|
+
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function _classPrivateFieldInitSpec(e, t, a) {
|
|
10
|
+
_checkPrivateRedeclaration(e, t), t.set(e, a);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function _checkInRHS(e) {
|
|
14
|
+
if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null"));
|
|
15
|
+
return e;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function _assertClassBrand(e, t, n) {
|
|
19
|
+
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
|
|
20
|
+
throw new TypeError("Private element is not present on this object");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function _classPrivateFieldGet2(s, a) {
|
|
24
|
+
return s.get(_assertClassBrand(s, a));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var _effectFailure = /*#__PURE__*/new WeakMap();
|
|
28
|
+
/**
|
|
29
|
+
* Preserves an Effect failure, defect or interruption through XState error routing.
|
|
30
|
+
*
|
|
31
|
+
* @experimental
|
|
32
|
+
*/
|
|
33
|
+
class EffectFailure extends Error {
|
|
34
|
+
static is(value) {
|
|
35
|
+
return typeof value === 'object' && value !== null && _effectFailure.has(_checkInRHS(value)) && _classPrivateFieldGet2(_effectFailure, value);
|
|
36
|
+
}
|
|
37
|
+
constructor(cause) {
|
|
38
|
+
super(Cause.pretty(cause));
|
|
39
|
+
this.cause = cause;
|
|
40
|
+
this._tag = 'EffectFailure';
|
|
41
|
+
_classPrivateFieldInitSpec(this, _effectFailure, true);
|
|
42
|
+
this.name = 'EffectFailure';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @deprecated Inspect `EffectFailure.cause` for interruption instead.
|
|
48
|
+
*
|
|
49
|
+
* @experimental
|
|
50
|
+
*/
|
|
51
|
+
class EffectInterruptedError extends Data.TaggedError('EffectInterruptedError') {
|
|
52
|
+
get message() {
|
|
53
|
+
return 'Effect was interrupted before the actor completed';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Reported by `waitFor` when the actor stops or errors before a snapshot
|
|
59
|
+
* matches, and by `join` when the actor stops without output. `join` reports
|
|
60
|
+
* an errored actor's outcome instead, preserving an Effect failure's Cause.
|
|
61
|
+
*
|
|
62
|
+
* @experimental
|
|
63
|
+
*/
|
|
64
|
+
class ActorStoppedError extends Data.TaggedError('ActorStoppedError') {
|
|
65
|
+
get message() {
|
|
66
|
+
return `Actor "${this.actorId}" ${this.snapshot.status === 'error' ? 'errored' : 'stopped'} before completing`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Reported by the `send` atom of `createActorAtoms` when an event is sent
|
|
72
|
+
* before the actor's runtime has finished building.
|
|
73
|
+
*
|
|
74
|
+
* @experimental
|
|
75
|
+
*/
|
|
76
|
+
class NotReadyError extends Data.TaggedError('NotReadyError') {
|
|
77
|
+
get message() {
|
|
78
|
+
return 'The actor is not ready yet';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
6
81
|
|
|
7
|
-
|
|
8
|
-
function
|
|
9
|
-
return
|
|
82
|
+
/** @internal */
|
|
83
|
+
function causeFromError(error) {
|
|
84
|
+
return error instanceof EffectFailure ? error.cause : Cause.fail(error);
|
|
10
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* An event rejected at the actor's delivery boundary.
|
|
88
|
+
*
|
|
89
|
+
* @experimental
|
|
90
|
+
*/
|
|
91
|
+
class EventRejectedError extends Data.TaggedError('EventRejectedError') {}
|
|
92
|
+
|
|
93
|
+
/** @internal */
|
|
94
|
+
|
|
11
95
|
const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
|
|
12
96
|
|
|
13
97
|
/**
|
|
@@ -39,6 +123,8 @@ function toObserver(nextHandler, errorHandler, completionHandler) {
|
|
|
39
123
|
* contract (`send`, `getSnapshot`, `subscribe`, `on`), so it works with
|
|
40
124
|
* `useSelector`, this package's actor functions and the inspection APIs, while
|
|
41
125
|
* the actor itself is driven by an Effect fiber over pure transitions.
|
|
126
|
+
*
|
|
127
|
+
* @experimental
|
|
42
128
|
*/
|
|
43
129
|
class EffectActor {
|
|
44
130
|
constructor(logic, root, snapshot, _mailbox, _stopExecution, _inspectors) {
|
|
@@ -57,6 +143,10 @@ class EffectActor {
|
|
|
57
143
|
this._observers = new Set();
|
|
58
144
|
this._listeners = new Map();
|
|
59
145
|
this._settled = false;
|
|
146
|
+
this._pending = new Set();
|
|
147
|
+
this._inStep = false;
|
|
148
|
+
this._deliveryDepth = 0;
|
|
149
|
+
this._stopHeld = false;
|
|
60
150
|
this._root = root;
|
|
61
151
|
this.id = root.id;
|
|
62
152
|
this.address = root.address;
|
|
@@ -73,15 +163,76 @@ class EffectActor {
|
|
|
73
163
|
|
|
74
164
|
/** Sends an event; the actor processes it on its own fiber. */
|
|
75
165
|
send(event) {
|
|
166
|
+
if (this._deliveryError(event)) return;
|
|
167
|
+
Queue.offerUnsafe(this._mailbox, {
|
|
168
|
+
kind: 'event',
|
|
169
|
+
event
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** @internal */
|
|
174
|
+
_sendAndAwait(event) {
|
|
175
|
+
return Effect.callback(resume => {
|
|
176
|
+
const error = this._deliveryError(event);
|
|
177
|
+
if (error) {
|
|
178
|
+
resume(Effect.fail(error));
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const receipt = {
|
|
182
|
+
resume: exit => resume(exit)
|
|
183
|
+
};
|
|
184
|
+
this._pending.add(receipt);
|
|
185
|
+
Queue.offerUnsafe(this._mailbox, {
|
|
186
|
+
kind: 'event',
|
|
187
|
+
event,
|
|
188
|
+
receipt
|
|
189
|
+
});
|
|
190
|
+
return Effect.sync(() => {
|
|
191
|
+
this._pending.delete(receipt);
|
|
192
|
+
receipt.resume = undefined;
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
_deliveryError(event) {
|
|
76
197
|
if (this._settled) {
|
|
77
198
|
this.system.deadLetter(undefined, this._root, event, 'stopped');
|
|
78
|
-
return;
|
|
199
|
+
return this._stoppedError();
|
|
200
|
+
}
|
|
201
|
+
if (this.logic instanceof StateMachine && this.logic.isInternalEventType(event.type)) {
|
|
202
|
+
this.system.deadLetter(undefined, this._root, event, 'internalEvent');
|
|
203
|
+
return new EventRejectedError({
|
|
204
|
+
actorId: this.id,
|
|
205
|
+
event,
|
|
206
|
+
reason: 'internalEvent'
|
|
207
|
+
});
|
|
79
208
|
}
|
|
80
|
-
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
_stoppedError() {
|
|
212
|
+
return new ActorStoppedError({
|
|
213
|
+
actorId: this.id,
|
|
214
|
+
snapshot: this._snapshot
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** @internal */
|
|
219
|
+
_completeReceipt(receipt, exit) {
|
|
220
|
+
if (!receipt || !this._pending.delete(receipt)) return;
|
|
221
|
+
const resume = receipt.resume;
|
|
222
|
+
receipt.resume = undefined;
|
|
223
|
+
resume?.(exit);
|
|
81
224
|
}
|
|
82
225
|
|
|
83
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* Stops the actor, its children and every Effect it hosts. If a listener or
|
|
228
|
+
* an observer calls `stop()` while the actor delivers a macrostep to it, the
|
|
229
|
+
* actor stops when that macrostep ends. `createActor` does the same.
|
|
230
|
+
*/
|
|
84
231
|
stop() {
|
|
232
|
+
if (this._inStep && this._deliveryDepth > 0) {
|
|
233
|
+
this._stopHeld = true;
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
85
236
|
this._stopExecution();
|
|
86
237
|
return this;
|
|
87
238
|
}
|
|
@@ -122,9 +273,20 @@ class EffectActor {
|
|
|
122
273
|
|
|
123
274
|
/** @internal Delivers an emitted event to `on` listeners. */
|
|
124
275
|
_emit(event) {
|
|
276
|
+
if (this._settled) return;
|
|
125
277
|
const listeners = [...(this._listeners.get(event.type) ?? []), ...(this._listeners.get('*') ?? [])];
|
|
126
|
-
|
|
127
|
-
|
|
278
|
+
this._deliver(() => {
|
|
279
|
+
for (const listener of listeners) {
|
|
280
|
+
safeCall(listener, event);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
_deliver(notify) {
|
|
285
|
+
this._deliveryDepth++;
|
|
286
|
+
try {
|
|
287
|
+
notify();
|
|
288
|
+
} finally {
|
|
289
|
+
this._deliveryDepth--;
|
|
128
290
|
}
|
|
129
291
|
}
|
|
130
292
|
|
|
@@ -146,25 +308,41 @@ class EffectActor {
|
|
|
146
308
|
}
|
|
147
309
|
toJSON() {
|
|
148
310
|
return {
|
|
149
|
-
|
|
311
|
+
'@actor': this.address
|
|
150
312
|
};
|
|
151
313
|
}
|
|
152
314
|
|
|
153
|
-
/** @internal
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
315
|
+
/** @internal */
|
|
316
|
+
_runStep(step) {
|
|
317
|
+
return Effect.gen({
|
|
318
|
+
self: this
|
|
319
|
+
}, function* () {
|
|
320
|
+
if (this._settled) return;
|
|
321
|
+
this._snapshot = step.snapshot;
|
|
322
|
+
this._inStep = true;
|
|
323
|
+
yield* step.effects;
|
|
324
|
+
if (this._settled) return;
|
|
325
|
+
if (this._snapshot.status !== 'active') {
|
|
326
|
+
this._settle(this._snapshot, step.kind === 'transition' ? step.receipt : undefined);
|
|
327
|
+
} else if (step.kind === 'transition') {
|
|
328
|
+
this._deliver(() => {
|
|
329
|
+
for (const observer of this._observers) {
|
|
330
|
+
safeCall(observer.next, this._snapshot);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
160
333
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
334
|
+
}).pipe(Effect.onExit(exit => Effect.sync(() => {
|
|
335
|
+
this._inStep = false;
|
|
336
|
+
if (this._stopHeld) {
|
|
337
|
+
this._stopExecution();
|
|
338
|
+
} else if (step.kind === 'transition' && Exit.isSuccess(exit)) {
|
|
339
|
+
this._completeReceipt(step.receipt, step.rejection ? Exit.fail(step.rejection) : Exit.succeed(step.snapshot));
|
|
340
|
+
}
|
|
341
|
+
})));
|
|
164
342
|
}
|
|
165
343
|
|
|
166
|
-
/** @internal
|
|
167
|
-
_settle(snapshot) {
|
|
344
|
+
/** @internal Settles the actor and notifies its observers. */
|
|
345
|
+
_settle(snapshot, processing) {
|
|
168
346
|
if (this._settled) {
|
|
169
347
|
return;
|
|
170
348
|
}
|
|
@@ -173,17 +351,23 @@ class EffectActor {
|
|
|
173
351
|
const observers = [...this._observers];
|
|
174
352
|
this._observers.clear();
|
|
175
353
|
const status = snapshot.status;
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
354
|
+
this._deliver(() => {
|
|
355
|
+
if (status === 'done') {
|
|
356
|
+
for (const observer of observers) {
|
|
357
|
+
safeCall(observer.next, snapshot);
|
|
358
|
+
}
|
|
179
359
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
360
|
+
for (const observer of observers) {
|
|
361
|
+
if (status === 'error') {
|
|
362
|
+
safeCall(observer.error, snapshot.error);
|
|
363
|
+
} else {
|
|
364
|
+
safeCall(observer.complete);
|
|
365
|
+
}
|
|
186
366
|
}
|
|
367
|
+
});
|
|
368
|
+
this._completeReceipt(processing, Exit.succeed(snapshot));
|
|
369
|
+
for (const receipt of this._pending) {
|
|
370
|
+
this._completeReceipt(receipt, Exit.fail(this._stoppedError()));
|
|
187
371
|
}
|
|
188
372
|
}
|
|
189
373
|
|
|
@@ -314,7 +498,7 @@ function trackEffect(actor, host, interrupt) {
|
|
|
314
498
|
* The fiber belongs to the host scope, so closing the owning actor also
|
|
315
499
|
* waits for asynchronous invocation finalizers.
|
|
316
500
|
*/
|
|
317
|
-
function startHostedEffect(actor, effect, spanName, onExit) {
|
|
501
|
+
function startHostedEffect(actor, effect, spanName, onExit, onCancel) {
|
|
318
502
|
const host = requireEffectHost(actor);
|
|
319
503
|
let active = true;
|
|
320
504
|
const traced = Effect.withSpan(spanName, {
|
|
@@ -331,11 +515,18 @@ function startHostedEffect(actor, effect, spanName, onExit) {
|
|
|
331
515
|
}
|
|
332
516
|
active = false;
|
|
333
517
|
untrackEffect(actor, host, cancel);
|
|
518
|
+
onCancel?.();
|
|
334
519
|
interrupt();
|
|
335
520
|
};
|
|
336
|
-
|
|
521
|
+
// Register the fiber before its body runs: the body can synchronously
|
|
522
|
+
// trigger the host's close, which only awaits registered fibers.
|
|
523
|
+
const registered = Effect.withFiber(fiber => {
|
|
524
|
+
Fiber.runIn(fiber, host.scope);
|
|
525
|
+
host.fibers.add(fiber);
|
|
526
|
+
return traced;
|
|
527
|
+
});
|
|
528
|
+
const fiber = Effect.runForkWith(host.context)(registered);
|
|
337
529
|
const interrupt = () => fiber.interruptUnsafe();
|
|
338
|
-
host.fibers.add(fiber);
|
|
339
530
|
fiber.addObserver(exit => {
|
|
340
531
|
host.fibers.delete(fiber);
|
|
341
532
|
if (!active) {
|
|
@@ -351,22 +542,16 @@ function startHostedEffect(actor, effect, spanName, onExit) {
|
|
|
351
542
|
return cancel;
|
|
352
543
|
}
|
|
353
544
|
|
|
354
|
-
/**
|
|
355
|
-
* Runs an Effect in the actor's host context and settles when it exits.
|
|
356
|
-
* Interruption settles without error; failures and defects reject with the
|
|
357
|
-
* squashed cause so the actor's error handling can observe them.
|
|
358
|
-
*/
|
|
545
|
+
/** @internal */
|
|
359
546
|
function runHostedEffect(actor, effect, spanName) {
|
|
360
547
|
return new Promise((resolve, reject) => {
|
|
361
548
|
startHostedEffect(actor, effect, spanName, exit => {
|
|
362
|
-
if (Exit.isSuccess(exit)
|
|
549
|
+
if (Exit.isSuccess(exit)) {
|
|
363
550
|
resolve();
|
|
364
551
|
} else {
|
|
365
|
-
|
|
366
|
-
// oxlint-disable-next-line typescript/prefer-promise-reject-errors
|
|
367
|
-
reject(Cause.squash(exit.cause));
|
|
552
|
+
reject(new EffectFailure(exit.cause));
|
|
368
553
|
}
|
|
369
|
-
});
|
|
554
|
+
}, resolve);
|
|
370
555
|
});
|
|
371
556
|
}
|
|
372
557
|
|
|
@@ -403,12 +588,18 @@ function relayToParent(actor, event) {
|
|
|
403
588
|
}
|
|
404
589
|
}
|
|
405
590
|
|
|
406
|
-
/**
|
|
591
|
+
/**
|
|
592
|
+
* The owning Effect actor's scope, supplied by `createEffectActor`.
|
|
593
|
+
*
|
|
594
|
+
* @experimental
|
|
595
|
+
*/
|
|
407
596
|
class ActorScope extends Context.Service()('@xstate/effect/ActorScope') {}
|
|
408
597
|
|
|
409
598
|
/**
|
|
410
599
|
* Acquires resources in the owning actor's scope. Their finalizers run when
|
|
411
600
|
* that actor stops, even if the invoking task finishes or is cancelled first.
|
|
601
|
+
*
|
|
602
|
+
* @experimental
|
|
412
603
|
*/
|
|
413
604
|
function withActorScope(effect) {
|
|
414
605
|
return ActorScope.use(scope => Scope.provide(effect, scope));
|
|
@@ -417,7 +608,11 @@ function withActorScope(effect) {
|
|
|
417
608
|
const XSTATE_TIMER = 'xstate.timer';
|
|
418
609
|
const XSTATE_INIT = 'xstate.init';
|
|
419
610
|
|
|
420
|
-
/**
|
|
611
|
+
/**
|
|
612
|
+
* Options for {@link createEffectActor}.
|
|
613
|
+
*
|
|
614
|
+
* @experimental
|
|
615
|
+
*/
|
|
421
616
|
|
|
422
617
|
/**
|
|
423
618
|
* Creates and starts an actor as an Effect interpreter over pure transitions.
|
|
@@ -432,6 +627,8 @@ const XSTATE_INIT = 'xstate.init';
|
|
|
432
627
|
* The actor is a scoped resource: it stops, and every Effect it hosts is
|
|
433
628
|
* interrupted, when the enclosing `Scope` closes. The returned Effect never
|
|
434
629
|
* fails; the actor's own outcome is its snapshot status, read with `join`.
|
|
630
|
+
*
|
|
631
|
+
* @experimental
|
|
435
632
|
*/
|
|
436
633
|
function createEffectActor(logic, ...[options]) {
|
|
437
634
|
return Effect.acquireRelease(Effect.gen(function* () {
|
|
@@ -443,6 +640,8 @@ function createEffectActor(logic, ...[options]) {
|
|
|
443
640
|
const runFork = Effect.runForkWith(context);
|
|
444
641
|
const runPromise = Effect.runPromiseWith(context);
|
|
445
642
|
const mailbox = yield* Queue.unbounded();
|
|
643
|
+
let delivered;
|
|
644
|
+
const getDelivered = () => delivered;
|
|
446
645
|
const timers = new Map();
|
|
447
646
|
// `root` and `actor` are declared after the adapter below; its
|
|
448
647
|
// callbacks only run once they are initialized.
|
|
@@ -469,14 +668,14 @@ function createEffectActor(logic, ...[options]) {
|
|
|
469
668
|
if (result && typeof result.then === 'function') {
|
|
470
669
|
void Promise.resolve(result).catch(error => {
|
|
471
670
|
offer({
|
|
472
|
-
|
|
671
|
+
kind: 'actionFailure',
|
|
473
672
|
error
|
|
474
673
|
});
|
|
475
674
|
});
|
|
476
675
|
}
|
|
477
676
|
} catch (error) {
|
|
478
677
|
offer({
|
|
479
|
-
|
|
678
|
+
kind: 'actionFailure',
|
|
480
679
|
error
|
|
481
680
|
});
|
|
482
681
|
}
|
|
@@ -500,7 +699,10 @@ function createEffectActor(logic, ...[options]) {
|
|
|
500
699
|
},
|
|
501
700
|
sendEvent: (source, target, event) => {
|
|
502
701
|
if (isRoot(target)) {
|
|
503
|
-
offer(
|
|
702
|
+
offer({
|
|
703
|
+
kind: 'event',
|
|
704
|
+
event: event
|
|
705
|
+
});
|
|
504
706
|
return;
|
|
505
707
|
}
|
|
506
708
|
deliverEvent(source, target, event);
|
|
@@ -522,7 +724,10 @@ function createEffectActor(logic, ...[options]) {
|
|
|
522
724
|
...timer
|
|
523
725
|
};
|
|
524
726
|
if (isRoot(source)) {
|
|
525
|
-
offer(
|
|
727
|
+
offer({
|
|
728
|
+
kind: 'event',
|
|
729
|
+
event: timerEvent
|
|
730
|
+
});
|
|
526
731
|
} else {
|
|
527
732
|
deliverEvent(source, source, timerEvent);
|
|
528
733
|
}
|
|
@@ -542,8 +747,15 @@ function createEffectActor(logic, ...[options]) {
|
|
|
542
747
|
}
|
|
543
748
|
}
|
|
544
749
|
},
|
|
545
|
-
waitForEvent: () => runPromise(Queue.take(mailbox))
|
|
750
|
+
waitForEvent: () => runPromise(Queue.take(mailbox)).then(item => {
|
|
751
|
+
delivered = item;
|
|
752
|
+
return item.kind === 'event' ? item.event : {
|
|
753
|
+
type: 'xstate.effect.actionFailure'
|
|
754
|
+
};
|
|
755
|
+
})
|
|
546
756
|
}, {
|
|
757
|
+
// A live actor needs one FIFO for child events, sends and timers.
|
|
758
|
+
captureRootEvents: false,
|
|
547
759
|
inspect: event => {
|
|
548
760
|
// The pure step scope re-materializes the root ref per step and
|
|
549
761
|
// announces it again; observers should see the root once.
|
|
@@ -586,10 +798,11 @@ function createEffectActor(logic, ...[options]) {
|
|
|
586
798
|
}
|
|
587
799
|
runFork(Queue.shutdown(mailbox));
|
|
588
800
|
if (!actor._isSettled) {
|
|
589
|
-
actor.
|
|
590
|
-
|
|
801
|
+
const current = actor.getSnapshot();
|
|
802
|
+
actor._settle(current.status === 'active' ? {
|
|
803
|
+
...current,
|
|
591
804
|
status: 'stopped'
|
|
592
|
-
});
|
|
805
|
+
} : current);
|
|
593
806
|
}
|
|
594
807
|
closeEffectHost(host);
|
|
595
808
|
};
|
|
@@ -624,39 +837,67 @@ function createEffectActor(logic, ...[options]) {
|
|
|
624
837
|
bindEffectHost(actor, host);
|
|
625
838
|
const executeEffects = batch => Effect.promise(() => durable.executeEffects(batch).then(() => undefined, error => {
|
|
626
839
|
offer({
|
|
627
|
-
|
|
840
|
+
kind: 'actionFailure',
|
|
628
841
|
error
|
|
629
842
|
});
|
|
630
843
|
}));
|
|
631
844
|
const loop = Effect.gen(function* () {
|
|
632
|
-
yield*
|
|
845
|
+
yield* actor._runStep({
|
|
846
|
+
kind: 'start',
|
|
847
|
+
snapshot,
|
|
848
|
+
effects: executeEffects(effects)
|
|
849
|
+
});
|
|
633
850
|
while (snapshot.status === 'active' && !stopped) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
851
|
+
delivered = undefined;
|
|
852
|
+
const received = yield* Effect.promise(() => durable.waitForEvent().catch(() => undefined));
|
|
853
|
+
if (received === undefined || stopped) break;
|
|
854
|
+
const item = getDelivered() ?? {
|
|
855
|
+
kind: 'event',
|
|
856
|
+
event: received
|
|
857
|
+
};
|
|
858
|
+
const receipt = item.kind === 'event' ? item.receipt : undefined;
|
|
638
859
|
let event;
|
|
639
|
-
if (
|
|
860
|
+
if (item.kind === 'actionFailure') {
|
|
640
861
|
const errorEvent = logic.getExecutionErrorEvent?.(snapshot, item.error);
|
|
641
862
|
if (!errorEvent) {
|
|
642
863
|
snapshot = errorSnapshot(snapshot, item.error);
|
|
864
|
+
yield* actor._runStep({
|
|
865
|
+
kind: 'transition',
|
|
866
|
+
snapshot,
|
|
867
|
+
effects: Effect.void
|
|
868
|
+
});
|
|
643
869
|
break;
|
|
644
870
|
}
|
|
645
871
|
event = errorEvent;
|
|
646
872
|
} else {
|
|
647
|
-
event = item;
|
|
873
|
+
event = item.event;
|
|
648
874
|
}
|
|
649
875
|
try {
|
|
650
876
|
[snapshot, effects] = durable.transition(snapshot, event);
|
|
651
877
|
} catch (error) {
|
|
652
878
|
snapshot = errorSnapshot(snapshot, error);
|
|
879
|
+
yield* actor._runStep({
|
|
880
|
+
kind: 'transition',
|
|
881
|
+
snapshot,
|
|
882
|
+
effects: Effect.void,
|
|
883
|
+
receipt
|
|
884
|
+
});
|
|
653
885
|
break;
|
|
654
886
|
}
|
|
655
|
-
|
|
656
|
-
yield*
|
|
887
|
+
const rejectedReason = effects.length === 1 && effects[0].effect.type === '@xstate.deadLetter' && 'reason' in effects[0].effect && effects[0].effect.reason === 'invalidEvent' ? effects[0].effect.reason : undefined;
|
|
888
|
+
yield* actor._runStep({
|
|
889
|
+
kind: 'transition',
|
|
890
|
+
snapshot,
|
|
891
|
+
effects: executeEffects(effects),
|
|
892
|
+
receipt,
|
|
893
|
+
rejection: rejectedReason !== undefined ? new EventRejectedError({
|
|
894
|
+
actorId: actor.id,
|
|
895
|
+
event,
|
|
896
|
+
reason: rejectedReason
|
|
897
|
+
}) : undefined
|
|
898
|
+
});
|
|
657
899
|
}
|
|
658
900
|
if (!stopped) {
|
|
659
|
-
actor._publish(snapshot);
|
|
660
901
|
if (snapshot.status !== 'active') {
|
|
661
902
|
stopChildren(snapshot);
|
|
662
903
|
closeEffectHost(host);
|
|
@@ -666,13 +907,14 @@ function createEffectActor(logic, ...[options]) {
|
|
|
666
907
|
yield* Effect.forkIn(loop, actorScope);
|
|
667
908
|
return {
|
|
668
909
|
actor,
|
|
669
|
-
host
|
|
910
|
+
host,
|
|
911
|
+
stopNow: stop
|
|
670
912
|
};
|
|
671
913
|
}), ({
|
|
672
|
-
|
|
673
|
-
|
|
914
|
+
host,
|
|
915
|
+
stopNow
|
|
674
916
|
}) => Effect.gen(function* () {
|
|
675
|
-
|
|
917
|
+
stopNow();
|
|
676
918
|
if (host.closing) {
|
|
677
919
|
yield* Fiber.join(host.closing);
|
|
678
920
|
} else {
|
|
@@ -683,52 +925,28 @@ function createEffectActor(logic, ...[options]) {
|
|
|
683
925
|
}) => actor));
|
|
684
926
|
}
|
|
685
927
|
|
|
686
|
-
/**
|
|
687
|
-
* The failure an Effect-backed actor reports when its Effect was interrupted
|
|
688
|
-
* by something other than the actor being stopped, such as `Effect.interrupt`,
|
|
689
|
-
* losing an `Effect.race`, or an `Effect.timeout` that interrupts.
|
|
690
|
-
*/
|
|
691
|
-
class EffectInterruptedError extends Data.TaggedError('EffectInterruptedError') {
|
|
692
|
-
get message() {
|
|
693
|
-
return 'Effect was interrupted before the actor completed';
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/**
|
|
698
|
-
* Reported by `waitFor` when the actor stops or errors before a snapshot
|
|
699
|
-
* matches, and by `join` when the actor stops without output. `join` reports
|
|
700
|
-
* an errored actor's own `snapshot.error` instead.
|
|
701
|
-
*/
|
|
702
|
-
class ActorStoppedError extends Data.TaggedError('ActorStoppedError') {
|
|
703
|
-
get message() {
|
|
704
|
-
return `Actor "${this.actorId}" ${this.snapshot.status === 'error' ? 'errored' : 'stopped'} before completing`;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Reported by the `send` atom of `createActorAtoms` when an event is sent
|
|
710
|
-
* before the actor's runtime has finished building.
|
|
711
|
-
*/
|
|
712
|
-
class NotReadyError extends Data.TaggedError('NotReadyError') {
|
|
713
|
-
get message() {
|
|
714
|
-
return 'The actor is not ready yet';
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
|
|
718
928
|
/**
|
|
719
929
|
* The dotted path of a state value: `'idle'`, `'form.editing'`. A parallel
|
|
720
930
|
* state has no single path, so its tag stops at the parallel state. When the
|
|
721
931
|
* machine itself is parallel, the tag is `TRootTag`: the machine id, or `''`
|
|
722
932
|
* for a machine without one.
|
|
933
|
+
*
|
|
934
|
+
* @experimental
|
|
723
935
|
*/
|
|
724
936
|
|
|
725
937
|
/**
|
|
726
938
|
* A machine snapshot as a tagged union over its states. `_tag` is the state's
|
|
727
939
|
* dotted path and `context` is the context of that state, including any
|
|
728
940
|
* per-state context schema, so `Match.tag` narrows both.
|
|
941
|
+
*
|
|
942
|
+
* @experimental
|
|
729
943
|
*/
|
|
730
944
|
|
|
731
|
-
/**
|
|
945
|
+
/**
|
|
946
|
+
* The tagged state union of a machine.
|
|
947
|
+
*
|
|
948
|
+
* @experimental
|
|
949
|
+
*/
|
|
732
950
|
|
|
733
951
|
/** Computes the dotted path tag of a state value. */
|
|
734
952
|
function stateTag(value, rootTag, prefix = '') {
|
|
@@ -757,6 +975,8 @@ function stateTag(value, rootTag, prefix = '') {
|
|
|
757
975
|
* );
|
|
758
976
|
* view(taggedState(snapshot));
|
|
759
977
|
* ```
|
|
978
|
+
*
|
|
979
|
+
* @experimental
|
|
760
980
|
*/
|
|
761
981
|
function taggedState(snapshot) {
|
|
762
982
|
return {
|
|
@@ -767,4 +987,4 @@ function taggedState(snapshot) {
|
|
|
767
987
|
};
|
|
768
988
|
}
|
|
769
989
|
|
|
770
|
-
export { ActorStoppedError as A,
|
|
990
|
+
export { ActorStoppedError as A, EffectFailure as E, NotReadyError as N, causeFromError as a, runHostedEffect as b, createEffectActor as c, EffectActor as d, ActorScope as e, EventRejectedError as f, EffectInterruptedError as g, relayToParent as r, startHostedEffect as s, taggedState as t, withActorScope as w };
|