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
package/dist/internal/runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { INTERNAL_INIT_EVENT } from "./utils.js";
|
|
2
|
-
import { NoReplyError } from "../errors.js";
|
|
3
|
-
import {
|
|
1
|
+
import { INTERNAL_INIT_EVENT, isEffect } from "./utils.js";
|
|
2
|
+
import { ActorStoppedError, NoReplyError } from "../errors.js";
|
|
3
|
+
import { makeEventAdvancement } from "./event-advancement.js";
|
|
4
|
+
import { processEventCoreImmediate, runSpawnEffects, shouldPostpone } from "./transition.js";
|
|
4
5
|
import { ActorExit } from "../supervision.js";
|
|
5
6
|
import { ActorSystem } from "../actor.js";
|
|
6
7
|
import { Cause, Deferred, Effect, Exit, Fiber, Queue, Ref, Schema, Scope, SubscriptionRef } from "effect";
|
|
@@ -42,9 +43,8 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
42
43
|
const { actorId, hooks, lifecycle } = config;
|
|
43
44
|
const services = yield* Effect.context();
|
|
44
45
|
const fork = Effect.runForkWith(services);
|
|
45
|
-
const stateRef = config.cellResources
|
|
46
|
-
const
|
|
47
|
-
const eventQueue = config.cellResources?.eventQueue ?? (yield* config.queueFactory ?? Queue.unbounded());
|
|
46
|
+
const { stateRef, stoppedRef, eventQueue } = config.cellResources;
|
|
47
|
+
const pendingRequests = /* @__PURE__ */ new Set();
|
|
48
48
|
const exitDeferred = yield* Deferred.make();
|
|
49
49
|
const actorScope = yield* Scope.make();
|
|
50
50
|
const deferredReplyRef = { current: void 0 };
|
|
@@ -57,10 +57,11 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
57
57
|
const childPrefix = config.childIdPrefix ?? "";
|
|
58
58
|
const defaultSpawn = (childId, childMachine) => system.spawn(`${childPrefix}${childId}`, childMachine).pipe(Effect.provideService(ActorSystem, system));
|
|
59
59
|
const onChildSpawned = config.onChildSpawned;
|
|
60
|
+
let spawn = defaultSpawn;
|
|
61
|
+
if (onChildSpawned !== void 0) spawn = (childId, childMachine) => defaultSpawn(childId, childMachine).pipe(Effect.tap((child) => onChildSpawned(childId, child)));
|
|
60
62
|
const self = {
|
|
61
63
|
send: selfSend,
|
|
62
|
-
|
|
63
|
-
spawn: onChildSpawned !== void 0 ? (childId, childMachine) => defaultSpawn(childId, childMachine).pipe(Effect.tap((child) => onChildSpawned(childId, child))) : defaultSpawn,
|
|
64
|
+
spawn,
|
|
64
65
|
reply: (value) => Effect.sync(() => {
|
|
65
66
|
const deferred = deferredReplyRef.current;
|
|
66
67
|
if (deferred !== void 0) {
|
|
@@ -73,14 +74,6 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
73
74
|
};
|
|
74
75
|
const stateScopeRef = { current: yield* Scope.make() };
|
|
75
76
|
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
77
|
const loopFiberRef = { current: void 0 };
|
|
85
78
|
/** Set the exit deferred exactly once. */
|
|
86
79
|
const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
|
|
@@ -92,19 +85,22 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
92
85
|
return;
|
|
93
86
|
}
|
|
94
87
|
const backgroundFibers = [];
|
|
95
|
-
for (const bg of machine.
|
|
88
|
+
for (const bg of machine._backgroundEffectEntries()) {
|
|
96
89
|
const fiber = yield* bg.handler({
|
|
97
90
|
actorId,
|
|
98
91
|
state: machine.initial,
|
|
99
92
|
event: initEvent,
|
|
100
93
|
self,
|
|
101
|
-
slots,
|
|
102
94
|
system
|
|
103
|
-
}).pipe(Effect.
|
|
95
|
+
}).pipe(Effect.forkIn(actorScope));
|
|
104
96
|
backgroundFibers.push(fiber);
|
|
105
97
|
}
|
|
106
98
|
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(() =>
|
|
99
|
+
const initialSpawnDefectSignal = (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => {
|
|
100
|
+
const loopFiber = loopFiberRef.current;
|
|
101
|
+
if (loopFiber !== void 0) return Fiber.interrupt(loopFiber);
|
|
102
|
+
return Effect.void;
|
|
103
|
+
})), Effect.asVoid);
|
|
108
104
|
yield* runSpawnEffects(machine, machine.initial, initEvent, self, stateScopeRef.current, system, actorId, hooks?.onError, initialSpawnDefectSignal).pipe(Effect.catchCause((cause) => Effect.gen(function* () {
|
|
109
105
|
yield* Ref.set(stoppedRef, true);
|
|
110
106
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
@@ -112,7 +108,7 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
112
108
|
yield* Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn"));
|
|
113
109
|
return yield* Effect.failCause(cause);
|
|
114
110
|
})));
|
|
115
|
-
if (machine.
|
|
111
|
+
if (machine._isFinal(machine.initial._tag)) {
|
|
116
112
|
if (lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(machine.initial);
|
|
117
113
|
yield* Ref.set(stoppedRef, true);
|
|
118
114
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
@@ -123,9 +119,13 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
123
119
|
}
|
|
124
120
|
const augmentedHooks = {
|
|
125
121
|
...hooks,
|
|
126
|
-
onSpawnDefect: (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() =>
|
|
122
|
+
onSpawnDefect: (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => {
|
|
123
|
+
const loopFiber = loopFiberRef.current;
|
|
124
|
+
if (loopFiber !== void 0) return Fiber.interrupt(loopFiber);
|
|
125
|
+
return Effect.void;
|
|
126
|
+
})), Effect.asVoid)
|
|
127
127
|
};
|
|
128
|
-
const loopFiber = yield*
|
|
128
|
+
const loopFiber = yield* runtimeEventLoop(machine, stateRef, eventQueue, pendingRequests, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, augmentedHooks, deferredReplyRef, lifecycle, fork).pipe(Effect.provide(services), Effect.forkDetach);
|
|
129
129
|
loopFiberRef.current = loopFiber;
|
|
130
130
|
if (backgroundFibers.length > 0) yield* Effect.raceAll(backgroundFibers.map((fiber) => Fiber.await(fiber).pipe(Effect.flatMap((exit) => {
|
|
131
131
|
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)));
|
|
@@ -141,6 +141,7 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
141
141
|
const stop = Effect.gen(function* () {
|
|
142
142
|
if (yield* Ref.get(stoppedRef)) return;
|
|
143
143
|
if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
|
|
144
|
+
yield* settlePendingRequests(pendingRequests, actorId);
|
|
144
145
|
yield* Ref.set(stoppedRef, true);
|
|
145
146
|
const loopFiber = loopFiberRef.current;
|
|
146
147
|
if (loopFiber !== void 0) yield* Fiber.interrupt(loopFiber);
|
|
@@ -150,105 +151,140 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
150
151
|
}).pipe(Effect.asVoid);
|
|
151
152
|
if (config.skipFinalizer !== true) yield* Effect.addFinalizer(() => stop);
|
|
152
153
|
return {
|
|
153
|
-
...makeHandle(stateRef, stoppedRef, eventQueue,
|
|
154
|
-
stop,
|
|
155
|
-
start
|
|
154
|
+
...makeHandle(actorId, stateRef, stoppedRef, eventQueue, pendingRequests, exitDeferred),
|
|
155
|
+
stop: stop.pipe(Effect.provide(services)),
|
|
156
|
+
start: start.pipe(Effect.provide(services))
|
|
156
157
|
};
|
|
157
158
|
});
|
|
158
159
|
/**
|
|
159
|
-
* Build the runtime handle
|
|
160
|
+
* Build the runtime handle.
|
|
160
161
|
* Shared between initial-final and normal paths.
|
|
161
162
|
*/
|
|
162
|
-
const makeHandle = (stateRef, stoppedRef, eventQueue,
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
const makeHandle = (actorId, stateRef, stoppedRef, eventQueue, pendingRequests, exitDeferred) => {
|
|
164
|
+
const track = (deferred, settle) => {
|
|
165
|
+
pendingRequests.add(settle);
|
|
166
|
+
return Deferred.await(deferred).pipe(Effect.ensuring(Effect.sync(() => pendingRequests.delete(settle))));
|
|
167
|
+
};
|
|
168
|
+
const send = (event) => Ref.get(stoppedRef).pipe(Effect.flatMap((stopped) => {
|
|
169
|
+
if (stopped) return Effect.void;
|
|
170
|
+
return Queue.offer(eventQueue, {
|
|
165
171
|
_tag: "send",
|
|
166
172
|
event
|
|
167
|
-
});
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
}).pipe(Effect.asVoid);
|
|
174
|
+
}));
|
|
175
|
+
return {
|
|
176
|
+
send,
|
|
177
|
+
sendWait: (event) => Effect.gen(function* () {
|
|
178
|
+
if (!(yield* Ref.get(stoppedRef))) {
|
|
179
|
+
const done = yield* Deferred.make();
|
|
180
|
+
yield* Queue.offer(eventQueue, {
|
|
181
|
+
_tag: "sendWait",
|
|
182
|
+
event,
|
|
183
|
+
done
|
|
184
|
+
});
|
|
185
|
+
yield* track(done, (error) => Deferred.fail(done, error).pipe(Effect.asVoid));
|
|
186
|
+
}
|
|
187
|
+
}),
|
|
188
|
+
call: (event) => Effect.gen(function* () {
|
|
189
|
+
if (yield* Ref.get(stoppedRef)) return yield* ActorStoppedError.make({ actorId });
|
|
190
|
+
const reply = yield* Deferred.make();
|
|
191
|
+
yield* Queue.offer(eventQueue, {
|
|
192
|
+
_tag: "call",
|
|
193
|
+
event,
|
|
194
|
+
reply
|
|
195
|
+
});
|
|
196
|
+
return yield* track(reply, (error) => Deferred.fail(reply, error).pipe(Effect.asVoid));
|
|
197
|
+
}),
|
|
198
|
+
ask: (event) => Effect.gen(function* () {
|
|
199
|
+
if (yield* Ref.get(stoppedRef)) return yield* ActorStoppedError.make({ actorId });
|
|
200
|
+
const reply = yield* Deferred.make();
|
|
172
201
|
yield* Queue.offer(eventQueue, {
|
|
173
|
-
_tag: "
|
|
202
|
+
_tag: "ask",
|
|
174
203
|
event,
|
|
204
|
+
reply
|
|
205
|
+
});
|
|
206
|
+
return yield* track(reply, (error) => Deferred.fail(reply, error).pipe(Effect.asVoid));
|
|
207
|
+
}),
|
|
208
|
+
drain: Effect.gen(function* () {
|
|
209
|
+
if (yield* Ref.get(stoppedRef)) return;
|
|
210
|
+
const done = yield* Deferred.make();
|
|
211
|
+
yield* Queue.offer(eventQueue, {
|
|
212
|
+
_tag: "drain",
|
|
175
213
|
done
|
|
176
214
|
});
|
|
177
215
|
yield* Deferred.await(done);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
start: Effect.void,
|
|
198
|
-
_queue: eventQueue,
|
|
199
|
-
_stoppedRef: stoppedRef,
|
|
200
|
-
exitDeferred,
|
|
201
|
-
actorScope
|
|
216
|
+
}).pipe(Effect.asVoid),
|
|
217
|
+
sendSync: (event) => {
|
|
218
|
+
if (!Effect.runSync(Ref.get(stoppedRef))) Effect.runSync(Queue.offer(eventQueue, {
|
|
219
|
+
_tag: "send",
|
|
220
|
+
event
|
|
221
|
+
}));
|
|
222
|
+
},
|
|
223
|
+
getState: SubscriptionRef.get(stateRef),
|
|
224
|
+
stateRef,
|
|
225
|
+
stop: Effect.void,
|
|
226
|
+
start: Effect.void,
|
|
227
|
+
settlePendingRequests: settlePendingRequests(pendingRequests, actorId),
|
|
228
|
+
exitDeferred
|
|
229
|
+
};
|
|
230
|
+
};
|
|
231
|
+
const settlePendingRequests = (pendingRequests, actorId) => Effect.gen(function* () {
|
|
232
|
+
const error = ActorStoppedError.make({ actorId });
|
|
233
|
+
for (const settle of pendingRequests) yield* settle(error);
|
|
234
|
+
pendingRequests.clear();
|
|
202
235
|
});
|
|
203
|
-
const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, hooks, deferredReplyRef, lifecycle,
|
|
236
|
+
const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function* (machine, stateRef, eventQueue, pendingRequests, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, hooks, deferredReplyRef, lifecycle, fork) {
|
|
204
237
|
const forkEffect = fork ?? Effect.runFork;
|
|
205
238
|
/** Set the exit deferred exactly once. */
|
|
206
239
|
const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
|
|
207
|
-
const
|
|
208
|
-
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
209
|
-
const processQueued = Effect.fn("effect-machine.runtime.processQueued")(function* (queued) {
|
|
240
|
+
const postponeQueued = Effect.fn("effect-machine.runtime.postponeQueued")(function* (currentState, queued) {
|
|
210
241
|
const event = queued.event;
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
242
|
+
const postponedResult = {
|
|
243
|
+
newState: currentState,
|
|
244
|
+
previousState: currentState,
|
|
245
|
+
transitioned: false,
|
|
246
|
+
lifecycleRan: false,
|
|
247
|
+
isFinal: false,
|
|
248
|
+
hasReply: false,
|
|
249
|
+
deferReply: false,
|
|
250
|
+
reply: void 0,
|
|
251
|
+
postponed: true
|
|
252
|
+
};
|
|
253
|
+
let input = queued;
|
|
254
|
+
if (queued._tag === "call") {
|
|
255
|
+
yield* Deferred.succeed(queued.reply, postponedResult);
|
|
256
|
+
input = {
|
|
257
|
+
_tag: "send",
|
|
258
|
+
event
|
|
259
|
+
};
|
|
260
|
+
} else if (queued._tag === "sendWait") {
|
|
261
|
+
yield* Deferred.succeed(queued.done, void 0);
|
|
262
|
+
input = {
|
|
229
263
|
_tag: "send",
|
|
230
264
|
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
265
|
};
|
|
247
266
|
}
|
|
267
|
+
return {
|
|
268
|
+
input,
|
|
269
|
+
value: {
|
|
270
|
+
shouldStop: false,
|
|
271
|
+
stateChanged: false,
|
|
272
|
+
result: postponedResult
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
const processQueued = (currentState, queued) => Effect.gen(function* () {
|
|
277
|
+
const event = queued.event;
|
|
248
278
|
if (lifecycle?.onEvent !== void 0) yield* lifecycle.onEvent(currentState, event);
|
|
249
|
-
const
|
|
279
|
+
const processing = processEventCoreImmediate(machine, currentState, event, self, stateScopeRef, system, actorId, hooks);
|
|
280
|
+
let result;
|
|
281
|
+
if (isEffect(processing)) result = yield* processing;
|
|
282
|
+
else result = processing;
|
|
250
283
|
if (result.transitioned) yield* SubscriptionRef.set(stateRef, result.newState);
|
|
251
|
-
if (lifecycle?.onStateChange !== void 0 && result.transitioned)
|
|
284
|
+
if (lifecycle?.onStateChange !== void 0 && result.transitioned) {
|
|
285
|
+
const stateChange = lifecycle.onStateChange(result, event);
|
|
286
|
+
if (isEffect(stateChange)) yield* stateChange;
|
|
287
|
+
}
|
|
252
288
|
switch (queued._tag) {
|
|
253
289
|
case "call":
|
|
254
290
|
yield* Deferred.succeed(queued.reply, result);
|
|
@@ -256,24 +292,25 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
|
|
|
256
292
|
case "sendWait":
|
|
257
293
|
yield* Deferred.succeed(queued.done, void 0);
|
|
258
294
|
break;
|
|
259
|
-
case "ask":
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
295
|
+
case "ask": if (result.hasReply) {
|
|
296
|
+
const replySchema = machine._replySchema(event._tag);
|
|
297
|
+
if (replySchema !== void 0) {
|
|
298
|
+
const decoded = yield* Schema.decodeUnknownEffect(replySchema)(result.reply).pipe(Effect.catch((decodeError) => Effect.gen(function* () {
|
|
299
|
+
yield* Deferred.die(queued.reply, decodeError);
|
|
300
|
+
return yield* Effect.die(decodeError);
|
|
301
|
+
})));
|
|
302
|
+
yield* Deferred.succeed(queued.reply, decoded);
|
|
303
|
+
} else yield* Deferred.succeed(queued.reply, result.reply);
|
|
304
|
+
} else if (result.deferReply && deferredReplyRef !== void 0) deferredReplyRef.current = queued.reply;
|
|
305
|
+
else yield* Deferred.fail(queued.reply, NoReplyError.make({
|
|
306
|
+
actorId,
|
|
307
|
+
eventTag: event._tag
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
if (lifecycle?.onProcessed !== void 0 && result.transitioned) {
|
|
311
|
+
const processed = lifecycle.onProcessed(result, event);
|
|
312
|
+
if (isEffect(processed)) yield* processed;
|
|
275
313
|
}
|
|
276
|
-
if (lifecycle?.onProcessed !== void 0 && result.transitioned) yield* lifecycle.onProcessed(result, event);
|
|
277
314
|
const shouldStop = result.isFinal && result.lifecycleRan;
|
|
278
315
|
if (shouldStop && lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(result.newState);
|
|
279
316
|
return {
|
|
@@ -282,24 +319,48 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
|
|
|
282
319
|
result
|
|
283
320
|
};
|
|
284
321
|
});
|
|
322
|
+
const initialState = yield* SubscriptionRef.get(stateRef);
|
|
323
|
+
let currentState = initialState;
|
|
324
|
+
const makePostponeAdvancement = () => makeEventAdvancement({
|
|
325
|
+
initial: initialState,
|
|
326
|
+
isFinal: (state) => machine._isFinal(state._tag),
|
|
327
|
+
shouldPostpone: (state, queued) => shouldPostpone(machine, state._tag, queued.event._tag),
|
|
328
|
+
postpone: postponeQueued,
|
|
329
|
+
process: (state, queued, _draining) => processQueued(state, queued).pipe(Effect.map((processed) => ({
|
|
330
|
+
state: processed.result.newState,
|
|
331
|
+
transitioned: processed.result.transitioned,
|
|
332
|
+
stateChanged: processed.stateChanged,
|
|
333
|
+
shouldStop: processed.shouldStop,
|
|
334
|
+
value: processed
|
|
335
|
+
}))),
|
|
336
|
+
discard: (queued) => {
|
|
337
|
+
if (queued._tag === "ask") return Deferred.fail(queued.reply, NoReplyError.make({
|
|
338
|
+
actorId,
|
|
339
|
+
eventTag: queued.event._tag
|
|
340
|
+
})).pipe(Effect.asVoid);
|
|
341
|
+
return Effect.void;
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
let advancement;
|
|
345
|
+
if (machine._hasPostponeRules()) advancement = makePostponeAdvancement();
|
|
285
346
|
const shutdown = (exitReason) => Effect.gen(function* () {
|
|
286
347
|
yield* Ref.set(stoppedRef, true);
|
|
287
348
|
if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
|
|
288
|
-
|
|
349
|
+
if (advancement !== void 0) yield* advancement.close();
|
|
289
350
|
const remaining = yield* Queue.clear(eventQueue);
|
|
290
351
|
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,
|
|
352
|
+
else if (entry._tag === "ask") forkEffect(Deferred.fail(entry.reply, NoReplyError.make({
|
|
292
353
|
actorId,
|
|
293
354
|
eventTag: entry.event._tag
|
|
294
355
|
})));
|
|
295
356
|
else if (entry._tag === "call") {
|
|
296
|
-
const
|
|
357
|
+
const state = yield* SubscriptionRef.get(stateRef);
|
|
297
358
|
forkEffect(Deferred.succeed(entry.reply, {
|
|
298
|
-
newState:
|
|
299
|
-
previousState:
|
|
359
|
+
newState: state,
|
|
360
|
+
previousState: state,
|
|
300
361
|
transitioned: false,
|
|
301
362
|
lifecycleRan: false,
|
|
302
|
-
isFinal: machine.
|
|
363
|
+
isFinal: machine._isFinal(state._tag),
|
|
303
364
|
hasReply: false,
|
|
304
365
|
deferReply: false,
|
|
305
366
|
reply: void 0,
|
|
@@ -317,44 +378,24 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
|
|
|
317
378
|
return;
|
|
318
379
|
}
|
|
319
380
|
const eventQueued = queued;
|
|
320
|
-
const
|
|
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.catchCause((cause) => {
|
|
381
|
+
const catchEventDefect = (effect) => effect.pipe(Effect.catchCause((cause) => {
|
|
324
382
|
if (queued._tag === "sendWait") forkEffect(Deferred.failCause(queued.done, cause));
|
|
325
383
|
else if (queued._tag === "ask") forkEffect(Deferred.die(queued.reply, cause));
|
|
326
384
|
else if (queued._tag === "call") forkEffect(Deferred.failCause(queued.reply, cause));
|
|
327
385
|
return shutdown(ActorExit.Defect(cause, "transition")).pipe(Effect.andThen(Effect.failCause(cause)));
|
|
328
386
|
}));
|
|
329
|
-
|
|
387
|
+
let stopped;
|
|
388
|
+
if (advancement === void 0) {
|
|
389
|
+
const processed = yield* catchEventDefect(processQueued(currentState, eventQueued));
|
|
390
|
+
currentState = processed.result.newState;
|
|
391
|
+
stopped = processed.shouldStop;
|
|
392
|
+
} else stopped = (yield* catchEventDefect(advancement.advance(eventQueued))).stopped;
|
|
393
|
+
if (stopped) {
|
|
330
394
|
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
331
395
|
yield* shutdown(ActorExit.Final(finalState));
|
|
332
396
|
return;
|
|
333
397
|
}
|
|
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
398
|
}
|
|
349
399
|
});
|
|
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
400
|
//#endregion
|
|
360
401
|
export { createRuntime };
|
|
@@ -1,63 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ActorSystemService } from "../actor.js";
|
|
4
|
-
import { Cause, Effect, Scope } from "effect";
|
|
5
|
-
|
|
1
|
+
import { Machine } from "../machine.js";
|
|
2
|
+
import { Transition } from "./machine-definition.js";
|
|
6
3
|
//#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: ActorSystemService, actorId: string) => Effect.Effect<{
|
|
33
|
-
newState: S;
|
|
34
|
-
hasReply: boolean;
|
|
35
|
-
deferReply: boolean;
|
|
36
|
-
reply: unknown;
|
|
37
|
-
}, never, Exclude<R, MachineContextTag>>;
|
|
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: ActorSystemService, 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, MachineContextTag>>;
|
|
61
4
|
/**
|
|
62
5
|
* Optional hooks for event processing inspection/tracing.
|
|
63
6
|
*/
|
|
@@ -103,54 +46,6 @@ interface ProcessEventResult<S> {
|
|
|
103
46
|
/** Whether the event was postponed (buffered for retry after next state change) */
|
|
104
47
|
readonly postponed: boolean;
|
|
105
48
|
}
|
|
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.Closeable;
|
|
133
|
-
}, system: ActorSystemService, 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, MachineContextTag> | Exclude<Exclude<R, MachineContextTag>, 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.Closeable, system: ActorSystemService, 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, MachineContextTag>, Scope.Scope>>;
|
|
154
49
|
/**
|
|
155
50
|
* Resolve which transition should fire for a given state and event.
|
|
156
51
|
* Uses indexed O(1) lookup. First matching transition wins.
|
|
@@ -159,32 +54,6 @@ declare const resolveTransition: <S extends {
|
|
|
159
54
|
readonly _tag: string;
|
|
160
55
|
}, E extends {
|
|
161
56
|
readonly _tag: string;
|
|
162
|
-
}, R>(machine: Machine<S, E, R, any, any
|
|
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>>;
|
|
57
|
+
}, R>(machine: Machine<S, E, R, any, any>, currentState: S, event: E) => Transition<S, E, never> | undefined;
|
|
189
58
|
//#endregion
|
|
190
|
-
export { ProcessEventError, ProcessEventHooks, ProcessEventResult,
|
|
59
|
+
export { ProcessEventError, ProcessEventHooks, ProcessEventResult, resolveTransition };
|