effect-machine 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -324
- package/dist/actor.d.ts +42 -27
- package/dist/actor.js +212 -305
- package/dist/cluster/entity-machine.js +20 -9
- package/dist/cluster/to-entity.d.ts +2 -2
- package/dist/errors.d.ts +16 -19
- package/dist/errors.js +3 -5
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/internal/runtime.d.ts +81 -6
- package/dist/internal/runtime.js +166 -57
- package/dist/internal/transition.d.ts +5 -4
- package/dist/internal/transition.js +9 -9
- package/dist/machine.d.ts +65 -44
- package/dist/machine.js +68 -67
- package/dist/schema.js +1 -1
- package/dist/supervision.d.ts +97 -0
- package/dist/supervision.js +42 -0
- package/dist/testing.d.ts +17 -8
- package/dist/testing.js +22 -25
- package/package.json +5 -5
- package/v3/dist/actor.d.ts +50 -34
- package/v3/dist/actor.js +209 -289
- package/v3/dist/cluster/entity-machine.js +5 -5
- package/v3/dist/errors.d.ts +3 -8
- package/v3/dist/errors.js +2 -4
- package/v3/dist/index.d.ts +5 -4
- package/v3/dist/index.js +3 -2
- package/v3/dist/internal/runtime.d.ts +82 -5
- package/v3/dist/internal/runtime.js +147 -48
- package/v3/dist/internal/transition.d.ts +5 -4
- package/v3/dist/internal/transition.js +8 -8
- package/v3/dist/machine.d.ts +18 -36
- package/v3/dist/machine.js +54 -64
- package/v3/dist/supervision.d.ts +97 -0
- package/v3/dist/supervision.js +42 -0
- package/v3/dist/testing.d.ts +18 -9
- package/v3/dist/testing.js +21 -22
package/dist/actor.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Inspector } from "./inspection.js";
|
|
2
|
-
import {
|
|
3
|
-
import { ActorStoppedError, DuplicateActorError, NoReplyError } from "./errors.js";
|
|
2
|
+
import { processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
|
|
4
3
|
import { emitWithTimestamp } from "./internal/inspection.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { ActorStoppedError, DuplicateActorError } from "./errors.js";
|
|
5
|
+
import { createRuntime } from "./internal/runtime.js";
|
|
6
|
+
import { Cause, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Semaphore, ServiceMap, Stream, SubscriptionRef } from "effect";
|
|
7
7
|
//#region src/actor.ts
|
|
8
8
|
/**
|
|
9
9
|
* Actor system: spawning, lifecycle, and event processing.
|
|
@@ -28,10 +28,11 @@ const notifyListeners = (listeners, state) => {
|
|
|
28
28
|
/**
|
|
29
29
|
* Build core ActorRef methods.
|
|
30
30
|
*/
|
|
31
|
-
const buildActorRefCore = (id, machine, stateRef,
|
|
31
|
+
const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, listeners, stop, system, childrenMap, pendingReplies, transitionsPubSub, exitDeferred) => {
|
|
32
32
|
const send = Effect.fn("effect-machine.actor.send")(function* (event) {
|
|
33
33
|
if (yield* Ref.get(stoppedRef)) return;
|
|
34
|
-
yield*
|
|
34
|
+
const q = yield* Ref.get(eventQueueRef);
|
|
35
|
+
yield* Queue.offer(q, {
|
|
35
36
|
_tag: "send",
|
|
36
37
|
event
|
|
37
38
|
});
|
|
@@ -49,7 +50,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
49
50
|
}
|
|
50
51
|
const reply = yield* Deferred.make();
|
|
51
52
|
pendingReplies.add(reply);
|
|
52
|
-
yield*
|
|
53
|
+
const q = yield* Ref.get(eventQueueRef);
|
|
54
|
+
yield* Queue.offer(q, {
|
|
53
55
|
_tag: "call",
|
|
54
56
|
event,
|
|
55
57
|
reply
|
|
@@ -66,7 +68,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
66
68
|
if (yield* Ref.get(stoppedRef)) return yield* new ActorStoppedError({ actorId: id });
|
|
67
69
|
const reply = yield* Deferred.make();
|
|
68
70
|
pendingReplies.add(reply);
|
|
69
|
-
yield*
|
|
71
|
+
const q = yield* Ref.get(eventQueueRef);
|
|
72
|
+
yield* Queue.offer(q, {
|
|
70
73
|
_tag: "ask",
|
|
71
74
|
event,
|
|
72
75
|
reply
|
|
@@ -126,12 +129,27 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
126
129
|
listeners.delete(fn);
|
|
127
130
|
};
|
|
128
131
|
},
|
|
132
|
+
awaitExit: Deferred.await(exitDeferred),
|
|
133
|
+
watch: (other) => other.awaitExit,
|
|
134
|
+
drain: Effect.gen(function* () {
|
|
135
|
+
if (yield* Ref.get(stoppedRef)) return;
|
|
136
|
+
const q = yield* Ref.get(eventQueueRef);
|
|
137
|
+
const done = yield* Deferred.make();
|
|
138
|
+
yield* Queue.offer(q, {
|
|
139
|
+
_tag: "drain",
|
|
140
|
+
done
|
|
141
|
+
});
|
|
142
|
+
yield* Deferred.await(done);
|
|
143
|
+
}).pipe(Effect.asVoid),
|
|
129
144
|
sync: {
|
|
130
145
|
send: (event) => {
|
|
131
|
-
if (!Effect.runSync(Ref.get(stoppedRef)))
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
146
|
+
if (!Effect.runSync(Ref.get(stoppedRef))) {
|
|
147
|
+
const q = Effect.runSync(Ref.get(eventQueueRef));
|
|
148
|
+
Effect.runSync(Queue.offer(q, {
|
|
149
|
+
_tag: "send",
|
|
150
|
+
event
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
135
153
|
},
|
|
136
154
|
stop: () => Effect.runFork(stop),
|
|
137
155
|
snapshot: () => Effect.runSync(SubscriptionRef.get(stateRef)),
|
|
@@ -144,12 +162,41 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
144
162
|
children: childrenMap
|
|
145
163
|
};
|
|
146
164
|
};
|
|
165
|
+
/** Build ProcessEventHooks from an inspector */
|
|
166
|
+
const buildInspectionHooks = (actorId, inspector) => ({
|
|
167
|
+
onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
168
|
+
type: "@machine.effect",
|
|
169
|
+
actorId,
|
|
170
|
+
effectType: "spawn",
|
|
171
|
+
state,
|
|
172
|
+
timestamp
|
|
173
|
+
})),
|
|
174
|
+
onTransition: (from, to, ev) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
175
|
+
type: "@machine.transition",
|
|
176
|
+
actorId,
|
|
177
|
+
fromState: from,
|
|
178
|
+
toState: to,
|
|
179
|
+
event: ev,
|
|
180
|
+
timestamp
|
|
181
|
+
})),
|
|
182
|
+
onError: (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
183
|
+
type: "@machine.error",
|
|
184
|
+
actorId,
|
|
185
|
+
phase: info.phase,
|
|
186
|
+
state: info.state,
|
|
187
|
+
event: info.event,
|
|
188
|
+
error: Cause.pretty(info.cause),
|
|
189
|
+
timestamp
|
|
190
|
+
}))
|
|
191
|
+
});
|
|
147
192
|
/**
|
|
148
|
-
* Create and start an actor for a machine
|
|
193
|
+
* Create and start an actor for a machine.
|
|
194
|
+
* Delegates to the shared runtime kernel with actor-specific lifecycle hooks.
|
|
149
195
|
*/
|
|
150
196
|
const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine, options) {
|
|
151
197
|
const initial = options?.initialState ?? machine.initial;
|
|
152
198
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
|
|
199
|
+
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
|
|
153
200
|
const existingSystem = yield* Effect.serviceOption(ActorSystem);
|
|
154
201
|
let system;
|
|
155
202
|
let implicitSystemScope;
|
|
@@ -160,102 +207,149 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
160
207
|
implicitSystemScope = scope;
|
|
161
208
|
}
|
|
162
209
|
const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
|
|
163
|
-
const eventQueue = yield* Queue.unbounded();
|
|
164
|
-
const stoppedRef = yield* Ref.make(false);
|
|
165
210
|
const childrenMap = /* @__PURE__ */ new Map();
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
yield* Queue.offer(eventQueue, {
|
|
170
|
-
_tag: "send",
|
|
171
|
-
event
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
const self = {
|
|
175
|
-
send: selfSend,
|
|
176
|
-
cast: selfSend,
|
|
177
|
-
spawn: (childId, childMachine) => Effect.gen(function* () {
|
|
178
|
-
const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
|
|
179
|
-
childrenMap.set(childId, child);
|
|
180
|
-
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
181
|
-
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
|
|
182
|
-
childrenMap.delete(childId);
|
|
183
|
-
}));
|
|
184
|
-
return child;
|
|
185
|
-
}),
|
|
186
|
-
reply: (value) => Effect.sync(() => {
|
|
187
|
-
const deferred = deferredReplyRef.current;
|
|
188
|
-
if (deferred !== void 0) {
|
|
189
|
-
deferredReplyRef.current = void 0;
|
|
190
|
-
Effect.runFork(Deferred.succeed(deferred, value));
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
return false;
|
|
194
|
-
})
|
|
195
|
-
};
|
|
196
|
-
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
|
|
211
|
+
const pendingReplies = /* @__PURE__ */ new Set();
|
|
212
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
213
|
+
const transitionsPubSub = yield* PubSub.unbounded();
|
|
197
214
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
198
215
|
type: "@machine.spawn",
|
|
199
216
|
actorId: id,
|
|
200
217
|
initialState: initial,
|
|
201
218
|
timestamp
|
|
202
219
|
}));
|
|
220
|
+
const hooks = inspectorValue !== void 0 ? buildInspectionHooks(id, inspectorValue) : void 0;
|
|
221
|
+
const machineWithState = initial !== machine.initial ? Object.create(machine, { initial: {
|
|
222
|
+
value: initial,
|
|
223
|
+
enumerable: true
|
|
224
|
+
} }) : machine;
|
|
203
225
|
const stateRef = yield* SubscriptionRef.make(initial);
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
226
|
+
const stoppedRef = yield* Ref.make(false);
|
|
227
|
+
const initialQueue = yield* Queue.unbounded();
|
|
228
|
+
const eventQueueRef = yield* Ref.make(initialQueue);
|
|
229
|
+
const terminalExitDeferred = yield* Deferred.make();
|
|
230
|
+
let stopEmitted = false;
|
|
231
|
+
const runtimeRef = { current: void 0 };
|
|
232
|
+
/** Build lifecycle hooks for a generation */
|
|
233
|
+
const buildLifecycle = () => {
|
|
234
|
+
stopEmitted = false;
|
|
235
|
+
return {
|
|
236
|
+
onEvent: inspectorValue !== void 0 ? (state, event) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
237
|
+
type: "@machine.event",
|
|
238
|
+
actorId: id,
|
|
239
|
+
state,
|
|
240
|
+
event,
|
|
241
|
+
timestamp
|
|
242
|
+
})) : void 0,
|
|
243
|
+
onStateChange: (result, _event) => Effect.gen(function* () {
|
|
244
|
+
notifyListeners(listeners, result.newState);
|
|
245
|
+
yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
|
|
246
|
+
if (result.lifecycleRan) {
|
|
247
|
+
yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
|
|
248
|
+
yield* Effect.annotateCurrentSpan("effect_machine.state.to", result.newState._tag);
|
|
249
|
+
}
|
|
250
|
+
}),
|
|
251
|
+
onProcessed: (result, event) => result.transitioned ? PubSub.publish(transitionsPubSub, {
|
|
252
|
+
fromState: result.previousState,
|
|
253
|
+
toState: result.newState,
|
|
254
|
+
event
|
|
255
|
+
}).pipe(Effect.asVoid) : Effect.void,
|
|
256
|
+
onFinal: inspectorValue !== void 0 ? (state) => Effect.gen(function* () {
|
|
257
|
+
stopEmitted = true;
|
|
258
|
+
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
259
|
+
type: "@machine.stop",
|
|
260
|
+
actorId: id,
|
|
261
|
+
finalState: state,
|
|
262
|
+
timestamp
|
|
263
|
+
}));
|
|
264
|
+
}) : void 0,
|
|
265
|
+
onShutdown: () => Effect.gen(function* () {
|
|
266
|
+
if (!stopEmitted) {
|
|
267
|
+
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
268
|
+
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
269
|
+
type: "@machine.stop",
|
|
270
|
+
actorId: id,
|
|
271
|
+
finalState,
|
|
272
|
+
timestamp
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
yield* settlePendingReplies(pendingReplies, id);
|
|
276
|
+
}),
|
|
277
|
+
onInitialSpawnEffects: inspectorValue !== void 0 ? (state) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
278
|
+
type: "@machine.effect",
|
|
279
|
+
actorId: id,
|
|
280
|
+
effectType: "spawn",
|
|
281
|
+
state,
|
|
282
|
+
timestamp
|
|
283
|
+
})) : void 0
|
|
284
|
+
};
|
|
213
285
|
};
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
286
|
+
/** Create a single runtime generation. machineForGen is machineWithState for initial, machine for restarts. */
|
|
287
|
+
const spawnGeneration = (machineForGen) => Ref.get(eventQueueRef).pipe(Effect.flatMap((currentQueue) => createRuntime(machineForGen, system, {
|
|
288
|
+
actorId: id,
|
|
289
|
+
hooks,
|
|
290
|
+
skipFinalizer: true,
|
|
291
|
+
cellResources: {
|
|
292
|
+
stateRef,
|
|
293
|
+
stoppedRef,
|
|
294
|
+
eventQueue: currentQueue
|
|
295
|
+
},
|
|
296
|
+
lifecycle: buildLifecycle(),
|
|
297
|
+
wrapProcess: (state, event, inner) => Effect.withSpan("effect-machine.event.process", { attributes: {
|
|
298
|
+
"effect_machine.actor.id": id,
|
|
299
|
+
"effect_machine.state.current": state._tag,
|
|
300
|
+
"effect_machine.event.type": event._tag
|
|
301
|
+
} })(inner.pipe(Effect.tap((r) => Effect.annotateCurrentSpan("effect_machine.transition.matched", r.result.transitioned)))),
|
|
302
|
+
onChildSpawned: (childId, child) => Effect.gen(function* () {
|
|
303
|
+
childrenMap.set(childId, child);
|
|
304
|
+
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
305
|
+
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
|
|
306
|
+
childrenMap.delete(childId);
|
|
307
|
+
}));
|
|
308
|
+
})
|
|
309
|
+
})));
|
|
310
|
+
const runtime = yield* spawnGeneration(machineWithState);
|
|
311
|
+
runtimeRef.current = runtime;
|
|
312
|
+
const supervision = options?.supervision;
|
|
313
|
+
let supervisorFiber;
|
|
314
|
+
if (supervision !== void 0) supervisorFiber = yield* Effect.forkDetach(Effect.gen(function* () {
|
|
315
|
+
const step = yield* Schedule.toStepWithSleep(supervision.schedule);
|
|
316
|
+
let generation = 0;
|
|
317
|
+
while (true) {
|
|
318
|
+
const currentRuntime = runtimeRef.current;
|
|
319
|
+
if (currentRuntime === void 0) return;
|
|
320
|
+
const generationExit = yield* Deferred.await(currentRuntime.exitDeferred);
|
|
321
|
+
if (generationExit._tag !== "Defect") {
|
|
322
|
+
yield* Deferred.succeed(terminalExitDeferred, generationExit);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (supervision.shouldRestart !== void 0 && !supervision.shouldRestart(generationExit)) {
|
|
326
|
+
yield* Deferred.succeed(terminalExitDeferred, generationExit);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if ((yield* step(generationExit).pipe(Effect.exit))._tag === "Failure") {
|
|
330
|
+
yield* Deferred.succeed(terminalExitDeferred, generationExit);
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
yield* settlePendingReplies(pendingReplies, id);
|
|
334
|
+
const freshQueue = yield* Queue.unbounded();
|
|
335
|
+
yield* Ref.set(eventQueueRef, freshQueue);
|
|
336
|
+
yield* SubscriptionRef.set(stateRef, machine.initial);
|
|
337
|
+
yield* Ref.set(stoppedRef, false);
|
|
338
|
+
childrenMap.clear();
|
|
339
|
+
runtimeRef.current = yield* spawnGeneration(machine);
|
|
340
|
+
generation++;
|
|
341
|
+
if (options?.onRestart !== void 0) yield* options.onRestart(generation, generationExit);
|
|
342
|
+
notifyListeners(listeners, machine.initial);
|
|
343
|
+
}
|
|
344
|
+
}));
|
|
345
|
+
else yield* Effect.forkDetach(Deferred.await(runtime.exitDeferred).pipe(Effect.tap((exit) => Deferred.succeed(terminalExitDeferred, exit))));
|
|
346
|
+
return buildActorRefCore(id, machine, stateRef, eventQueueRef, stoppedRef, listeners, Effect.gen(function* () {
|
|
347
|
+
if (supervisorFiber !== void 0) yield* Fiber.interrupt(supervisorFiber);
|
|
348
|
+
const currentRuntime = runtimeRef.current;
|
|
349
|
+
if (currentRuntime !== void 0) yield* currentRuntime.stop;
|
|
350
|
+
yield* Deferred.succeed(terminalExitDeferred, { _tag: "Stopped" });
|
|
257
351
|
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
258
|
-
}).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap, pendingReplies, transitionsPubSub);
|
|
352
|
+
}).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap, pendingReplies, transitionsPubSub, terminalExitDeferred);
|
|
259
353
|
});
|
|
260
354
|
/** Fail all pending call/ask Deferreds with ActorStoppedError. Safe to call multiple times. */
|
|
261
355
|
const settlePendingReplies = (pendingReplies, actorId) => Effect.sync(() => {
|
|
@@ -263,207 +357,6 @@ const settlePendingReplies = (pendingReplies, actorId) => Effect.sync(() => {
|
|
|
263
357
|
for (const deferred of pendingReplies) Effect.runFork(Deferred.fail(deferred, error));
|
|
264
358
|
pendingReplies.clear();
|
|
265
359
|
});
|
|
266
|
-
/**
|
|
267
|
-
* Main event loop for the actor.
|
|
268
|
-
* Includes postpone buffer — events matching postpone rules are buffered
|
|
269
|
-
* and drained after state tag changes (gen_statem semantics).
|
|
270
|
-
*/
|
|
271
|
-
const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, actorId, inspector, system, pendingReplies, transitionsPubSub, deferredReplyRef) {
|
|
272
|
-
const postponed = [];
|
|
273
|
-
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
274
|
-
const processQueued = Effect.fn("effect-machine.actor.processQueued")(function* (queued) {
|
|
275
|
-
const event = queued.event;
|
|
276
|
-
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
277
|
-
if (hasPostponeRules && shouldPostpone(machine, currentState._tag, event._tag)) {
|
|
278
|
-
postponed.push(queued);
|
|
279
|
-
if (queued._tag === "call") {
|
|
280
|
-
const postponedResult = {
|
|
281
|
-
newState: currentState,
|
|
282
|
-
previousState: currentState,
|
|
283
|
-
transitioned: false,
|
|
284
|
-
lifecycleRan: false,
|
|
285
|
-
isFinal: false,
|
|
286
|
-
hasReply: false,
|
|
287
|
-
deferReply: false,
|
|
288
|
-
reply: void 0,
|
|
289
|
-
postponed: true
|
|
290
|
-
};
|
|
291
|
-
yield* Deferred.succeed(queued.reply, postponedResult);
|
|
292
|
-
}
|
|
293
|
-
return {
|
|
294
|
-
shouldStop: false,
|
|
295
|
-
stateChanged: false
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
const { shouldStop, result } = yield* Effect.withSpan("effect-machine.event.process", { attributes: {
|
|
299
|
-
"effect_machine.actor.id": actorId,
|
|
300
|
-
"effect_machine.state.current": currentState._tag,
|
|
301
|
-
"effect_machine.event.type": event._tag
|
|
302
|
-
} })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system));
|
|
303
|
-
switch (queued._tag) {
|
|
304
|
-
case "call":
|
|
305
|
-
yield* Deferred.succeed(queued.reply, result);
|
|
306
|
-
break;
|
|
307
|
-
case "ask":
|
|
308
|
-
if (result.hasReply) {
|
|
309
|
-
const replySchema = machine._replySchemas?.get(event._tag);
|
|
310
|
-
if (replySchema !== void 0) {
|
|
311
|
-
let decoded;
|
|
312
|
-
try {
|
|
313
|
-
decoded = Schema.decodeUnknownSync(replySchema)(result.reply);
|
|
314
|
-
} catch (decodeError) {
|
|
315
|
-
yield* Deferred.die(queued.reply, decodeError);
|
|
316
|
-
return yield* Effect.die(decodeError);
|
|
317
|
-
}
|
|
318
|
-
yield* Deferred.succeed(queued.reply, decoded);
|
|
319
|
-
} else yield* Deferred.succeed(queued.reply, result.reply);
|
|
320
|
-
} else if (result.deferReply) deferredReplyRef.current = queued.reply;
|
|
321
|
-
else yield* Deferred.fail(queued.reply, new NoReplyError({
|
|
322
|
-
actorId,
|
|
323
|
-
eventTag: event._tag
|
|
324
|
-
}));
|
|
325
|
-
break;
|
|
326
|
-
}
|
|
327
|
-
if (result.transitioned) yield* PubSub.publish(transitionsPubSub, {
|
|
328
|
-
fromState: result.previousState,
|
|
329
|
-
toState: result.newState,
|
|
330
|
-
event
|
|
331
|
-
});
|
|
332
|
-
return {
|
|
333
|
-
shouldStop,
|
|
334
|
-
stateChanged: result.lifecycleRan
|
|
335
|
-
};
|
|
336
|
-
});
|
|
337
|
-
while (true) {
|
|
338
|
-
const { shouldStop, stateChanged } = yield* processQueued(yield* Queue.take(eventQueue));
|
|
339
|
-
if (shouldStop) {
|
|
340
|
-
yield* Ref.set(stoppedRef, true);
|
|
341
|
-
settlePostponedBuffer(postponed, pendingReplies, actorId);
|
|
342
|
-
yield* settlePendingReplies(pendingReplies, actorId);
|
|
343
|
-
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
344
|
-
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
let drainTriggered = stateChanged;
|
|
348
|
-
while (drainTriggered && postponed.length > 0) {
|
|
349
|
-
drainTriggered = false;
|
|
350
|
-
const drained = postponed.splice(0);
|
|
351
|
-
for (const entry of drained) {
|
|
352
|
-
const drain = yield* processQueued(entry);
|
|
353
|
-
if (drain.shouldStop) {
|
|
354
|
-
yield* Ref.set(stoppedRef, true);
|
|
355
|
-
settlePostponedBuffer(postponed, pendingReplies, actorId);
|
|
356
|
-
yield* settlePendingReplies(pendingReplies, actorId);
|
|
357
|
-
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
358
|
-
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
if (drain.stateChanged) drainTriggered = true;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
/**
|
|
367
|
-
* Settle all reply-bearing entries in the postpone buffer on shutdown.
|
|
368
|
-
* Call entries already had their Deferred settled with the postponed result
|
|
369
|
-
* (so their pendingReplies entry is already removed). Ask/send entries
|
|
370
|
-
* with Deferreds are settled via the pendingReplies registry.
|
|
371
|
-
*/
|
|
372
|
-
const settlePostponedBuffer = (postponed, _pendingReplies, _actorId) => {
|
|
373
|
-
postponed.length = 0;
|
|
374
|
-
};
|
|
375
|
-
/**
|
|
376
|
-
* Process a single event, returning true if the actor should stop.
|
|
377
|
-
* Wraps processEventCore with actor-specific concerns (inspection, listeners, state ref).
|
|
378
|
-
*/
|
|
379
|
-
const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system) {
|
|
380
|
-
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
381
|
-
type: "@machine.event",
|
|
382
|
-
actorId,
|
|
383
|
-
state: currentState,
|
|
384
|
-
event,
|
|
385
|
-
timestamp
|
|
386
|
-
}));
|
|
387
|
-
const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, actorId, inspector === void 0 ? void 0 : {
|
|
388
|
-
onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
389
|
-
type: "@machine.effect",
|
|
390
|
-
actorId,
|
|
391
|
-
effectType: "spawn",
|
|
392
|
-
state,
|
|
393
|
-
timestamp
|
|
394
|
-
})),
|
|
395
|
-
onTransition: (from, to, ev) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
396
|
-
type: "@machine.transition",
|
|
397
|
-
actorId,
|
|
398
|
-
fromState: from,
|
|
399
|
-
toState: to,
|
|
400
|
-
event: ev,
|
|
401
|
-
timestamp
|
|
402
|
-
})),
|
|
403
|
-
onError: (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
404
|
-
type: "@machine.error",
|
|
405
|
-
actorId,
|
|
406
|
-
phase: info.phase,
|
|
407
|
-
state: info.state,
|
|
408
|
-
event: info.event,
|
|
409
|
-
error: Cause.pretty(info.cause),
|
|
410
|
-
timestamp
|
|
411
|
-
}))
|
|
412
|
-
});
|
|
413
|
-
if (!result.transitioned) {
|
|
414
|
-
yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", false);
|
|
415
|
-
return {
|
|
416
|
-
shouldStop: false,
|
|
417
|
-
result
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
|
|
421
|
-
yield* SubscriptionRef.set(stateRef, result.newState);
|
|
422
|
-
notifyListeners(listeners, result.newState);
|
|
423
|
-
if (result.lifecycleRan) {
|
|
424
|
-
yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
|
|
425
|
-
yield* Effect.annotateCurrentSpan("effect_machine.state.to", result.newState._tag);
|
|
426
|
-
if (result.isFinal) {
|
|
427
|
-
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
428
|
-
type: "@machine.stop",
|
|
429
|
-
actorId,
|
|
430
|
-
finalState: result.newState,
|
|
431
|
-
timestamp
|
|
432
|
-
}));
|
|
433
|
-
return {
|
|
434
|
-
shouldStop: true,
|
|
435
|
-
result
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
shouldStop: false,
|
|
441
|
-
result
|
|
442
|
-
};
|
|
443
|
-
});
|
|
444
|
-
/**
|
|
445
|
-
* Run spawn effects with actor-specific inspection and tracing.
|
|
446
|
-
* Wraps the core runSpawnEffects with inspection events and spans.
|
|
447
|
-
* @internal
|
|
448
|
-
*/
|
|
449
|
-
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector, system) {
|
|
450
|
-
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
451
|
-
type: "@machine.effect",
|
|
452
|
-
actorId,
|
|
453
|
-
effectType: "spawn",
|
|
454
|
-
state,
|
|
455
|
-
timestamp
|
|
456
|
-
}));
|
|
457
|
-
yield* runSpawnEffects(machine, state, event, self, stateScope, system, actorId, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
458
|
-
type: "@machine.error",
|
|
459
|
-
actorId,
|
|
460
|
-
phase: info.phase,
|
|
461
|
-
state: info.state,
|
|
462
|
-
event: info.event,
|
|
463
|
-
error: Cause.pretty(info.cause),
|
|
464
|
-
timestamp
|
|
465
|
-
})));
|
|
466
|
-
});
|
|
467
360
|
/** Notify all system event listeners (sync). */
|
|
468
361
|
const notifySystemListeners = (listeners, event) => {
|
|
469
362
|
for (const listener of listeners) try {
|
|
@@ -502,7 +395,8 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
502
395
|
yield* emitSystemEvent({
|
|
503
396
|
_tag: "ActorStopped",
|
|
504
397
|
id,
|
|
505
|
-
actor: actorRef
|
|
398
|
+
actor: actorRef,
|
|
399
|
+
exit: { _tag: "Stopped" }
|
|
506
400
|
});
|
|
507
401
|
MutableHashMap.remove(actorsMap, id);
|
|
508
402
|
}
|
|
@@ -510,11 +404,23 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
510
404
|
}));
|
|
511
405
|
return actor;
|
|
512
406
|
});
|
|
513
|
-
const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id,
|
|
407
|
+
const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id, machine, spawnOptions) {
|
|
514
408
|
if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
|
|
515
|
-
|
|
409
|
+
let actorRef;
|
|
410
|
+
const actor = yield* createActor(id, machine, {
|
|
411
|
+
supervision: spawnOptions?.supervision,
|
|
412
|
+
onRestart: spawnOptions?.supervision !== void 0 ? (generation, exit) => actorRef !== void 0 ? emitSystemEvent({
|
|
413
|
+
_tag: "ActorRestarted",
|
|
414
|
+
id,
|
|
415
|
+
actor: actorRef,
|
|
416
|
+
generation,
|
|
417
|
+
exit
|
|
418
|
+
}) : Effect.void : void 0
|
|
419
|
+
});
|
|
420
|
+
actorRef = actor;
|
|
421
|
+
return yield* registerActor(id, actor);
|
|
516
422
|
});
|
|
517
|
-
const spawn = (id, machine) => withSpawnGate(spawnRegular(id, machine));
|
|
423
|
+
const spawn = (id, machine, options) => withSpawnGate(spawnRegular(id, machine, options));
|
|
518
424
|
const get = Effect.fn("effect-machine.actorSystem.get")(function* (id) {
|
|
519
425
|
return yield* Effect.sync(() => MutableHashMap.get(actorsMap, id));
|
|
520
426
|
});
|
|
@@ -526,7 +432,8 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
526
432
|
yield* emitSystemEvent({
|
|
527
433
|
_tag: "ActorStopped",
|
|
528
434
|
id,
|
|
529
|
-
actor
|
|
435
|
+
actor,
|
|
436
|
+
exit: { _tag: "Stopped" }
|
|
530
437
|
});
|
|
531
438
|
yield* actor.stop;
|
|
532
439
|
return true;
|