effect-machine 0.12.0 → 0.13.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 +128 -324
- package/dist/actor.d.ts +42 -27
- package/dist/actor.js +212 -305
- package/dist/cluster/entity-machine.js +20 -9
- package/dist/cluster/to-entity.d.ts +2 -2
- package/dist/errors.d.ts +16 -19
- package/dist/errors.js +3 -5
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/internal/runtime.d.ts +81 -6
- package/dist/internal/runtime.js +166 -57
- package/dist/internal/transition.d.ts +5 -4
- package/dist/internal/transition.js +9 -9
- package/dist/machine.d.ts +65 -44
- package/dist/machine.js +68 -67
- package/dist/schema.js +1 -1
- package/dist/supervision.d.ts +97 -0
- package/dist/supervision.js +42 -0
- package/dist/testing.d.ts +17 -8
- package/dist/testing.js +22 -25
- package/package.json +5 -5
- package/v3/dist/actor.d.ts +50 -34
- package/v3/dist/actor.js +209 -289
- package/v3/dist/cluster/entity-machine.js +5 -5
- package/v3/dist/errors.d.ts +3 -8
- package/v3/dist/errors.js +2 -4
- package/v3/dist/index.d.ts +5 -4
- package/v3/dist/index.js +3 -2
- package/v3/dist/internal/runtime.d.ts +82 -5
- package/v3/dist/internal/runtime.js +147 -48
- package/v3/dist/internal/transition.d.ts +5 -4
- package/v3/dist/internal/transition.js +8 -8
- package/v3/dist/machine.d.ts +18 -36
- package/v3/dist/machine.js +54 -64
- package/v3/dist/supervision.d.ts +97 -0
- package/v3/dist/supervision.js +42 -0
- package/v3/dist/testing.d.ts +18 -9
- package/v3/dist/testing.js +21 -22
package/dist/internal/runtime.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { INTERNAL_INIT_EVENT } from "./utils.js";
|
|
2
|
-
import { NoReplyError } from "../errors.js";
|
|
3
2
|
import { processEventCore, runSpawnEffects, shouldPostpone } from "./transition.js";
|
|
3
|
+
import { NoReplyError } from "../errors.js";
|
|
4
|
+
import { ActorExit } from "../supervision.js";
|
|
4
5
|
import { ActorSystem } from "../actor.js";
|
|
5
|
-
import { Deferred, Effect, Exit, Fiber, Queue, Ref, Schema, Scope, SubscriptionRef } from "effect";
|
|
6
|
+
import { Cause, Deferred, Effect, Exit, Fiber, Option, Queue, Ref, Schema, Scope, SubscriptionRef } from "effect";
|
|
6
7
|
//#region src/internal/runtime.ts
|
|
7
8
|
/**
|
|
8
9
|
* Shared runtime kernel for machine event processing.
|
|
@@ -10,35 +11,42 @@ import { Deferred, Effect, Exit, Fiber, Queue, Ref, Schema, Scope, SubscriptionR
|
|
|
10
11
|
* Provides a single-queue event loop with:
|
|
11
12
|
* - Sequential event processing (no split-mailbox race)
|
|
12
13
|
* - Postpone buffer with drain-on-state-change (gen_statem)
|
|
13
|
-
* - Background effect lifecycle
|
|
14
|
+
* - Background effect lifecycle (under actorScope fault boundary)
|
|
14
15
|
* - Spawn effect lifecycle (per-state scope)
|
|
15
16
|
* - Final state detection → stop
|
|
16
17
|
* - Reply settlement (call/ask Deferreds)
|
|
17
18
|
* - Reply schema validation
|
|
19
|
+
* - Lifecycle hooks for actor-specific concerns (inspection, listeners, etc.)
|
|
20
|
+
* - ActorExit with exit reason (Final/Stopped/Defect) via exitDeferred
|
|
18
21
|
*
|
|
19
|
-
* Used by entity-machine
|
|
20
|
-
* with additional concerns (inspection, listeners, subscription ref, etc.)
|
|
21
|
-
* that will be migrated to use this kernel in a future refactor.
|
|
22
|
+
* Used by entity-machine and local actor (actor.ts delegates here).
|
|
22
23
|
*
|
|
23
24
|
* @internal
|
|
24
25
|
*/
|
|
25
26
|
/**
|
|
26
27
|
* Create a runtime for a machine. Returns a handle for sending events
|
|
27
28
|
* and querying state. The runtime owns:
|
|
28
|
-
* - Single event queue (all events serialized)
|
|
29
29
|
* - Event loop fiber
|
|
30
30
|
* - Postpone buffer
|
|
31
|
-
* - Background effects
|
|
31
|
+
* - Background effects (under actorScope)
|
|
32
32
|
* - State scope (spawn effects)
|
|
33
33
|
* - Final state detection
|
|
34
|
+
* - Exit reason via exitDeferred
|
|
35
|
+
*
|
|
36
|
+
* Resources (stateRef, eventQueue, stoppedRef) are either cell-provided
|
|
37
|
+
* or allocated fresh by the runtime.
|
|
34
38
|
*
|
|
35
39
|
* @internal
|
|
36
40
|
*/
|
|
37
41
|
const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (machine, system, config) {
|
|
38
|
-
const { actorId, hooks } = config;
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
+
const { actorId, hooks, lifecycle } = config;
|
|
43
|
+
const services = yield* Effect.services();
|
|
44
|
+
const fork = Effect.runForkWith(services);
|
|
45
|
+
const stateRef = config.cellResources?.stateRef ?? (yield* SubscriptionRef.make(machine.initial));
|
|
46
|
+
const stoppedRef = config.cellResources?.stoppedRef ?? (yield* Ref.make(false));
|
|
47
|
+
const eventQueue = config.cellResources?.eventQueue ?? (yield* config.queueFactory ?? Queue.unbounded());
|
|
48
|
+
const exitDeferred = yield* Deferred.make();
|
|
49
|
+
const actorScope = yield* Scope.make();
|
|
42
50
|
const deferredReplyRef = { current: void 0 };
|
|
43
51
|
const selfSend = Effect.fn("effect-machine.runtime.self.send")(function* (event) {
|
|
44
52
|
if (!(yield* Ref.get(stoppedRef))) yield* Queue.offer(eventQueue, {
|
|
@@ -46,15 +54,18 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
46
54
|
event
|
|
47
55
|
});
|
|
48
56
|
});
|
|
57
|
+
const childPrefix = config.childIdPrefix ?? "";
|
|
58
|
+
const defaultSpawn = (childId, childMachine) => system.spawn(`${childPrefix}${childId}`, childMachine).pipe(Effect.provideService(ActorSystem, system));
|
|
59
|
+
const onChildSpawned = config.onChildSpawned;
|
|
49
60
|
const self = {
|
|
50
61
|
send: selfSend,
|
|
51
62
|
cast: selfSend,
|
|
52
|
-
spawn: (childId, childMachine) =>
|
|
63
|
+
spawn: onChildSpawned !== void 0 ? (childId, childMachine) => defaultSpawn(childId, childMachine).pipe(Effect.tap((child) => onChildSpawned(childId, child))) : defaultSpawn,
|
|
53
64
|
reply: (value) => Effect.sync(() => {
|
|
54
65
|
const deferred = deferredReplyRef.current;
|
|
55
66
|
if (deferred !== void 0) {
|
|
56
67
|
deferredReplyRef.current = void 0;
|
|
57
|
-
|
|
68
|
+
fork(Deferred.succeed(deferred, value));
|
|
58
69
|
return true;
|
|
59
70
|
}
|
|
60
71
|
return false;
|
|
@@ -72,34 +83,65 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
72
83
|
};
|
|
73
84
|
const { effects: effectSlots } = machine._slots;
|
|
74
85
|
for (const bg of machine.backgroundEffects) {
|
|
75
|
-
const fiber = yield*
|
|
86
|
+
const fiber = yield* bg.handler({
|
|
76
87
|
actorId,
|
|
77
88
|
state: machine.initial,
|
|
78
89
|
event: initEvent,
|
|
79
90
|
self,
|
|
80
91
|
effects: effectSlots,
|
|
81
92
|
system
|
|
82
|
-
}).pipe(Effect.provideService(machine.Context, ctx)));
|
|
93
|
+
}).pipe(Effect.provideService(machine.Context, ctx), Effect.forkIn(actorScope));
|
|
83
94
|
backgroundFibers.push(fiber);
|
|
84
95
|
}
|
|
85
|
-
yield*
|
|
96
|
+
if (lifecycle?.onInitialSpawnEffects !== void 0) yield* lifecycle.onInitialSpawnEffects(machine.initial);
|
|
97
|
+
const loopFiberRef = { current: void 0 };
|
|
98
|
+
const initialSpawnDefectSignal = (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => loopFiberRef.current !== void 0 ? Fiber.interrupt(loopFiberRef.current) : Effect.void)), Effect.asVoid);
|
|
99
|
+
yield* runSpawnEffects(machine, machine.initial, initEvent, self, stateScopeRef.current, system, actorId, hooks?.onError, initialSpawnDefectSignal).pipe(Effect.catchCause((cause) => {
|
|
100
|
+
return Effect.gen(function* () {
|
|
101
|
+
yield* Ref.set(stoppedRef, true);
|
|
102
|
+
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
103
|
+
yield* Scope.close(actorScope, Exit.void);
|
|
104
|
+
yield* Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn"));
|
|
105
|
+
return yield* Effect.failCause(cause);
|
|
106
|
+
});
|
|
107
|
+
}));
|
|
108
|
+
/** Set the exit deferred exactly once. */
|
|
109
|
+
const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
|
|
86
110
|
if (machine.finalStates.has(machine.initial._tag)) {
|
|
111
|
+
if (lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(machine.initial);
|
|
87
112
|
yield* Ref.set(stoppedRef, true);
|
|
88
113
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
89
|
-
yield*
|
|
90
|
-
|
|
114
|
+
yield* Scope.close(actorScope, Exit.void);
|
|
115
|
+
yield* setExit(ActorExit.Final(machine.initial));
|
|
116
|
+
return makeHandle(stateRef, stoppedRef, eventQueue, exitDeferred, actorScope);
|
|
91
117
|
}
|
|
92
|
-
const
|
|
118
|
+
const augmentedHooks = {
|
|
119
|
+
...hooks,
|
|
120
|
+
onSpawnDefect: (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => loopFiberRef.current !== void 0 ? Fiber.interrupt(loopFiberRef.current) : Effect.void)), Effect.asVoid)
|
|
121
|
+
};
|
|
122
|
+
const loopFiber = yield* Effect.forkDetach(runtimeEventLoop(machine, stateRef, eventQueue, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, augmentedHooks, deferredReplyRef, lifecycle, config.wrapProcess, fork));
|
|
123
|
+
loopFiberRef.current = loopFiber;
|
|
124
|
+
if (backgroundFibers.length > 0) yield* Effect.raceAll(backgroundFibers.map((fiber) => Fiber.await(fiber).pipe(Effect.flatMap((exit) => {
|
|
125
|
+
if (exit._tag === "Failure" && !Cause.hasInterruptsOnly(exit.cause)) return setExit(ActorExit.Defect(exit.cause, "background")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Fiber.interrupt(loopFiber)));
|
|
126
|
+
return Effect.never;
|
|
127
|
+
})))).pipe(Effect.forkIn(actorScope));
|
|
128
|
+
yield* Effect.forkDetach(Effect.gen(function* () {
|
|
129
|
+
const loopExit = yield* Fiber.await(loopFiber);
|
|
130
|
+
if (loopExit._tag === "Success") yield* Scope.close(actorScope, Exit.void);
|
|
131
|
+
else yield* Scope.close(actorScope, loopExit);
|
|
132
|
+
}));
|
|
93
133
|
const stop = Effect.gen(function* () {
|
|
94
134
|
if (yield* Ref.get(stoppedRef)) return;
|
|
135
|
+
if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
|
|
95
136
|
yield* Ref.set(stoppedRef, true);
|
|
96
137
|
yield* Fiber.interrupt(loopFiber);
|
|
97
138
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
98
|
-
yield*
|
|
139
|
+
yield* Scope.close(actorScope, Exit.void);
|
|
140
|
+
yield* setExit(ActorExit.Stopped);
|
|
99
141
|
}).pipe(Effect.asVoid);
|
|
100
|
-
yield* Effect.addFinalizer(() => stop);
|
|
142
|
+
if (config.skipFinalizer !== true) yield* Effect.addFinalizer(() => stop);
|
|
101
143
|
return {
|
|
102
|
-
...makeHandle(stateRef, stoppedRef, eventQueue,
|
|
144
|
+
...makeHandle(stateRef, stoppedRef, eventQueue, exitDeferred, actorScope),
|
|
103
145
|
stop
|
|
104
146
|
};
|
|
105
147
|
});
|
|
@@ -107,7 +149,7 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
107
149
|
* Build the runtime handle (send/ask/getState/isStopped).
|
|
108
150
|
* Shared between initial-final and normal paths.
|
|
109
151
|
*/
|
|
110
|
-
const makeHandle = (stateRef, stoppedRef, eventQueue,
|
|
152
|
+
const makeHandle = (stateRef, stoppedRef, eventQueue, exitDeferred, actorScope) => ({
|
|
111
153
|
send: (event) => Effect.gen(function* () {
|
|
112
154
|
if (!(yield* Ref.get(stoppedRef))) yield* Queue.offer(eventQueue, {
|
|
113
155
|
_tag: "send",
|
|
@@ -141,28 +183,65 @@ const makeHandle = (stateRef, stoppedRef, eventQueue, _machine) => ({
|
|
|
141
183
|
getState: SubscriptionRef.get(stateRef),
|
|
142
184
|
stateRef,
|
|
143
185
|
isStopped: Ref.get(stoppedRef),
|
|
144
|
-
stop: Effect.void
|
|
186
|
+
stop: Effect.void,
|
|
187
|
+
_queue: eventQueue,
|
|
188
|
+
_stoppedRef: stoppedRef,
|
|
189
|
+
exitDeferred,
|
|
190
|
+
actorScope
|
|
145
191
|
});
|
|
146
|
-
const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self,
|
|
192
|
+
const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, hooks, deferredReplyRef, lifecycle, wrapProcess, fork) {
|
|
193
|
+
const forkEffect = fork ?? Effect.runFork;
|
|
194
|
+
/** Set the exit deferred exactly once. */
|
|
195
|
+
const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
|
|
147
196
|
const postponed = [];
|
|
148
197
|
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
149
198
|
const processQueued = Effect.fn("effect-machine.runtime.processQueued")(function* (queued) {
|
|
150
199
|
const event = queued.event;
|
|
151
200
|
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
152
201
|
if (hasPostponeRules && shouldPostpone(machine, currentState._tag, event._tag)) {
|
|
202
|
+
if (queued._tag === "call") {
|
|
203
|
+
const postponedResult = {
|
|
204
|
+
newState: currentState,
|
|
205
|
+
previousState: currentState,
|
|
206
|
+
transitioned: false,
|
|
207
|
+
lifecycleRan: false,
|
|
208
|
+
isFinal: false,
|
|
209
|
+
hasReply: false,
|
|
210
|
+
deferReply: false,
|
|
211
|
+
reply: void 0,
|
|
212
|
+
postponed: true
|
|
213
|
+
};
|
|
214
|
+
yield* Deferred.succeed(queued.reply, postponedResult);
|
|
215
|
+
}
|
|
216
|
+
if (queued._tag === "sendWait") yield* Deferred.succeed(queued.done, void 0);
|
|
153
217
|
postponed.push({
|
|
154
218
|
_tag: "send",
|
|
155
219
|
event
|
|
156
220
|
});
|
|
157
|
-
if (queued._tag === "sendWait") yield* Deferred.succeed(queued.done, void 0);
|
|
158
221
|
return {
|
|
159
222
|
shouldStop: false,
|
|
160
|
-
stateChanged: false
|
|
223
|
+
stateChanged: false,
|
|
224
|
+
result: {
|
|
225
|
+
newState: currentState,
|
|
226
|
+
previousState: currentState,
|
|
227
|
+
transitioned: false,
|
|
228
|
+
lifecycleRan: false,
|
|
229
|
+
isFinal: false,
|
|
230
|
+
hasReply: false,
|
|
231
|
+
deferReply: false,
|
|
232
|
+
reply: void 0,
|
|
233
|
+
postponed: true
|
|
234
|
+
}
|
|
161
235
|
};
|
|
162
236
|
}
|
|
237
|
+
if (lifecycle?.onEvent !== void 0) yield* lifecycle.onEvent(currentState, event);
|
|
163
238
|
const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, actorId, hooks);
|
|
164
239
|
if (result.transitioned) yield* SubscriptionRef.set(stateRef, result.newState);
|
|
240
|
+
if (lifecycle?.onStateChange !== void 0 && result.transitioned) yield* lifecycle.onStateChange(result, event);
|
|
165
241
|
switch (queued._tag) {
|
|
242
|
+
case "call":
|
|
243
|
+
yield* Deferred.succeed(queued.reply, result);
|
|
244
|
+
break;
|
|
166
245
|
case "sendWait":
|
|
167
246
|
yield* Deferred.succeed(queued.done, void 0);
|
|
168
247
|
break;
|
|
@@ -186,29 +265,67 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
|
|
|
186
265
|
}));
|
|
187
266
|
break;
|
|
188
267
|
}
|
|
268
|
+
if (lifecycle?.onProcessed !== void 0 && result.transitioned) yield* lifecycle.onProcessed(result, event);
|
|
269
|
+
const shouldStop = result.isFinal && result.lifecycleRan;
|
|
270
|
+
if (shouldStop && lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(result.newState);
|
|
189
271
|
return {
|
|
190
|
-
shouldStop
|
|
191
|
-
stateChanged: result.lifecycleRan
|
|
272
|
+
shouldStop,
|
|
273
|
+
stateChanged: result.lifecycleRan,
|
|
274
|
+
result
|
|
192
275
|
};
|
|
193
276
|
});
|
|
277
|
+
const shutdown = (exitReason) => Effect.gen(function* () {
|
|
278
|
+
yield* Ref.set(stoppedRef, true);
|
|
279
|
+
if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
|
|
280
|
+
settlePostponed(postponed, actorId, forkEffect);
|
|
281
|
+
const remaining = [];
|
|
282
|
+
let next = yield* Queue.poll(eventQueue);
|
|
283
|
+
while (Option.isSome(next)) {
|
|
284
|
+
remaining.push(next.value);
|
|
285
|
+
next = yield* Queue.poll(eventQueue);
|
|
286
|
+
}
|
|
287
|
+
for (const entry of remaining) if (entry._tag === "sendWait") forkEffect(Deferred.succeed(entry.done, void 0));
|
|
288
|
+
else if (entry._tag === "ask") forkEffect(Deferred.fail(entry.reply, new NoReplyError({
|
|
289
|
+
actorId,
|
|
290
|
+
eventTag: entry.event._tag
|
|
291
|
+
})));
|
|
292
|
+
else if (entry._tag === "call") {
|
|
293
|
+
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
294
|
+
forkEffect(Deferred.succeed(entry.reply, {
|
|
295
|
+
newState: currentState,
|
|
296
|
+
previousState: currentState,
|
|
297
|
+
transitioned: false,
|
|
298
|
+
lifecycleRan: false,
|
|
299
|
+
isFinal: machine.finalStates.has(currentState._tag),
|
|
300
|
+
hasReply: false,
|
|
301
|
+
deferReply: false,
|
|
302
|
+
reply: void 0,
|
|
303
|
+
postponed: false
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
307
|
+
yield* setExit(exitReason);
|
|
308
|
+
});
|
|
194
309
|
while (true) {
|
|
195
310
|
const queued = yield* Queue.take(eventQueue);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return
|
|
311
|
+
if (queued._tag === "drain") {
|
|
312
|
+
yield* shutdown(ActorExit.Stopped);
|
|
313
|
+
yield* Deferred.succeed(queued.done, void 0);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const eventQueued = queued;
|
|
317
|
+
const processInner = processQueued(eventQueued);
|
|
318
|
+
const { shouldStop, stateChanged } = yield* (wrapProcess !== void 0 ? Effect.gen(function* () {
|
|
319
|
+
return yield* wrapProcess(yield* SubscriptionRef.get(stateRef), eventQueued.event, processInner);
|
|
320
|
+
}) : processInner).pipe(Effect.catchCause((cause) => {
|
|
321
|
+
if (queued._tag === "sendWait") forkEffect(Deferred.failCause(queued.done, cause));
|
|
322
|
+
else if (queued._tag === "ask") forkEffect(Deferred.die(queued.reply, cause));
|
|
323
|
+
else if (queued._tag === "call") forkEffect(Deferred.failCause(queued.reply, cause));
|
|
324
|
+
return shutdown(ActorExit.Defect(cause, "transition")).pipe(Effect.andThen(Effect.failCause(cause)));
|
|
200
325
|
}));
|
|
201
326
|
if (shouldStop) {
|
|
202
|
-
yield*
|
|
203
|
-
|
|
204
|
-
const remaining = yield* Queue.takeAll(eventQueue);
|
|
205
|
-
for (const entry of remaining) if (entry._tag === "sendWait") Effect.runFork(Deferred.succeed(entry.done, void 0));
|
|
206
|
-
else if (entry._tag === "ask") Effect.runFork(Deferred.fail(entry.reply, new NoReplyError({
|
|
207
|
-
actorId,
|
|
208
|
-
eventTag: entry.event._tag
|
|
209
|
-
})));
|
|
210
|
-
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
211
|
-
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
327
|
+
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
328
|
+
yield* shutdown(ActorExit.Final(finalState));
|
|
212
329
|
return;
|
|
213
330
|
}
|
|
214
331
|
let drainTriggered = stateChanged;
|
|
@@ -218,16 +335,8 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
|
|
|
218
335
|
for (const entry of drained) {
|
|
219
336
|
const drain = yield* processQueued(entry);
|
|
220
337
|
if (drain.shouldStop) {
|
|
221
|
-
yield*
|
|
222
|
-
|
|
223
|
-
const remaining2 = yield* Queue.takeAll(eventQueue);
|
|
224
|
-
for (const r of remaining2) if (r._tag === "sendWait") Effect.runFork(Deferred.succeed(r.done, void 0));
|
|
225
|
-
else if (r._tag === "ask") Effect.runFork(Deferred.fail(r.reply, new NoReplyError({
|
|
226
|
-
actorId,
|
|
227
|
-
eventTag: r.event._tag
|
|
228
|
-
})));
|
|
229
|
-
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
230
|
-
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
338
|
+
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
339
|
+
yield* shutdown(ActorExit.Final(finalState));
|
|
231
340
|
return;
|
|
232
341
|
}
|
|
233
342
|
if (drain.stateChanged) drainTriggered = true;
|
|
@@ -236,12 +345,12 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
|
|
|
236
345
|
}
|
|
237
346
|
});
|
|
238
347
|
/** Settle all pending Deferreds in the postpone buffer on shutdown. */
|
|
239
|
-
const settlePostponed = (postponed, actorId) => {
|
|
240
|
-
for (const entry of postponed) if (entry._tag === "ask")
|
|
348
|
+
const settlePostponed = (postponed, actorId, forkFn) => {
|
|
349
|
+
for (const entry of postponed) if (entry._tag === "ask") forkFn(Deferred.fail(entry.reply, new NoReplyError({
|
|
241
350
|
actorId,
|
|
242
351
|
eventTag: entry.event._tag
|
|
243
352
|
})));
|
|
244
|
-
else if (entry._tag === "sendWait")
|
|
353
|
+
else if (entry._tag === "sendWait") forkFn(Deferred.succeed(entry.done, void 0));
|
|
245
354
|
postponed.length = 0;
|
|
246
355
|
};
|
|
247
356
|
//#endregion
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EffectsDef, GuardsDef, MachineContext } from "../slot.js";
|
|
2
|
-
import {
|
|
2
|
+
import { Machine, MachineRef, SpawnEffect, Transition } from "../machine.js";
|
|
3
3
|
import { ActorSystem } from "../actor.js";
|
|
4
4
|
import { Cause, Effect, Scope } from "effect";
|
|
5
5
|
|
|
@@ -68,6 +68,8 @@ interface ProcessEventHooks<S, E> {
|
|
|
68
68
|
readonly onTransition?: (from: S, to: S, event: E) => Effect.Effect<void>;
|
|
69
69
|
/** Called when a transition handler or spawn effect fails with a defect */
|
|
70
70
|
readonly onError?: (info: ProcessEventError<S, E>) => Effect.Effect<void>;
|
|
71
|
+
/** Called when a forked spawn fiber defects — signals the runtime to set exitDeferred */
|
|
72
|
+
readonly onSpawnDefect?: (cause: Cause.Cause<unknown>) => Effect.Effect<void>;
|
|
71
73
|
}
|
|
72
74
|
/**
|
|
73
75
|
* Error info for inspection hooks.
|
|
@@ -148,7 +150,7 @@ declare const runSpawnEffects: <S extends {
|
|
|
148
150
|
readonly _tag: string;
|
|
149
151
|
}, E extends {
|
|
150
152
|
readonly _tag: string;
|
|
151
|
-
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, state: S, event: E, self: MachineRef<E>, stateScope: Scope.Closeable, system: ActorSystem, actorId: string, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never, Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
|
|
153
|
+
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, state: S, event: E, self: MachineRef<E>, stateScope: Scope.Closeable, system: ActorSystem, actorId: string, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined, onSpawnDefect?: ((cause: Cause.Cause<unknown>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never, Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
|
|
152
154
|
/**
|
|
153
155
|
* Resolve which transition should fire for a given state and event.
|
|
154
156
|
* Uses indexed O(1) lookup. First matching transition wins.
|
|
@@ -166,14 +168,13 @@ declare const invalidateIndex: (machine: object) => void;
|
|
|
166
168
|
* Find all transitions matching a state/event pair.
|
|
167
169
|
* Returns empty array if no matches.
|
|
168
170
|
*
|
|
169
|
-
* Accepts both `Machine` and `BuiltMachine`.
|
|
170
171
|
* O(1) lookup after first access (index is lazily built).
|
|
171
172
|
*/
|
|
172
173
|
declare const findTransitions: <S extends {
|
|
173
174
|
readonly _tag: string;
|
|
174
175
|
}, E extends {
|
|
175
176
|
readonly _tag: string;
|
|
176
|
-
}, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(
|
|
177
|
+
}, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(machine: Machine<S, E, R, any, any, GD, EFD>, stateTag: string, eventTag: string) => ReadonlyArray<Transition<S, E, GD, EFD, R>>;
|
|
177
178
|
/**
|
|
178
179
|
* Find all spawn effects for a state.
|
|
179
180
|
* Returns empty array if no matches.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { INTERNAL_ENTER_EVENT, isDeferReplyResult, isEffect, isReplyResult } from "./utils.js";
|
|
2
|
-
import { BuiltMachine } from "../machine.js";
|
|
3
2
|
import { Cause, Effect, Exit, Scope } from "effect";
|
|
4
3
|
//#region src/internal/transition.ts
|
|
5
4
|
/**
|
|
@@ -139,7 +138,7 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
|
|
|
139
138
|
stateScopeRef.current = yield* Scope.make();
|
|
140
139
|
if (hooks?.onTransition !== void 0) yield* hooks.onTransition(currentState, newState, event);
|
|
141
140
|
if (hooks?.onSpawnEffect !== void 0) yield* hooks.onSpawnEffect(newState);
|
|
142
|
-
yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, system, actorId, hooks?.onError);
|
|
141
|
+
yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, system, actorId, hooks?.onError, hooks?.onSpawnDefect);
|
|
143
142
|
}
|
|
144
143
|
return {
|
|
145
144
|
newState,
|
|
@@ -158,7 +157,7 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
|
|
|
158
157
|
*
|
|
159
158
|
* @internal
|
|
160
159
|
*/
|
|
161
|
-
const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, system, actorId, onError) {
|
|
160
|
+
const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, system, actorId, onError, onSpawnDefect) {
|
|
162
161
|
const spawnEffects = findSpawnEffects(machine, state._tag);
|
|
163
162
|
const ctx = {
|
|
164
163
|
actorId,
|
|
@@ -169,6 +168,7 @@ const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (m
|
|
|
169
168
|
};
|
|
170
169
|
const { effects: effectSlots } = machine._slots;
|
|
171
170
|
const reportError = onError;
|
|
171
|
+
const defectSignal = onSpawnDefect;
|
|
172
172
|
for (const spawnEffect of spawnEffects) {
|
|
173
173
|
const effect = spawnEffect.handler({
|
|
174
174
|
actorId,
|
|
@@ -179,13 +179,14 @@ const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (m
|
|
|
179
179
|
system
|
|
180
180
|
}).pipe(Effect.provideService(machine.Context, ctx), Effect.catchCause((cause) => {
|
|
181
181
|
if (Cause.hasInterruptsOnly(cause)) return Effect.interrupt;
|
|
182
|
-
|
|
183
|
-
return reportError({
|
|
182
|
+
const report = reportError !== void 0 ? reportError({
|
|
184
183
|
phase: "spawn",
|
|
185
184
|
state,
|
|
186
185
|
event,
|
|
187
186
|
cause
|
|
188
|
-
})
|
|
187
|
+
}) : Effect.void;
|
|
188
|
+
const signal = defectSignal !== void 0 ? defectSignal(cause) : Effect.void;
|
|
189
|
+
return report.pipe(Effect.andThen(signal), Effect.andThen(Effect.failCause(cause).pipe(Effect.orDie)));
|
|
189
190
|
}));
|
|
190
191
|
yield* Effect.forkScoped(effect).pipe(Effect.provideService(Scope.Scope, stateScope));
|
|
191
192
|
}
|
|
@@ -258,11 +259,10 @@ const getIndex = (machine) => {
|
|
|
258
259
|
* Find all transitions matching a state/event pair.
|
|
259
260
|
* Returns empty array if no matches.
|
|
260
261
|
*
|
|
261
|
-
* Accepts both `Machine` and `BuiltMachine`.
|
|
262
262
|
* O(1) lookup after first access (index is lazily built).
|
|
263
263
|
*/
|
|
264
|
-
const findTransitions = (
|
|
265
|
-
const index = getIndex(
|
|
264
|
+
const findTransitions = (machine, stateTag, eventTag) => {
|
|
265
|
+
const index = getIndex(machine);
|
|
266
266
|
const specific = index.transitions.get(stateTag)?.get(eventTag) ?? [];
|
|
267
267
|
if (specific.length > 0) return specific;
|
|
268
268
|
return index.transitions.get("*")?.get(eventTag) ?? [];
|
package/dist/machine.d.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { EffectHandlers, EffectSlots, EffectsDef, EffectsSchema, GuardHandlers, GuardSlots, GuardsDef, GuardsSchema, MachineContext } from "./slot.js";
|
|
2
1
|
import { DeferReplyResult, ReplyResult, TransitionResult } from "./internal/utils.js";
|
|
3
2
|
import { BrandedEvent, BrandedState, ExtractReply, TaggedOrConstructor } from "./internal/brands.js";
|
|
4
3
|
import { MachineEventSchema, MachineStateSchema, VariantsUnion } from "./schema.js";
|
|
5
4
|
import { DuplicateActorError } from "./errors.js";
|
|
5
|
+
import { EffectHandlers, EffectSlots, EffectsDef, EffectsSchema, GuardHandlers, GuardSlots, GuardsDef, GuardsSchema, MachineContext } from "./slot.js";
|
|
6
|
+
import { Supervision } from "./supervision.js";
|
|
6
7
|
import { findTransitions } from "./internal/transition.js";
|
|
7
8
|
import { ActorRef, ActorSystem } from "./actor.js";
|
|
8
9
|
import { Cause, Duration, Effect, Schema, Scope, ServiceMap } from "effect";
|
|
9
10
|
|
|
10
11
|
//#region src/machine.d.ts
|
|
11
12
|
declare namespace machine_d_exports {
|
|
12
|
-
export { BackgroundEffect,
|
|
13
|
+
export { BackgroundEffect, DeferReplyResult, HandlerContext, Machine, MachineRef, MakeConfig, ProvideHandlers, ReplyResult, SlotContext, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, make, materializeMachine, replay, reply, spawn };
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Self reference for sending events back to the machine
|
|
@@ -22,7 +23,7 @@ interface MachineRef<Event> {
|
|
|
22
23
|
readonly _tag: string;
|
|
23
24
|
}, E2 extends {
|
|
24
25
|
readonly _tag: string;
|
|
25
|
-
}, R2>(id: string, machine:
|
|
26
|
+
}, R2>(id: string, machine: Machine<S2, E2, R2, any, any, any, any>) => Effect.Effect<ActorRef<S2, E2>, DuplicateActorError, R2>;
|
|
26
27
|
/**
|
|
27
28
|
* Settle a deferred reply from a spawn handler.
|
|
28
29
|
* Only usable when the transition handler returned `Machine.deferReply(state)`.
|
|
@@ -99,9 +100,6 @@ interface TimeoutConfig<State, Event> {
|
|
|
99
100
|
/** Event to send when the timer fires. Static or derived from current state. */
|
|
100
101
|
readonly event: Event | ((state: State) => Event);
|
|
101
102
|
}
|
|
102
|
-
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
103
|
-
type IsUnknown<T> = unknown extends T ? ([T] extends [unknown] ? true : false) : false;
|
|
104
|
-
type NormalizeR<T> = IsAny<T> extends true ? T : IsUnknown<T> extends true ? never : T;
|
|
105
103
|
interface MakeConfig<SD extends Record<string, Schema.Struct.Fields>, ED extends Record<string, Schema.Struct.Fields>, S extends BrandedState, E extends BrandedEvent, GD extends GuardsDef, EFD extends EffectsDef> {
|
|
106
104
|
readonly state: MachineStateSchema<SD> & {
|
|
107
105
|
Type: S;
|
|
@@ -121,22 +119,14 @@ type HasEffectKeys<EFD extends EffectsDef> = [keyof EFD] extends [never] ? false
|
|
|
121
119
|
type SlotContext<State, Event> = MachineContext<State, Event, MachineRef<Event>>;
|
|
122
120
|
/** Combined handlers for build() - guards and effects only */
|
|
123
121
|
type ProvideHandlers<State, Event, GD extends GuardsDef, EFD extends EffectsDef, R> = (HasGuardKeys<GD> extends true ? GuardHandlers<GD, SlotContext<State, Event>, R> : object) & (HasEffectKeys<EFD> extends true ? EffectHandlers<EFD, SlotContext<State, Event>, R> : object);
|
|
124
|
-
/** Whether the machine has any guard or effect slots */
|
|
125
|
-
type HasSlots<GD extends GuardsDef, EFD extends EffectsDef> = HasGuardKeys<GD> extends true ? true : HasEffectKeys<EFD>;
|
|
126
122
|
/**
|
|
127
|
-
*
|
|
123
|
+
* Bind slot handlers to a machine, returning a fresh copy with handlers installed.
|
|
124
|
+
* If no handlers provided and machine has no slots, returns the machine as-is.
|
|
125
|
+
* Validates that all required slots are provided and no extra slots are given.
|
|
128
126
|
*
|
|
129
|
-
*
|
|
130
|
-
* accepted by `Machine.spawn` and `ActorSystem.spawn` (regular overload).
|
|
131
|
-
* Testing utilities (`simulate`, `createTestHarness`, etc.) still accept `Machine`.
|
|
127
|
+
* @internal — used by spawn, replay, simulate, test harness, entity-machine
|
|
132
128
|
*/
|
|
133
|
-
declare
|
|
134
|
-
/** @internal */
|
|
135
|
-
readonly _inner: Machine<State, Event, R, any, any, any, any>;
|
|
136
|
-
/** @internal */
|
|
137
|
-
constructor(machine: Machine<State, Event, R, any, any, any, any>);
|
|
138
|
-
get initial(): State;
|
|
139
|
-
}
|
|
129
|
+
declare const materializeMachine: <S, E, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, any, any, GD, EFD>, handlers?: Record<string, any>) => Machine<S, E, never, any, any, GD, EFD>;
|
|
140
130
|
/**
|
|
141
131
|
* Machine definition with fluent builder API.
|
|
142
132
|
*
|
|
@@ -312,13 +302,6 @@ declare class Machine<State, Event, R = never, _SD extends Record<string, Schema
|
|
|
312
302
|
*/
|
|
313
303
|
postpone<NS extends VariantsUnion<_SD> & BrandedState>(state: TaggedOrConstructor<NS>, events: TaggedOrConstructor<VariantsUnion<_ED> & BrandedEvent> | ReadonlyArray<TaggedOrConstructor<VariantsUnion<_ED> & BrandedEvent>>): Machine<State, Event, R, _SD, _ED, GD, EFD>;
|
|
314
304
|
final<NS extends VariantsUnion<_SD> & BrandedState>(state: TaggedOrConstructor<NS>): Machine<State, Event, R, _SD, _ED, GD, EFD>;
|
|
315
|
-
/**
|
|
316
|
-
* Finalize the machine. Returns a `BuiltMachine` — the only type accepted by `Machine.spawn`.
|
|
317
|
-
*
|
|
318
|
-
* - Machines with slots: pass implementations as the first argument.
|
|
319
|
-
* - Machines without slots: call with no arguments.
|
|
320
|
-
*/
|
|
321
|
-
build<R2 = never>(...args: HasSlots<GD, EFD> extends true ? [handlers: ProvideHandlers<State, Event, GD, EFD, R2>] : [handlers?: ProvideHandlers<State, Event, GD, EFD, R2>]): BuiltMachine<State, Event, R | NormalizeR<R2>>;
|
|
322
305
|
static make<SD extends Record<string, Schema.Struct.Fields>, ED extends Record<string, Schema.Struct.Fields>, S extends BrandedState, E extends BrandedEvent, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(config: MakeConfig<SD, ED, S, E, GD, EFD>): Machine<S, E, never, SD, ED, GD, EFD>;
|
|
323
306
|
}
|
|
324
307
|
declare class TransitionScope<State, Event, R, _SD extends Record<string, Schema.Struct.Fields>, _ED extends Record<string, Schema.Struct.Fields>, GD extends GuardsDef, EFD extends EffectsDef, SelectedState extends VariantsUnion<_SD> & BrandedState> {
|
|
@@ -330,34 +313,72 @@ declare class TransitionScope<State, Event, R, _SD extends Record<string, Schema
|
|
|
330
313
|
}
|
|
331
314
|
declare const make: typeof Machine.make;
|
|
332
315
|
/**
|
|
333
|
-
* Spawn an actor
|
|
316
|
+
* Spawn an actor directly without ActorSystem ceremony.
|
|
317
|
+
* Accepts a `Machine` directly. For slotful machines, pass `{ slots }` in options.
|
|
318
|
+
*
|
|
319
|
+
* **Single actor, no registry.** Caller manages lifetime via `actor.stop`.
|
|
320
|
+
* If a `Scope` exists in context, cleanup attaches automatically on scope close.
|
|
334
321
|
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
* - `hydrate` — restore from a previously-saved state snapshot.
|
|
338
|
-
* The actor starts in the hydrated state and re-runs spawn effects
|
|
339
|
-
* for that state (timers, scoped resources, etc.). Transition history
|
|
340
|
-
* is not replayed — only the current state's entry effects run.
|
|
322
|
+
* For registry, lookup by ID, persistence, or multi-actor coordination,
|
|
323
|
+
* use `ActorSystemService` / `system.spawn` instead.
|
|
341
324
|
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* // Fire-and-forget — caller manages lifetime
|
|
328
|
+
* const actor = yield* Machine.spawn(machine.build());
|
|
329
|
+
* yield* actor.send(Event.Start);
|
|
330
|
+
* yield* actor.awaitFinal;
|
|
331
|
+
* yield* actor.stop;
|
|
332
|
+
*
|
|
333
|
+
* // Scope-aware — auto-cleans up on scope close
|
|
334
|
+
* yield* Effect.scoped(Effect.gen(function* () {
|
|
335
|
+
* const actor = yield* Machine.spawn(machine.build());
|
|
336
|
+
* yield* actor.send(Event.Start);
|
|
337
|
+
* // actor.stop called automatically when scope closes
|
|
338
|
+
* }));
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
341
|
+
type AnyMachine<S, E, R> = Machine<S, E, R, any, any, any, any>;
|
|
342
|
+
/**
|
|
343
|
+
* Spawn an actor from a machine.
|
|
344
|
+
*
|
|
345
|
+
* For machines with slots, pass implementations via `{ slots: { ... } }`.
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* // No slots
|
|
350
|
+
* const actor = yield* Machine.spawn(machine);
|
|
351
|
+
*
|
|
352
|
+
* // With slots
|
|
353
|
+
* const actor = yield* Machine.spawn(machine, {
|
|
354
|
+
* slots: { canRetry: ({ max }, { state }) => state.attempts < max },
|
|
355
|
+
* });
|
|
356
|
+
*
|
|
357
|
+
* // With hydration
|
|
358
|
+
* const actor = yield* Machine.spawn(machine, { hydrate: savedState });
|
|
359
|
+
* ```
|
|
344
360
|
*/
|
|
345
361
|
declare const spawn: <S extends {
|
|
346
362
|
readonly _tag: string;
|
|
347
363
|
}, E extends {
|
|
348
364
|
readonly _tag: string;
|
|
349
|
-
}, R>(machine:
|
|
365
|
+
}, R>(machine: AnyMachine<S, E, R>, options?: string | {
|
|
350
366
|
id?: string;
|
|
351
367
|
hydrate?: S;
|
|
368
|
+
slots?: Record<string, any>;
|
|
369
|
+
supervision?: Supervision.Policy;
|
|
352
370
|
}) => Effect.Effect<ActorRef<S, E>, never, R>;
|
|
353
|
-
declare const replay:
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
371
|
+
declare const replay: {
|
|
372
|
+
<S extends {
|
|
373
|
+
readonly _tag: string;
|
|
374
|
+
}, E extends {
|
|
375
|
+
readonly _tag: string;
|
|
376
|
+
}, R>(machine: AnyMachine<S, E, R>, events: ReadonlyArray<E>, options?: {
|
|
377
|
+
from?: S;
|
|
378
|
+
slots?: Record<string, any>;
|
|
379
|
+
}): Effect.Effect<S, never, R>;
|
|
380
|
+
};
|
|
360
381
|
declare const reply: <State, Reply>(state: State, reply: Reply) => ReplyResult<State, Reply>;
|
|
361
382
|
declare const deferReply: <State>(state: State) => DeferReplyResult<State>;
|
|
362
383
|
//#endregion
|
|
363
|
-
export { BackgroundEffect,
|
|
384
|
+
export { BackgroundEffect, type DeferReplyResult, HandlerContext, Machine, MachineRef, MakeConfig, ProvideHandlers, type ReplyResult, SlotContext, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, machine_d_exports, make, materializeMachine, replay, reply, spawn };
|