@xstate/effect 0.1.0-alpha.7 → 1.0.0-alpha.11
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 +35 -18
- package/dist/declarations/src/actor.d.ts +4 -4
- package/dist/declarations/src/atom.d.ts +8 -9
- package/dist/declarations/src/effectActor.d.ts +19 -13
- package/dist/declarations/src/{fromEffect.d.ts → effectLogic.d.ts} +28 -30
- package/dist/declarations/src/errors.d.ts +23 -6
- package/dist/declarations/src/index.d.ts +4 -3
- package/dist/declarations/src/oxlint.d.ts +2 -0
- package/dist/declarations/src/persistence.d.ts +39 -0
- package/dist/declarations/src/schema.d.ts +2 -2
- package/dist/declarations/src/setupEffect.d.ts +48 -13
- package/dist/declarations/src/state.d.ts +8 -7
- package/dist/declarations/src/test.d.ts +11 -0
- package/dist/declarations/src/types.d.ts +2 -3
- package/dist/{state-b3d9b3e2.js → state-37b37ed4.js} +212 -88
- package/dist/xstate-effect-atom.js +10 -8
- package/dist/xstate-effect-oxlint.d.ts +3 -0
- package/dist/xstate-effect-oxlint.js +124 -0
- package/dist/xstate-effect-test.d.ts +2 -0
- package/dist/xstate-effect-test.js +11 -0
- package/dist/xstate-effect.js +231 -54
- package/package.json +17 -5
- package/dist/declarations/src/brands.d.ts +0 -1
|
@@ -1,13 +1,79 @@
|
|
|
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
|
+
/** Preserves an Effect failure, defect or interruption through XState error routing. */
|
|
29
|
+
class EffectFailure extends Error {
|
|
30
|
+
static is(value) {
|
|
31
|
+
return typeof value === 'object' && value !== null && _effectFailure.has(_checkInRHS(value)) && _classPrivateFieldGet2(_effectFailure, value);
|
|
32
|
+
}
|
|
33
|
+
constructor(cause) {
|
|
34
|
+
super(Cause.pretty(cause));
|
|
35
|
+
this.cause = cause;
|
|
36
|
+
this._tag = 'EffectFailure';
|
|
37
|
+
_classPrivateFieldInitSpec(this, _effectFailure, true);
|
|
38
|
+
this.name = 'EffectFailure';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
6
41
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
42
|
+
/** @deprecated Inspect `EffectFailure.cause` for interruption instead. */
|
|
43
|
+
class EffectInterruptedError extends Data.TaggedError('EffectInterruptedError') {
|
|
44
|
+
get message() {
|
|
45
|
+
return 'Effect was interrupted before the actor completed';
|
|
46
|
+
}
|
|
10
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Reported by `waitFor` when the actor stops or errors before a snapshot
|
|
51
|
+
* matches, and by `join` when the actor stops without output. `join` reports
|
|
52
|
+
* an errored actor's outcome instead, preserving an Effect failure's Cause.
|
|
53
|
+
*/
|
|
54
|
+
class ActorStoppedError extends Data.TaggedError('ActorStoppedError') {
|
|
55
|
+
get message() {
|
|
56
|
+
return `Actor "${this.actorId}" ${this.snapshot.status === 'error' ? 'errored' : 'stopped'} before completing`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Reported by the `send` atom of `createActorAtoms` when an event is sent
|
|
62
|
+
* before the actor's runtime has finished building.
|
|
63
|
+
*/
|
|
64
|
+
class NotReadyError extends Data.TaggedError('NotReadyError') {
|
|
65
|
+
get message() {
|
|
66
|
+
return 'The actor is not ready yet';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** @internal */
|
|
71
|
+
function causeFromError(error) {
|
|
72
|
+
return error instanceof EffectFailure ? error.cause : Cause.fail(error);
|
|
73
|
+
}
|
|
74
|
+
/** An event rejected at the actor's delivery boundary. */
|
|
75
|
+
class EventRejectedError extends Data.TaggedError('EventRejectedError') {}
|
|
76
|
+
|
|
11
77
|
const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
|
|
12
78
|
|
|
13
79
|
/**
|
|
@@ -57,6 +123,7 @@ class EffectActor {
|
|
|
57
123
|
this._observers = new Set();
|
|
58
124
|
this._listeners = new Map();
|
|
59
125
|
this._settled = false;
|
|
126
|
+
this._pending = new Set();
|
|
60
127
|
this._root = root;
|
|
61
128
|
this.id = root.id;
|
|
62
129
|
this.address = root.address;
|
|
@@ -73,11 +140,69 @@ class EffectActor {
|
|
|
73
140
|
|
|
74
141
|
/** Sends an event; the actor processes it on its own fiber. */
|
|
75
142
|
send(event) {
|
|
143
|
+
if (this._deliveryError(event)) return;
|
|
144
|
+
Queue.offerUnsafe(this._mailbox, {
|
|
145
|
+
kind: 'event',
|
|
146
|
+
event
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** @internal */
|
|
151
|
+
_sendAndAwait(event) {
|
|
152
|
+
return Effect.callback(resume => {
|
|
153
|
+
const error = this._deliveryError(event);
|
|
154
|
+
if (error) {
|
|
155
|
+
resume(Effect.fail(error));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const receipt = {
|
|
159
|
+
resume: exit => resume(exit)
|
|
160
|
+
};
|
|
161
|
+
this._pending.add(receipt);
|
|
162
|
+
Queue.offerUnsafe(this._mailbox, {
|
|
163
|
+
kind: 'event',
|
|
164
|
+
event,
|
|
165
|
+
receipt
|
|
166
|
+
});
|
|
167
|
+
return Effect.sync(() => {
|
|
168
|
+
this._pending.delete(receipt);
|
|
169
|
+
receipt.resume = undefined;
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
_deliveryError(event) {
|
|
76
174
|
if (this._settled) {
|
|
77
175
|
this.system.deadLetter(undefined, this._root, event, 'stopped');
|
|
78
|
-
return;
|
|
176
|
+
return this._stoppedError();
|
|
177
|
+
}
|
|
178
|
+
if (this.logic instanceof StateMachine && this.logic.isInternalEventType(event.type)) {
|
|
179
|
+
this.system.deadLetter(undefined, this._root, event, 'internalEvent');
|
|
180
|
+
return new EventRejectedError({
|
|
181
|
+
actorId: this.id,
|
|
182
|
+
event,
|
|
183
|
+
reason: 'internalEvent'
|
|
184
|
+
});
|
|
79
185
|
}
|
|
80
|
-
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** @internal */
|
|
190
|
+
_rejectReceipts() {
|
|
191
|
+
for (const receipt of this._pending) this._completeReceipt(receipt, Exit.fail(this._stoppedError()));
|
|
192
|
+
}
|
|
193
|
+
_stoppedError() {
|
|
194
|
+
return new ActorStoppedError({
|
|
195
|
+
actorId: this.id,
|
|
196
|
+
snapshot: this._snapshot
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** @internal */
|
|
201
|
+
_completeReceipt(receipt, exit) {
|
|
202
|
+
if (!receipt || !this._pending.delete(receipt)) return;
|
|
203
|
+
const resume = receipt.resume;
|
|
204
|
+
receipt.resume = undefined;
|
|
205
|
+
resume?.(exit);
|
|
81
206
|
}
|
|
82
207
|
|
|
83
208
|
/** Stops the actor, its children and every Effect it hosts. */
|
|
@@ -146,13 +271,13 @@ class EffectActor {
|
|
|
146
271
|
}
|
|
147
272
|
toJSON() {
|
|
148
273
|
return {
|
|
149
|
-
|
|
150
|
-
id: this.id
|
|
274
|
+
$actor: this.address
|
|
151
275
|
};
|
|
152
276
|
}
|
|
153
277
|
|
|
154
278
|
/** @internal Publishes a snapshot produced by the execution loop. */
|
|
155
|
-
_publish(snapshot) {
|
|
279
|
+
_publish(snapshot, receipt) {
|
|
280
|
+
if (this._settled) return;
|
|
156
281
|
this._snapshot = snapshot;
|
|
157
282
|
const status = snapshot.status;
|
|
158
283
|
if (status === 'active') {
|
|
@@ -161,16 +286,19 @@ class EffectActor {
|
|
|
161
286
|
}
|
|
162
287
|
return;
|
|
163
288
|
}
|
|
164
|
-
this._settle(snapshot);
|
|
289
|
+
this._settle(snapshot, receipt);
|
|
165
290
|
}
|
|
166
291
|
|
|
167
292
|
/** @internal Marks the actor stopped and notifies observers. */
|
|
168
|
-
_settle(snapshot) {
|
|
293
|
+
_settle(snapshot, processing) {
|
|
169
294
|
if (this._settled) {
|
|
170
295
|
return;
|
|
171
296
|
}
|
|
172
297
|
this._snapshot = snapshot;
|
|
173
298
|
this._settled = true;
|
|
299
|
+
for (const receipt of this._pending) {
|
|
300
|
+
if (receipt !== processing) this._completeReceipt(receipt, Exit.fail(this._stoppedError()));
|
|
301
|
+
}
|
|
174
302
|
const observers = [...this._observers];
|
|
175
303
|
this._observers.clear();
|
|
176
304
|
const status = snapshot.status;
|
|
@@ -315,7 +443,7 @@ function trackEffect(actor, host, interrupt) {
|
|
|
315
443
|
* The fiber belongs to the host scope, so closing the owning actor also
|
|
316
444
|
* waits for asynchronous invocation finalizers.
|
|
317
445
|
*/
|
|
318
|
-
function startHostedEffect(actor, effect, spanName, onExit) {
|
|
446
|
+
function startHostedEffect(actor, effect, spanName, onExit, onCancel) {
|
|
319
447
|
const host = requireEffectHost(actor);
|
|
320
448
|
let active = true;
|
|
321
449
|
const traced = Effect.withSpan(spanName, {
|
|
@@ -332,11 +460,18 @@ function startHostedEffect(actor, effect, spanName, onExit) {
|
|
|
332
460
|
}
|
|
333
461
|
active = false;
|
|
334
462
|
untrackEffect(actor, host, cancel);
|
|
463
|
+
onCancel?.();
|
|
335
464
|
interrupt();
|
|
336
465
|
};
|
|
337
|
-
|
|
466
|
+
// Register the fiber before its body runs: the body can synchronously
|
|
467
|
+
// trigger the host's close, which only awaits registered fibers.
|
|
468
|
+
const registered = Effect.withFiber(fiber => {
|
|
469
|
+
Fiber.runIn(fiber, host.scope);
|
|
470
|
+
host.fibers.add(fiber);
|
|
471
|
+
return traced;
|
|
472
|
+
});
|
|
473
|
+
const fiber = Effect.runForkWith(host.context)(registered);
|
|
338
474
|
const interrupt = () => fiber.interruptUnsafe();
|
|
339
|
-
host.fibers.add(fiber);
|
|
340
475
|
fiber.addObserver(exit => {
|
|
341
476
|
host.fibers.delete(fiber);
|
|
342
477
|
if (!active) {
|
|
@@ -352,22 +487,16 @@ function startHostedEffect(actor, effect, spanName, onExit) {
|
|
|
352
487
|
return cancel;
|
|
353
488
|
}
|
|
354
489
|
|
|
355
|
-
/**
|
|
356
|
-
* Runs an Effect in the actor's host context and settles when it exits.
|
|
357
|
-
* Interruption settles without error; failures and defects reject with the
|
|
358
|
-
* squashed cause so the actor's error handling can observe them.
|
|
359
|
-
*/
|
|
490
|
+
/** @internal */
|
|
360
491
|
function runHostedEffect(actor, effect, spanName) {
|
|
361
492
|
return new Promise((resolve, reject) => {
|
|
362
493
|
startHostedEffect(actor, effect, spanName, exit => {
|
|
363
|
-
if (Exit.isSuccess(exit)
|
|
494
|
+
if (Exit.isSuccess(exit)) {
|
|
364
495
|
resolve();
|
|
365
496
|
} else {
|
|
366
|
-
|
|
367
|
-
// oxlint-disable-next-line typescript/prefer-promise-reject-errors
|
|
368
|
-
reject(Cause.squash(exit.cause));
|
|
497
|
+
reject(new EffectFailure(exit.cause));
|
|
369
498
|
}
|
|
370
|
-
});
|
|
499
|
+
}, resolve);
|
|
371
500
|
});
|
|
372
501
|
}
|
|
373
502
|
|
|
@@ -416,7 +545,7 @@ function withActorScope(effect) {
|
|
|
416
545
|
}
|
|
417
546
|
|
|
418
547
|
const XSTATE_TIMER = 'xstate.timer';
|
|
419
|
-
const XSTATE_INIT = '
|
|
548
|
+
const XSTATE_INIT = 'xstate.init';
|
|
420
549
|
|
|
421
550
|
/** Options for {@link createEffectActor}. */
|
|
422
551
|
|
|
@@ -444,6 +573,8 @@ function createEffectActor(logic, ...[options]) {
|
|
|
444
573
|
const runFork = Effect.runForkWith(context);
|
|
445
574
|
const runPromise = Effect.runPromiseWith(context);
|
|
446
575
|
const mailbox = yield* Queue.unbounded();
|
|
576
|
+
let delivered;
|
|
577
|
+
const getDelivered = () => delivered;
|
|
447
578
|
const timers = new Map();
|
|
448
579
|
// `root` and `actor` are declared after the adapter below; its
|
|
449
580
|
// callbacks only run once they are initialized.
|
|
@@ -454,7 +585,7 @@ function createEffectActor(logic, ...[options]) {
|
|
|
454
585
|
Queue.offerUnsafe(mailbox, item);
|
|
455
586
|
}
|
|
456
587
|
};
|
|
457
|
-
const timerKey = (source,
|
|
588
|
+
const timerKey = (source, timer) => `${source.sessionId}:${JSON.stringify(timer)}`;
|
|
458
589
|
const inspectors = new Set();
|
|
459
590
|
let rootAnnounced = false;
|
|
460
591
|
const clock = Context.get(context, Clock.Clock);
|
|
@@ -470,14 +601,14 @@ function createEffectActor(logic, ...[options]) {
|
|
|
470
601
|
if (result && typeof result.then === 'function') {
|
|
471
602
|
void Promise.resolve(result).catch(error => {
|
|
472
603
|
offer({
|
|
473
|
-
|
|
604
|
+
kind: 'actionFailure',
|
|
474
605
|
error
|
|
475
606
|
});
|
|
476
607
|
});
|
|
477
608
|
}
|
|
478
609
|
} catch (error) {
|
|
479
610
|
offer({
|
|
480
|
-
|
|
611
|
+
kind: 'actionFailure',
|
|
481
612
|
error
|
|
482
613
|
});
|
|
483
614
|
}
|
|
@@ -501,7 +632,10 @@ function createEffectActor(logic, ...[options]) {
|
|
|
501
632
|
},
|
|
502
633
|
sendEvent: (source, target, event) => {
|
|
503
634
|
if (isRoot(target)) {
|
|
504
|
-
offer(
|
|
635
|
+
offer({
|
|
636
|
+
kind: 'event',
|
|
637
|
+
event: event
|
|
638
|
+
});
|
|
505
639
|
return;
|
|
506
640
|
}
|
|
507
641
|
deliverEvent(source, target, event);
|
|
@@ -513,25 +647,28 @@ function createEffectActor(logic, ...[options]) {
|
|
|
513
647
|
}
|
|
514
648
|
source._emit(event);
|
|
515
649
|
},
|
|
516
|
-
scheduleTimer: (source,
|
|
517
|
-
const key = timerKey(source,
|
|
650
|
+
scheduleTimer: (source, timer, delay) => {
|
|
651
|
+
const key = timerKey(source, timer);
|
|
518
652
|
timers.get(key)?.interruptUnsafe();
|
|
519
653
|
const fiber = Fiber.runIn(runFork(Effect.andThen(Effect.sleep(Duration.millis(delay)), Effect.sync(() => {
|
|
520
654
|
timers.delete(key);
|
|
521
655
|
const timerEvent = {
|
|
522
656
|
type: XSTATE_TIMER,
|
|
523
|
-
|
|
657
|
+
...timer
|
|
524
658
|
};
|
|
525
659
|
if (isRoot(source)) {
|
|
526
|
-
offer(
|
|
660
|
+
offer({
|
|
661
|
+
kind: 'event',
|
|
662
|
+
event: timerEvent
|
|
663
|
+
});
|
|
527
664
|
} else {
|
|
528
665
|
deliverEvent(source, source, timerEvent);
|
|
529
666
|
}
|
|
530
667
|
}))), actorScope);
|
|
531
668
|
timers.set(key, fiber);
|
|
532
669
|
},
|
|
533
|
-
cancelTimer: (source,
|
|
534
|
-
const key = timerKey(source,
|
|
670
|
+
cancelTimer: (source, timer) => {
|
|
671
|
+
const key = timerKey(source, timer);
|
|
535
672
|
timers.get(key)?.interruptUnsafe();
|
|
536
673
|
timers.delete(key);
|
|
537
674
|
},
|
|
@@ -543,8 +680,15 @@ function createEffectActor(logic, ...[options]) {
|
|
|
543
680
|
}
|
|
544
681
|
}
|
|
545
682
|
},
|
|
546
|
-
waitForEvent: () => runPromise(Queue.take(mailbox))
|
|
683
|
+
waitForEvent: () => runPromise(Queue.take(mailbox)).then(item => {
|
|
684
|
+
delivered = item;
|
|
685
|
+
return item.kind === 'event' ? item.event : {
|
|
686
|
+
type: 'xstate.effect.actionFailure'
|
|
687
|
+
};
|
|
688
|
+
})
|
|
547
689
|
}, {
|
|
690
|
+
// A live actor needs one FIFO for child events, sends and timers.
|
|
691
|
+
captureRootEvents: false,
|
|
548
692
|
inspect: event => {
|
|
549
693
|
// The pure step scope re-materializes the root ref per step and
|
|
550
694
|
// announces it again; observers should see the root once.
|
|
@@ -592,6 +736,7 @@ function createEffectActor(logic, ...[options]) {
|
|
|
592
736
|
status: 'stopped'
|
|
593
737
|
});
|
|
594
738
|
}
|
|
739
|
+
actor._rejectReceipts();
|
|
595
740
|
closeEffectHost(host);
|
|
596
741
|
};
|
|
597
742
|
|
|
@@ -625,19 +770,23 @@ function createEffectActor(logic, ...[options]) {
|
|
|
625
770
|
bindEffectHost(actor, host);
|
|
626
771
|
const executeEffects = batch => Effect.promise(() => durable.executeEffects(batch).then(() => undefined, error => {
|
|
627
772
|
offer({
|
|
628
|
-
|
|
773
|
+
kind: 'actionFailure',
|
|
629
774
|
error
|
|
630
775
|
});
|
|
631
776
|
}));
|
|
632
777
|
const loop = Effect.gen(function* () {
|
|
633
778
|
yield* executeEffects(effects);
|
|
634
779
|
while (snapshot.status === 'active' && !stopped) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
780
|
+
delivered = undefined;
|
|
781
|
+
const received = yield* Effect.promise(() => durable.waitForEvent().catch(() => undefined));
|
|
782
|
+
if (received === undefined || stopped) break;
|
|
783
|
+
const item = getDelivered() ?? {
|
|
784
|
+
kind: 'event',
|
|
785
|
+
event: received
|
|
786
|
+
};
|
|
787
|
+
const receipt = item.kind === 'event' ? item.receipt : undefined;
|
|
639
788
|
let event;
|
|
640
|
-
if (
|
|
789
|
+
if (item.kind === 'actionFailure') {
|
|
641
790
|
const errorEvent = logic.getExecutionErrorEvent?.(snapshot, item.error);
|
|
642
791
|
if (!errorEvent) {
|
|
643
792
|
snapshot = errorSnapshot(snapshot, item.error);
|
|
@@ -645,16 +794,25 @@ function createEffectActor(logic, ...[options]) {
|
|
|
645
794
|
}
|
|
646
795
|
event = errorEvent;
|
|
647
796
|
} else {
|
|
648
|
-
event = item;
|
|
797
|
+
event = item.event;
|
|
649
798
|
}
|
|
650
799
|
try {
|
|
651
800
|
[snapshot, effects] = durable.transition(snapshot, event);
|
|
652
801
|
} catch (error) {
|
|
653
802
|
snapshot = errorSnapshot(snapshot, error);
|
|
803
|
+
actor._publish(snapshot, receipt);
|
|
804
|
+
actor._completeReceipt(receipt, Exit.succeed(snapshot));
|
|
654
805
|
break;
|
|
655
806
|
}
|
|
656
|
-
actor._publish(snapshot);
|
|
807
|
+
actor._publish(snapshot, receipt);
|
|
808
|
+
if (stopped) break;
|
|
809
|
+
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;
|
|
657
810
|
yield* executeEffects(effects);
|
|
811
|
+
actor._completeReceipt(receipt, rejectedReason !== undefined ? Exit.fail(new EventRejectedError({
|
|
812
|
+
actorId: actor.id,
|
|
813
|
+
event,
|
|
814
|
+
reason: rejectedReason
|
|
815
|
+
})) : Exit.succeed(snapshot));
|
|
658
816
|
}
|
|
659
817
|
if (!stopped) {
|
|
660
818
|
actor._publish(snapshot);
|
|
@@ -684,45 +842,11 @@ function createEffectActor(logic, ...[options]) {
|
|
|
684
842
|
}) => actor));
|
|
685
843
|
}
|
|
686
844
|
|
|
687
|
-
/**
|
|
688
|
-
* The failure an Effect-backed actor reports when its Effect was interrupted
|
|
689
|
-
* by something other than the actor being stopped, such as `Effect.interrupt`,
|
|
690
|
-
* losing an `Effect.race`, or an `Effect.timeout` that interrupts.
|
|
691
|
-
*/
|
|
692
|
-
class EffectInterruptedError extends Data.TaggedError('EffectInterruptedError') {
|
|
693
|
-
get message() {
|
|
694
|
-
return 'Effect was interrupted before the actor completed';
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* Reported by `waitFor` when the actor stops or errors before a snapshot
|
|
700
|
-
* matches, and by `join` when the actor stops without output. `join` reports
|
|
701
|
-
* an errored actor's own `snapshot.error` instead.
|
|
702
|
-
*/
|
|
703
|
-
class ActorStoppedError extends Data.TaggedError('ActorStoppedError') {
|
|
704
|
-
get message() {
|
|
705
|
-
return `Actor "${this.actorId}" ${this.snapshot.status === 'error' ? 'errored' : 'stopped'} before completing`;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* Reported by the `send` atom of `createActorAtoms` when an event is sent
|
|
711
|
-
* before the actor's runtime has finished building.
|
|
712
|
-
*/
|
|
713
|
-
class NotReadyError extends Data.TaggedError('NotReadyError') {
|
|
714
|
-
get message() {
|
|
715
|
-
return 'The actor is not ready yet';
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
/** The tag of the machine itself, used when the root state is parallel. */
|
|
720
|
-
const MACHINE_TAG = '(machine)';
|
|
721
|
-
|
|
722
845
|
/**
|
|
723
846
|
* The dotted path of a state value: `'idle'`, `'form.editing'`. A parallel
|
|
724
|
-
* state has no single path, so its tag stops at the parallel state
|
|
725
|
-
*
|
|
847
|
+
* state has no single path, so its tag stops at the parallel state. When the
|
|
848
|
+
* machine itself is parallel, the tag is `TRootTag`: the machine id, or `''`
|
|
849
|
+
* for a machine without one.
|
|
726
850
|
*/
|
|
727
851
|
|
|
728
852
|
/**
|
|
@@ -734,16 +858,16 @@ const MACHINE_TAG = '(machine)';
|
|
|
734
858
|
/** The tagged state union of a machine. */
|
|
735
859
|
|
|
736
860
|
/** Computes the dotted path tag of a state value. */
|
|
737
|
-
function stateTag(value, prefix = '') {
|
|
861
|
+
function stateTag(value, rootTag, prefix = '') {
|
|
738
862
|
if (typeof value === 'string') {
|
|
739
863
|
return prefix ? `${prefix}.${value}` : value;
|
|
740
864
|
}
|
|
741
865
|
const keys = Object.keys(value);
|
|
742
866
|
if (keys.length !== 1) {
|
|
743
|
-
return prefix ||
|
|
867
|
+
return prefix || rootTag;
|
|
744
868
|
}
|
|
745
869
|
const key = keys[0];
|
|
746
|
-
return stateTag(value[key], prefix ? `${prefix}.${key}` : key);
|
|
870
|
+
return stateTag(value[key], rootTag, prefix ? `${prefix}.${key}` : key);
|
|
747
871
|
}
|
|
748
872
|
|
|
749
873
|
/**
|
|
@@ -763,11 +887,11 @@ function stateTag(value, prefix = '') {
|
|
|
763
887
|
*/
|
|
764
888
|
function taggedState(snapshot) {
|
|
765
889
|
return {
|
|
766
|
-
_tag: stateTag(snapshot.value),
|
|
890
|
+
_tag: stateTag(snapshot.value, snapshot.machine.id),
|
|
767
891
|
value: snapshot.value,
|
|
768
892
|
context: snapshot.context,
|
|
769
893
|
snapshot
|
|
770
894
|
};
|
|
771
895
|
}
|
|
772
896
|
|
|
773
|
-
export { ActorStoppedError as A,
|
|
897
|
+
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 };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Cause } from 'effect';
|
|
2
2
|
import { Atom, AsyncResult } from 'effect/reactivity';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import { StateMachine } from 'xstate';
|
|
4
|
+
import { c as createEffectActor, a as causeFromError, N as NotReadyError, t as taggedState } from './state-37b37ed4.js';
|
|
5
|
+
export { N as NotReadyError } from './state-37b37ed4.js';
|
|
6
6
|
import 'xstate/durable';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -55,7 +55,7 @@ function createActorAtoms(runtime, logic, ...[options]) {
|
|
|
55
55
|
}
|
|
56
56
|
const value = current.value;
|
|
57
57
|
if (value.status === 'error') {
|
|
58
|
-
return AsyncResult.failureWithPrevious(
|
|
58
|
+
return AsyncResult.failureWithPrevious(causeFromError(value.error), {
|
|
59
59
|
previous: get.self()
|
|
60
60
|
});
|
|
61
61
|
}
|
|
@@ -75,15 +75,17 @@ function createActorAtoms(runtime, logic, ...[options]) {
|
|
|
75
75
|
function select(selector) {
|
|
76
76
|
return Atom.map(snapshot, result => AsyncResult.map(result, selector));
|
|
77
77
|
}
|
|
78
|
-
const
|
|
79
|
-
return {
|
|
78
|
+
const base = {
|
|
80
79
|
actor,
|
|
81
80
|
snapshot,
|
|
82
81
|
result,
|
|
83
82
|
send: sendAtom,
|
|
84
|
-
select
|
|
85
|
-
state
|
|
83
|
+
select
|
|
86
84
|
};
|
|
85
|
+
return logic instanceof StateMachine ? {
|
|
86
|
+
...base,
|
|
87
|
+
state: select(current => taggedState(current))
|
|
88
|
+
} : base;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
export { createActorAtoms };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export * from "./declarations/src/oxlint.js";
|
|
2
|
+
export { default } from "./declarations/src/oxlint.js";
|
|
3
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoieHN0YXRlLWVmZmVjdC1veGxpbnQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4vZGVjbGFyYXRpb25zL3NyYy9veGxpbnQuZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSJ9
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { defineRule, definePlugin } from '@oxlint/plugins';
|
|
2
|
+
|
|
3
|
+
const ENQUEUE_NAMES = new Set(['enq', 'enqueue']);
|
|
4
|
+
const EFFECT_LOGIC_CONSTRUCTORS = new Set(['createEffectLogic', 'createEffectStreamLogic', 'createEffectEventStreamLogic']);
|
|
5
|
+
const isNode = value => typeof value === 'object' && value !== null && 'type' in value && typeof value.type === 'string';
|
|
6
|
+
const rootIdentifier = node => {
|
|
7
|
+
let current = node;
|
|
8
|
+
for (;;) {
|
|
9
|
+
if (current.type === 'CallExpression') {
|
|
10
|
+
current = current.callee;
|
|
11
|
+
} else if (current.type === 'MemberExpression') {
|
|
12
|
+
current = current.object;
|
|
13
|
+
} else if (current.type === 'Identifier') {
|
|
14
|
+
return current.name;
|
|
15
|
+
} else {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const returnedExpressions = fn => {
|
|
21
|
+
if (fn.body === null) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
if (fn.body.type !== 'BlockStatement') {
|
|
25
|
+
return [fn.body];
|
|
26
|
+
}
|
|
27
|
+
const found = [];
|
|
28
|
+
const visit = node => {
|
|
29
|
+
if (node.type === 'ReturnStatement') {
|
|
30
|
+
if (node.argument) {
|
|
31
|
+
found.push(node.argument);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression' || node.type === 'FunctionDeclaration') {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const [key, value] of Object.entries(node)) {
|
|
39
|
+
if (key === 'parent') {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
for (const item of value) {
|
|
44
|
+
if (isNode(item)) {
|
|
45
|
+
visit(item);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else if (isNode(value)) {
|
|
49
|
+
visit(value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
visit(fn.body);
|
|
54
|
+
return found;
|
|
55
|
+
};
|
|
56
|
+
const isPromiseResolve = node => node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'Promise' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'resolve';
|
|
57
|
+
const isEffectExpression = node => {
|
|
58
|
+
if (node.type !== 'CallExpression') {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
if (isPromiseResolve(node)) {
|
|
62
|
+
const [argument] = node.arguments;
|
|
63
|
+
return argument !== undefined && isEffectExpression(argument);
|
|
64
|
+
}
|
|
65
|
+
return rootIdentifier(node) === 'Effect';
|
|
66
|
+
};
|
|
67
|
+
const isInlineEffectLogic = node => node.type === 'CallExpression' && node.callee.type === 'Identifier' && EFFECT_LOGIC_CONSTRUCTORS.has(node.callee.name);
|
|
68
|
+
const isFunctionNode = node => node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression';
|
|
69
|
+
const noInlineEffect = defineRule({
|
|
70
|
+
create(context) {
|
|
71
|
+
return {
|
|
72
|
+
CallExpression(node) {
|
|
73
|
+
const {
|
|
74
|
+
callee
|
|
75
|
+
} = node;
|
|
76
|
+
if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && ENQUEUE_NAMES.has(callee.object.name) && callee.property.type === 'Identifier' && callee.property.name === 'spawn') {
|
|
77
|
+
const [logic] = node.arguments;
|
|
78
|
+
if (logic !== undefined && isInlineEffectLogic(logic)) {
|
|
79
|
+
context.report({
|
|
80
|
+
messageId: 'inlineSpawn',
|
|
81
|
+
node: logic
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (callee.type !== 'Identifier' || !ENQUEUE_NAMES.has(callee.name)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const [action] = node.arguments;
|
|
90
|
+
if (action === undefined || !isFunctionNode(action)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
for (const expression of returnedExpressions(action)) {
|
|
94
|
+
if (isEffectExpression(expression)) {
|
|
95
|
+
context.report({
|
|
96
|
+
messageId: 'inlineAction',
|
|
97
|
+
node: expression
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
meta: {
|
|
105
|
+
docs: {
|
|
106
|
+
description: 'Disallow Effects in inline enqueue callbacks and inline Effect logic in enq.spawn; declare them in setupEffect({ actions }) or actors.'
|
|
107
|
+
},
|
|
108
|
+
messages: {
|
|
109
|
+
inlineAction: 'An Effect returned from an inline action is discarded. Declare it in setupEffect({ actions }) and run it with enq(args.actions.name, args).',
|
|
110
|
+
inlineSpawn: 'Inline Effect logic passed to enq.spawn is invisible to RequirementsFrom and rejected at runtime. Declare it in actors and spawn args.actors.name.'
|
|
111
|
+
},
|
|
112
|
+
type: 'problem'
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
var oxlint = definePlugin({
|
|
116
|
+
meta: {
|
|
117
|
+
name: 'xstate-effect'
|
|
118
|
+
},
|
|
119
|
+
rules: {
|
|
120
|
+
'no-inline-effect': noInlineEffect
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export { oxlint as default };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export * from "./declarations/src/test.js";
|
|
2
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoieHN0YXRlLWVmZmVjdC10ZXN0LmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuL2RlY2xhcmF0aW9ucy9zcmMvdGVzdC5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIn0=
|