effect-machine 0.17.0 → 0.18.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.
Files changed (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -54
  3. package/dist/actor.d.ts +8 -45
  4. package/dist/actor.js +157 -231
  5. package/dist/cluster/adapters/in-memory.d.ts +0 -1
  6. package/dist/cluster/adapters/in-memory.js +10 -5
  7. package/dist/cluster/entity-actor-ref.d.ts +2 -3
  8. package/dist/cluster/entity-actor-ref.js +14 -17
  9. package/dist/cluster/entity-machine.d.ts +2 -8
  10. package/dist/cluster/entity-machine.js +57 -34
  11. package/dist/cluster/index.js +2 -2
  12. package/dist/cluster/persistence.d.ts +0 -1
  13. package/dist/cluster/to-entity.d.ts +19 -21
  14. package/dist/cluster/to-entity.js +18 -19
  15. package/dist/errors.d.ts +11 -37
  16. package/dist/errors.js +12 -30
  17. package/dist/index.d.ts +4 -5
  18. package/dist/index.js +4 -5
  19. package/dist/inspection.d.ts +0 -1
  20. package/dist/inspection.js +24 -13
  21. package/dist/internal/brands.d.ts +0 -1
  22. package/dist/internal/event-advancement.d.ts +50 -0
  23. package/dist/internal/event-advancement.js +79 -0
  24. package/dist/internal/inspection.d.ts +5 -9
  25. package/dist/internal/inspection.js +31 -10
  26. package/dist/internal/machine-definition.d.ts +16 -0
  27. package/dist/internal/runtime.d.ts +1 -161
  28. package/dist/internal/runtime.js +191 -150
  29. package/dist/internal/transition.d.ts +4 -135
  30. package/dist/internal/transition.js +82 -157
  31. package/dist/internal/utils.d.ts +3 -40
  32. package/dist/internal/utils.js +2 -2
  33. package/dist/machine.d.ts +47 -137
  34. package/dist/machine.js +147 -215
  35. package/dist/schema.d.ts +42 -21
  36. package/dist/schema.js +28 -18
  37. package/dist/supervision.d.ts +1 -24
  38. package/dist/supervision.js +2 -3
  39. package/dist/testing.d.ts +15 -29
  40. package/dist/testing.js +76 -95
  41. package/package.json +19 -38
  42. package/dist/slot.d.ts +0 -159
  43. package/dist/slot.js +0 -165
  44. package/v3/dist/_virtual/_rolldown/runtime.js +0 -13
  45. package/v3/dist/actor.d.ts +0 -250
  46. package/v3/dist/actor.js +0 -577
  47. package/v3/dist/cluster/adapters/in-memory.d.ts +0 -15
  48. package/v3/dist/cluster/adapters/in-memory.js +0 -62
  49. package/v3/dist/cluster/entity-actor-ref.d.ts +0 -49
  50. package/v3/dist/cluster/entity-actor-ref.js +0 -19
  51. package/v3/dist/cluster/entity-machine.d.ts +0 -74
  52. package/v3/dist/cluster/entity-machine.js +0 -166
  53. package/v3/dist/cluster/index.d.ts +0 -6
  54. package/v3/dist/cluster/index.js +0 -6
  55. package/v3/dist/cluster/persistence.d.ts +0 -48
  56. package/v3/dist/cluster/persistence.js +0 -14
  57. package/v3/dist/cluster/to-entity.d.ts +0 -69
  58. package/v3/dist/cluster/to-entity.js +0 -59
  59. package/v3/dist/errors.d.ts +0 -95
  60. package/v3/dist/errors.js +0 -54
  61. package/v3/dist/index.d.ts +0 -11
  62. package/v3/dist/index.js +0 -9
  63. package/v3/dist/inspection.d.ts +0 -151
  64. package/v3/dist/inspection.js +0 -128
  65. package/v3/dist/internal/brands.d.ts +0 -50
  66. package/v3/dist/internal/inspection.d.ts +0 -11
  67. package/v3/dist/internal/inspection.js +0 -20
  68. package/v3/dist/internal/runtime.d.ts +0 -161
  69. package/v3/dist/internal/runtime.js +0 -360
  70. package/v3/dist/internal/transition.d.ts +0 -190
  71. package/v3/dist/internal/transition.js +0 -278
  72. package/v3/dist/internal/utils.d.ts +0 -101
  73. package/v3/dist/internal/utils.js +0 -75
  74. package/v3/dist/machine.d.ts +0 -398
  75. package/v3/dist/machine.js +0 -487
  76. package/v3/dist/schema.d.ts +0 -174
  77. package/v3/dist/schema.js +0 -206
  78. package/v3/dist/slot.d.ts +0 -158
  79. package/v3/dist/slot.js +0 -165
  80. package/v3/dist/supervision.d.ts +0 -97
  81. package/v3/dist/supervision.js +0 -42
  82. package/v3/dist/testing.d.ts +0 -151
  83. package/v3/dist/testing.js +0 -189
  84. /package/{v3/dist/internal/brands.js → dist/internal/machine-definition.js} +0 -0
@@ -1,360 +0,0 @@
1
- import { INTERNAL_INIT_EVENT } from "./utils.js";
2
- import { NoReplyError } from "../errors.js";
3
- import { processEventCore, runSpawnEffects, shouldPostpone } from "./transition.js";
4
- import { ActorExit } from "../supervision.js";
5
- import { ActorSystem } from "../actor.js";
6
- import { Cause, Deferred, Effect, Exit, Fiber, Queue, Ref, Runtime, Schema, Scope, SubscriptionRef } from "effect";
7
- //#region src/internal/runtime.ts
8
- /**
9
- * Shared runtime kernel for machine event processing.
10
- *
11
- * Provides a single-queue event loop with:
12
- * - Sequential event processing (no split-mailbox race)
13
- * - Postpone buffer with drain-on-state-change (gen_statem)
14
- * - Background effect lifecycle (under actorScope fault boundary)
15
- * - Spawn effect lifecycle (per-state scope)
16
- * - Final state detection → stop
17
- * - Reply settlement (call/ask Deferreds)
18
- * - Reply schema validation
19
- * - Lifecycle hooks for actor-specific concerns (inspection, listeners, etc.)
20
- * - ActorExit with exit reason (Final/Stopped/Defect) via exitDeferred
21
- *
22
- * Used by entity-machine and local actor (actor.ts delegates here).
23
- *
24
- * @internal
25
- */
26
- /**
27
- * Create a runtime for a machine. Returns a handle for sending events
28
- * and querying state. The runtime owns:
29
- * - Event loop fiber
30
- * - Postpone buffer
31
- * - Background effects (under actorScope)
32
- * - State scope (spawn effects)
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.
38
- *
39
- * @internal
40
- */
41
- const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (machine, system, config) {
42
- const { actorId, hooks, lifecycle } = config;
43
- const rt = yield* Effect.runtime();
44
- const fork = Runtime.runFork(rt);
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();
50
- const deferredReplyRef = { current: void 0 };
51
- const selfSend = Effect.fn("effect-machine.runtime.self.send")(function* (event) {
52
- if (!(yield* Ref.get(stoppedRef))) yield* Queue.offer(eventQueue, {
53
- _tag: "send",
54
- event
55
- });
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;
60
- const self = {
61
- send: selfSend,
62
- cast: selfSend,
63
- spawn: onChildSpawned !== void 0 ? (childId, childMachine) => defaultSpawn(childId, childMachine).pipe(Effect.tap((child) => onChildSpawned(childId, child))) : defaultSpawn,
64
- reply: (value) => Effect.sync(() => {
65
- const deferred = deferredReplyRef.current;
66
- if (deferred !== void 0) {
67
- deferredReplyRef.current = void 0;
68
- fork(Deferred.succeed(deferred, value));
69
- return true;
70
- }
71
- return false;
72
- })
73
- };
74
- const stateScopeRef = { current: yield* Scope.make() };
75
- const initEvent = { _tag: INTERNAL_INIT_EVENT };
76
- const ctx = {
77
- actorId,
78
- state: machine.initial,
79
- event: initEvent,
80
- self,
81
- system
82
- };
83
- const slots = machine._slots;
84
- const loopFiberRef = { current: void 0 };
85
- /** Set the exit deferred exactly once. */
86
- const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
87
- const startDeferred = yield* Deferred.make();
88
- const startedRef = yield* Ref.make(false);
89
- const start = Effect.gen(function* () {
90
- if (yield* Ref.getAndSet(startedRef, true)) {
91
- yield* Deferred.await(startDeferred);
92
- return;
93
- }
94
- const backgroundFibers = [];
95
- for (const bg of machine.backgroundEffects) {
96
- const fiber = yield* bg.handler({
97
- actorId,
98
- state: machine.initial,
99
- event: initEvent,
100
- self,
101
- slots,
102
- system
103
- }).pipe(Effect.provideService(machine.Context, ctx), Effect.forkIn(actorScope));
104
- backgroundFibers.push(fiber);
105
- }
106
- if (lifecycle?.onInitialSpawnEffects !== void 0) yield* lifecycle.onInitialSpawnEffects(machine.initial);
107
- 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);
108
- yield* runSpawnEffects(machine, machine.initial, initEvent, self, stateScopeRef.current, system, actorId, hooks?.onError, initialSpawnDefectSignal).pipe(Effect.catchAllCause((cause) => Effect.gen(function* () {
109
- yield* Ref.set(stoppedRef, true);
110
- yield* Scope.close(stateScopeRef.current, Exit.void);
111
- yield* Scope.close(actorScope, Exit.void);
112
- yield* Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn"));
113
- return yield* Effect.failCause(cause);
114
- })));
115
- if (machine.finalStates.has(machine.initial._tag)) {
116
- if (lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(machine.initial);
117
- yield* Ref.set(stoppedRef, true);
118
- yield* Scope.close(stateScopeRef.current, Exit.void);
119
- yield* Scope.close(actorScope, Exit.void);
120
- yield* setExit(ActorExit.Final(machine.initial));
121
- yield* Deferred.succeed(startDeferred, void 0);
122
- return;
123
- }
124
- const augmentedHooks = {
125
- ...hooks,
126
- 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)
127
- };
128
- const loopFiber = yield* Effect.forkDaemon(runtimeEventLoop(machine, stateRef, eventQueue, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, augmentedHooks, deferredReplyRef, lifecycle, config.wrapProcess, fork));
129
- loopFiberRef.current = loopFiber;
130
- if (backgroundFibers.length > 0) yield* Effect.raceAll(backgroundFibers.map((fiber) => Fiber.await(fiber).pipe(Effect.flatMap((exit) => {
131
- if (exit._tag === "Failure" && !Cause.isInterruptedOnly(exit.cause)) return setExit(ActorExit.Defect(exit.cause, "background")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Fiber.interrupt(loopFiber)));
132
- return Effect.never;
133
- })))).pipe(Effect.forkIn(actorScope));
134
- yield* Effect.forkDaemon(Effect.gen(function* () {
135
- const loopExit = yield* Fiber.await(loopFiber);
136
- if (loopExit._tag === "Success") yield* Scope.close(actorScope, Exit.void);
137
- else yield* Scope.close(actorScope, loopExit);
138
- }));
139
- yield* Deferred.succeed(startDeferred, void 0);
140
- }).pipe(Effect.catchAllCause((cause) => Deferred.failCause(startDeferred, cause).pipe(Effect.andThen(Effect.failCause(cause)))));
141
- const stop = Effect.gen(function* () {
142
- if (yield* Ref.get(stoppedRef)) return;
143
- if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
144
- yield* Ref.set(stoppedRef, true);
145
- const loopFiber = loopFiberRef.current;
146
- if (loopFiber !== void 0) yield* Fiber.interrupt(loopFiber);
147
- yield* Scope.close(stateScopeRef.current, Exit.void);
148
- yield* Scope.close(actorScope, Exit.void);
149
- yield* setExit(ActorExit.Stopped);
150
- }).pipe(Effect.asVoid);
151
- if (config.skipFinalizer !== true) yield* Effect.addFinalizer(() => stop);
152
- return {
153
- ...makeHandle(stateRef, stoppedRef, eventQueue, exitDeferred, actorScope),
154
- stop,
155
- start
156
- };
157
- });
158
- /**
159
- * Build the runtime handle (send/ask/getState/isStopped).
160
- * Shared between initial-final and normal paths.
161
- */
162
- const makeHandle = (stateRef, stoppedRef, eventQueue, exitDeferred, actorScope) => ({
163
- send: (event) => Effect.gen(function* () {
164
- if (!(yield* Ref.get(stoppedRef))) yield* Queue.offer(eventQueue, {
165
- _tag: "send",
166
- event
167
- });
168
- }),
169
- sendWait: (event) => Effect.gen(function* () {
170
- if (!(yield* Ref.get(stoppedRef))) {
171
- const done = yield* Deferred.make();
172
- yield* Queue.offer(eventQueue, {
173
- _tag: "sendWait",
174
- event,
175
- done
176
- });
177
- yield* Deferred.await(done);
178
- }
179
- }),
180
- ask: (event) => Effect.gen(function* () {
181
- if (yield* Ref.get(stoppedRef)) return yield* new NoReplyError({
182
- actorId: "stopped",
183
- eventTag: event._tag
184
- });
185
- const reply = yield* Deferred.make();
186
- yield* Queue.offer(eventQueue, {
187
- _tag: "ask",
188
- event,
189
- reply
190
- });
191
- return yield* Deferred.await(reply);
192
- }),
193
- getState: SubscriptionRef.get(stateRef),
194
- stateRef,
195
- isStopped: Ref.get(stoppedRef),
196
- stop: Effect.void,
197
- start: Effect.void,
198
- _queue: eventQueue,
199
- _stoppedRef: stoppedRef,
200
- exitDeferred,
201
- actorScope
202
- });
203
- const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, hooks, deferredReplyRef, lifecycle, wrapProcess, fork) {
204
- const forkEffect = fork ?? Effect.runFork;
205
- /** Set the exit deferred exactly once. */
206
- const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
207
- const postponed = [];
208
- const hasPostponeRules = machine.postponeRules.length > 0;
209
- const processQueued = Effect.fn("effect-machine.runtime.processQueued")(function* (queued) {
210
- const event = queued.event;
211
- const currentState = yield* SubscriptionRef.get(stateRef);
212
- if (hasPostponeRules && shouldPostpone(machine, currentState._tag, event._tag)) {
213
- if (queued._tag === "call") {
214
- const postponedResult = {
215
- newState: currentState,
216
- previousState: currentState,
217
- transitioned: false,
218
- lifecycleRan: false,
219
- isFinal: false,
220
- hasReply: false,
221
- deferReply: false,
222
- reply: void 0,
223
- postponed: true
224
- };
225
- yield* Deferred.succeed(queued.reply, postponedResult);
226
- }
227
- if (queued._tag === "sendWait") yield* Deferred.succeed(queued.done, void 0);
228
- postponed.push({
229
- _tag: "send",
230
- event
231
- });
232
- return {
233
- shouldStop: false,
234
- stateChanged: false,
235
- result: {
236
- newState: currentState,
237
- previousState: currentState,
238
- transitioned: false,
239
- lifecycleRan: false,
240
- isFinal: false,
241
- hasReply: false,
242
- deferReply: false,
243
- reply: void 0,
244
- postponed: true
245
- }
246
- };
247
- }
248
- if (lifecycle?.onEvent !== void 0) yield* lifecycle.onEvent(currentState, event);
249
- const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, actorId, hooks);
250
- if (result.transitioned) yield* SubscriptionRef.set(stateRef, result.newState);
251
- if (lifecycle?.onStateChange !== void 0 && result.transitioned) yield* lifecycle.onStateChange(result, event);
252
- switch (queued._tag) {
253
- case "call":
254
- yield* Deferred.succeed(queued.reply, result);
255
- break;
256
- case "sendWait":
257
- yield* Deferred.succeed(queued.done, void 0);
258
- break;
259
- case "ask":
260
- if (result.hasReply) {
261
- const replySchema = machine._replySchemas?.get(event._tag);
262
- if (replySchema !== void 0) {
263
- const decoded = yield* Schema.decodeUnknown(replySchema)(result.reply).pipe(Effect.catchAll((decodeError) => Effect.gen(function* () {
264
- yield* Deferred.die(queued.reply, decodeError);
265
- return yield* Effect.die(decodeError);
266
- })));
267
- yield* Deferred.succeed(queued.reply, decoded);
268
- } else yield* Deferred.succeed(queued.reply, result.reply);
269
- } else if (result.deferReply && deferredReplyRef !== void 0) deferredReplyRef.current = queued.reply;
270
- else yield* Deferred.fail(queued.reply, new NoReplyError({
271
- actorId,
272
- eventTag: event._tag
273
- }));
274
- break;
275
- }
276
- if (lifecycle?.onProcessed !== void 0 && result.transitioned) yield* lifecycle.onProcessed(result, event);
277
- const shouldStop = result.isFinal && result.lifecycleRan;
278
- if (shouldStop && lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(result.newState);
279
- return {
280
- shouldStop,
281
- stateChanged: result.lifecycleRan,
282
- result
283
- };
284
- });
285
- const shutdown = (exitReason) => Effect.gen(function* () {
286
- yield* Ref.set(stoppedRef, true);
287
- if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
288
- settlePostponed(postponed, actorId, forkEffect);
289
- const remaining = yield* Queue.takeAll(eventQueue);
290
- for (const entry of remaining) if (entry._tag === "sendWait") forkEffect(Deferred.succeed(entry.done, void 0));
291
- else if (entry._tag === "ask") forkEffect(Deferred.fail(entry.reply, new NoReplyError({
292
- actorId,
293
- eventTag: entry.event._tag
294
- })));
295
- else if (entry._tag === "call") {
296
- const currentState = yield* SubscriptionRef.get(stateRef);
297
- forkEffect(Deferred.succeed(entry.reply, {
298
- newState: currentState,
299
- previousState: currentState,
300
- transitioned: false,
301
- lifecycleRan: false,
302
- isFinal: machine.finalStates.has(currentState._tag),
303
- hasReply: false,
304
- deferReply: false,
305
- reply: void 0,
306
- postponed: false
307
- }));
308
- }
309
- yield* Scope.close(stateScopeRef.current, Exit.void);
310
- yield* setExit(exitReason);
311
- });
312
- while (true) {
313
- const queued = yield* Queue.take(eventQueue);
314
- if (queued._tag === "drain") {
315
- yield* shutdown(ActorExit.Stopped);
316
- yield* Deferred.succeed(queued.done, void 0);
317
- return;
318
- }
319
- const eventQueued = queued;
320
- const processInner = processQueued(eventQueued);
321
- const { shouldStop, stateChanged } = yield* (wrapProcess !== void 0 ? Effect.gen(function* () {
322
- return yield* wrapProcess(yield* SubscriptionRef.get(stateRef), eventQueued.event, processInner);
323
- }) : processInner).pipe(Effect.catchAllCause((cause) => {
324
- if (queued._tag === "sendWait") forkEffect(Deferred.failCause(queued.done, cause));
325
- else if (queued._tag === "ask") forkEffect(Deferred.die(queued.reply, cause));
326
- else if (queued._tag === "call") forkEffect(Deferred.failCause(queued.reply, cause));
327
- return shutdown(ActorExit.Defect(cause, "transition")).pipe(Effect.andThen(Effect.failCause(cause)));
328
- }));
329
- if (shouldStop) {
330
- const finalState = yield* SubscriptionRef.get(stateRef);
331
- yield* shutdown(ActorExit.Final(finalState));
332
- return;
333
- }
334
- let drainTriggered = stateChanged;
335
- while (drainTriggered && postponed.length > 0) {
336
- drainTriggered = false;
337
- const drained = postponed.splice(0);
338
- for (const entry of drained) {
339
- const drain = yield* processQueued(entry);
340
- if (drain.shouldStop) {
341
- const finalState = yield* SubscriptionRef.get(stateRef);
342
- yield* shutdown(ActorExit.Final(finalState));
343
- return;
344
- }
345
- if (drain.stateChanged) drainTriggered = true;
346
- }
347
- }
348
- }
349
- });
350
- /** Settle all pending Deferreds in the postpone buffer on shutdown. */
351
- const settlePostponed = (postponed, actorId, forkFn) => {
352
- for (const entry of postponed) if (entry._tag === "ask") forkFn(Deferred.fail(entry.reply, new NoReplyError({
353
- actorId,
354
- eventTag: entry.event._tag
355
- })));
356
- else if (entry._tag === "sendWait") forkFn(Deferred.succeed(entry.done, void 0));
357
- postponed.length = 0;
358
- };
359
- //#endregion
360
- export { createRuntime };
@@ -1,190 +0,0 @@
1
- import { MachineContext, SlotsDef } from "../slot.js";
2
- import { Machine, MachineRef, SpawnEffect, Transition } from "../machine.js";
3
- import { ActorSystem } from "../actor.js";
4
- import { Cause, Effect, Scope } from "effect";
5
-
6
- //#region src/internal/transition.d.ts
7
- /**
8
- * Result of executing a transition.
9
- */
10
- interface TransitionExecutionResult<S> {
11
- /** New state after transition (or current state if no transition matched) */
12
- readonly newState: S;
13
- /** Whether a transition was executed */
14
- readonly transitioned: boolean;
15
- /** Whether reenter was specified on the transition */
16
- readonly reenter: boolean;
17
- }
18
- /**
19
- * Run a transition handler and return the new state.
20
- * Shared logic for executing handlers with proper context.
21
- *
22
- * Used by:
23
- * - executeTransition (actor event loop, testing)
24
- * - Machine.replay (event sourcing restore)
25
- *
26
- * @internal
27
- */
28
- declare const runTransitionHandler: <S extends {
29
- readonly _tag: string;
30
- }, E extends {
31
- readonly _tag: string;
32
- }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, transition: Transition<S, E, SD, R>, state: S, event: E, self: MachineRef<E>, system: ActorSystem, actorId: string) => Effect.Effect<{
33
- newState: S;
34
- hasReply: boolean;
35
- deferReply: boolean;
36
- reply: unknown;
37
- }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
38
- /**
39
- * Execute a transition for a given state and event.
40
- * Handles transition resolution, handler invocation, and guard/effect slot creation.
41
- *
42
- * Used by:
43
- * - processEvent in actor.ts (actual actor event loop)
44
- * - simulate in testing.ts (pure transition simulation)
45
- * - createTestHarness.send in testing.ts (step-by-step testing)
46
- *
47
- * @internal
48
- */
49
- declare const executeTransition: <S extends {
50
- readonly _tag: string;
51
- }, E extends {
52
- readonly _tag: string;
53
- }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, currentState: S, event: E, self: MachineRef<E>, system: ActorSystem, actorId: string) => Effect.Effect<{
54
- newState: S;
55
- transitioned: boolean;
56
- reenter: boolean;
57
- hasReply: boolean;
58
- deferReply: boolean;
59
- reply: unknown;
60
- }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
61
- /**
62
- * Optional hooks for event processing inspection/tracing.
63
- */
64
- interface ProcessEventHooks<S, E> {
65
- /** Called before running spawn effects */
66
- readonly onSpawnEffect?: (state: S) => Effect.Effect<void>;
67
- /** Called after transition completes */
68
- readonly onTransition?: (from: S, to: S, event: E) => Effect.Effect<void>;
69
- /** Called when a transition handler or spawn effect fails with a defect */
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>;
73
- }
74
- /**
75
- * Error info for inspection hooks.
76
- */
77
- interface ProcessEventError<S, E> {
78
- readonly phase: "transition" | "spawn";
79
- readonly state: S;
80
- readonly event: E;
81
- readonly cause: Cause.Cause<unknown>;
82
- }
83
- /**
84
- * Result of processing an event through the machine.
85
- */
86
- interface ProcessEventResult<S> {
87
- /** New state after processing */
88
- readonly newState: S;
89
- /** Previous state before processing */
90
- readonly previousState: S;
91
- /** Whether a transition occurred */
92
- readonly transitioned: boolean;
93
- /** Whether lifecycle effects ran (state change or reenter) */
94
- readonly lifecycleRan: boolean;
95
- /** Whether new state is final */
96
- readonly isFinal: boolean;
97
- /** Whether the handler provided a reply (structural, not value-based) */
98
- readonly hasReply: boolean;
99
- /** Whether the handler deferred the reply to a spawn handler (Machine.deferReply) */
100
- readonly deferReply: boolean;
101
- /** Domain reply value from handler (used by ask). Only meaningful when hasReply is true. */
102
- readonly reply?: unknown;
103
- /** Whether the event was postponed (buffered for retry after next state change) */
104
- readonly postponed: boolean;
105
- }
106
- /**
107
- * Check if an event should be postponed in the current state.
108
- * @internal
109
- */
110
- declare const shouldPostpone: <S extends {
111
- readonly _tag: string;
112
- }, E extends {
113
- readonly _tag: string;
114
- }, R>(machine: Machine<S, E, R, any, any, any>, stateTag: string, eventTag: string) => boolean;
115
- /**
116
- * Process a single event through the machine.
117
- *
118
- * Handles:
119
- * - Transition execution
120
- * - State scope lifecycle (close old, create new)
121
- * - Running spawn effects
122
- *
123
- * Optional hooks allow inspection/tracing without coupling to specific impl.
124
- *
125
- * @internal
126
- */
127
- declare const processEventCore: <S extends {
128
- readonly _tag: string;
129
- }, E extends {
130
- readonly _tag: string;
131
- }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, currentState: S, event: E, self: MachineRef<E>, stateScopeRef: {
132
- current: Scope.CloseableScope;
133
- }, system: ActorSystem, actorId: string, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
134
- newState: S;
135
- previousState: S;
136
- transitioned: boolean;
137
- lifecycleRan: boolean;
138
- isFinal: boolean;
139
- hasReply: boolean;
140
- deferReply: boolean;
141
- reply: unknown;
142
- postponed: boolean;
143
- }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
144
- /**
145
- * Run spawn effects for a state (forked into state scope, auto-cancelled on state exit).
146
- *
147
- * @internal
148
- */
149
- declare const runSpawnEffects: <S extends {
150
- readonly _tag: string;
151
- }, E extends {
152
- readonly _tag: string;
153
- }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, state: S, event: E, self: MachineRef<E>, stateScope: Scope.CloseableScope, 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>>;
154
- /**
155
- * Resolve which transition should fire for a given state and event.
156
- * Uses indexed O(1) lookup. First matching transition wins.
157
- */
158
- declare const resolveTransition: <S extends {
159
- readonly _tag: string;
160
- }, E extends {
161
- readonly _tag: string;
162
- }, R>(machine: Machine<S, E, R, any, any, any>, currentState: S, event: E) => (typeof machine.transitions)[number] | undefined;
163
- /**
164
- * Invalidate cached index for a machine (call after mutation).
165
- */
166
- declare const invalidateIndex: (machine: object) => void;
167
- /**
168
- * Find all transitions matching a state/event pair.
169
- * Returns empty array if no matches.
170
- *
171
- * O(1) lookup after first access (index is lazily built).
172
- */
173
- declare const findTransitions: <S extends {
174
- readonly _tag: string;
175
- }, E extends {
176
- readonly _tag: string;
177
- }, R, SD extends SlotsDef = Record<string, never>>(machine: Machine<S, E, R, any, any, SD>, stateTag: string, eventTag: string) => ReadonlyArray<Transition<S, E, SD, R>>;
178
- /**
179
- * Find all spawn effects for a state.
180
- * Returns empty array if no matches.
181
- *
182
- * O(1) lookup after first access (index is lazily built).
183
- */
184
- declare const findSpawnEffects: <S extends {
185
- readonly _tag: string;
186
- }, E extends {
187
- readonly _tag: string;
188
- }, R, SD extends SlotsDef = Record<string, never>>(machine: Machine<S, E, R, any, any, SD>, stateTag: string) => ReadonlyArray<SpawnEffect<S, E, SD, R>>;
189
- //#endregion
190
- export { ProcessEventError, ProcessEventHooks, ProcessEventResult, TransitionExecutionResult, executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler, shouldPostpone };