effect-machine 0.17.1 → 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.
- package/LICENSE +21 -0
- package/README.md +50 -54
- package/dist/actor.d.ts +8 -45
- package/dist/actor.js +157 -231
- package/dist/cluster/adapters/in-memory.d.ts +0 -1
- package/dist/cluster/adapters/in-memory.js +10 -5
- package/dist/cluster/entity-actor-ref.d.ts +2 -3
- package/dist/cluster/entity-actor-ref.js +14 -17
- package/dist/cluster/entity-machine.d.ts +2 -8
- package/dist/cluster/entity-machine.js +57 -34
- package/dist/cluster/index.js +1 -1
- package/dist/cluster/persistence.d.ts +0 -1
- package/dist/cluster/to-entity.d.ts +19 -21
- package/dist/cluster/to-entity.js +18 -19
- package/dist/errors.d.ts +11 -37
- package/dist/errors.js +12 -30
- package/dist/index.d.ts +4 -5
- package/dist/index.js +4 -5
- package/dist/inspection.d.ts +0 -1
- package/dist/inspection.js +24 -13
- package/dist/internal/brands.d.ts +0 -1
- package/dist/internal/event-advancement.d.ts +50 -0
- package/dist/internal/event-advancement.js +79 -0
- package/dist/internal/inspection.d.ts +5 -9
- package/dist/internal/inspection.js +31 -10
- package/dist/internal/machine-definition.d.ts +16 -0
- package/dist/internal/runtime.d.ts +1 -161
- package/dist/internal/runtime.js +191 -150
- package/dist/internal/transition.d.ts +4 -135
- package/dist/internal/transition.js +82 -157
- package/dist/internal/utils.d.ts +3 -40
- package/dist/internal/utils.js +2 -2
- package/dist/machine.d.ts +47 -137
- package/dist/machine.js +147 -215
- package/dist/schema.d.ts +42 -21
- package/dist/schema.js +28 -18
- package/dist/supervision.d.ts +1 -24
- package/dist/supervision.js +2 -3
- package/dist/testing.d.ts +15 -29
- package/dist/testing.js +76 -95
- package/package.json +19 -38
- package/dist/slot.d.ts +0 -159
- package/dist/slot.js +0 -165
- package/v3/dist/_virtual/_rolldown/runtime.js +0 -13
- package/v3/dist/actor.d.ts +0 -250
- package/v3/dist/actor.js +0 -577
- package/v3/dist/cluster/adapters/in-memory.d.ts +0 -15
- package/v3/dist/cluster/adapters/in-memory.js +0 -62
- package/v3/dist/cluster/entity-actor-ref.d.ts +0 -49
- package/v3/dist/cluster/entity-actor-ref.js +0 -19
- package/v3/dist/cluster/entity-machine.d.ts +0 -74
- package/v3/dist/cluster/entity-machine.js +0 -166
- package/v3/dist/cluster/index.d.ts +0 -6
- package/v3/dist/cluster/index.js +0 -6
- package/v3/dist/cluster/persistence.d.ts +0 -48
- package/v3/dist/cluster/persistence.js +0 -14
- package/v3/dist/cluster/to-entity.d.ts +0 -69
- package/v3/dist/cluster/to-entity.js +0 -59
- package/v3/dist/errors.d.ts +0 -95
- package/v3/dist/errors.js +0 -54
- package/v3/dist/index.d.ts +0 -11
- package/v3/dist/index.js +0 -9
- package/v3/dist/inspection.d.ts +0 -151
- package/v3/dist/inspection.js +0 -128
- package/v3/dist/internal/brands.d.ts +0 -50
- package/v3/dist/internal/inspection.d.ts +0 -11
- package/v3/dist/internal/inspection.js +0 -20
- package/v3/dist/internal/runtime.d.ts +0 -161
- package/v3/dist/internal/runtime.js +0 -360
- package/v3/dist/internal/transition.d.ts +0 -190
- package/v3/dist/internal/transition.js +0 -278
- package/v3/dist/internal/utils.d.ts +0 -101
- package/v3/dist/internal/utils.js +0 -75
- package/v3/dist/machine.d.ts +0 -398
- package/v3/dist/machine.js +0 -487
- package/v3/dist/schema.d.ts +0 -174
- package/v3/dist/schema.js +0 -206
- package/v3/dist/slot.d.ts +0 -158
- package/v3/dist/slot.js +0 -165
- package/v3/dist/supervision.d.ts +0 -97
- package/v3/dist/supervision.js +0 -42
- package/v3/dist/testing.d.ts +0 -151
- package/v3/dist/testing.js +0 -189
- /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 };
|