effect-machine 0.11.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 +52 -31
- package/dist/actor.js +218 -283
- package/dist/cluster/adapters/in-memory.d.ts +28 -0
- package/dist/cluster/adapters/in-memory.js +79 -0
- package/dist/cluster/entity-actor-ref.d.ts +56 -0
- package/dist/cluster/entity-actor-ref.js +33 -0
- package/dist/cluster/entity-machine.d.ts +31 -49
- package/dist/cluster/entity-machine.js +178 -52
- package/dist/cluster/index.d.ts +5 -2
- package/dist/cluster/index.js +4 -1
- package/dist/cluster/persistence.d.ts +49 -0
- package/dist/cluster/persistence.js +18 -0
- package/dist/cluster/to-entity.d.ts +9 -3
- package/dist/cluster/to-entity.js +16 -4
- package/dist/errors.d.ts +25 -17
- package/dist/errors.js +10 -5
- package/dist/index.d.ts +6 -4
- package/dist/index.js +4 -3
- package/dist/internal/brands.d.ts +14 -1
- package/dist/internal/runtime.d.ts +142 -0
- package/dist/internal/runtime.js +357 -0
- package/dist/internal/transition.d.ts +10 -4
- package/dist/internal/transition.js +24 -12
- package/dist/internal/utils.d.ts +42 -6
- package/dist/internal/utils.js +27 -1
- package/dist/machine.d.ts +89 -55
- package/dist/machine.js +80 -68
- package/dist/schema.d.ts +35 -34
- package/dist/schema.js +33 -4
- 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 -23
- package/package.json +7 -7
- package/v3/dist/actor.d.ts +54 -37
- package/v3/dist/actor.js +209 -277
- package/v3/dist/cluster/adapters/in-memory.d.ts +15 -0
- package/v3/dist/cluster/adapters/in-memory.js +62 -0
- package/v3/dist/cluster/entity-actor-ref.d.ts +49 -0
- package/v3/dist/cluster/entity-actor-ref.js +19 -0
- package/v3/dist/cluster/entity-machine.d.ts +34 -49
- package/v3/dist/cluster/entity-machine.js +134 -50
- package/v3/dist/cluster/index.d.ts +5 -2
- package/v3/dist/cluster/index.js +4 -1
- package/v3/dist/cluster/persistence.d.ts +48 -0
- package/v3/dist/cluster/persistence.js +14 -0
- package/v3/dist/cluster/to-entity.d.ts +5 -2
- package/v3/dist/cluster/to-entity.js +12 -4
- package/v3/dist/errors.d.ts +18 -8
- package/v3/dist/errors.js +9 -4
- package/v3/dist/index.d.ts +6 -4
- package/v3/dist/index.js +3 -2
- package/v3/dist/internal/brands.d.ts +15 -1
- package/v3/dist/internal/runtime.d.ts +142 -0
- package/v3/dist/internal/runtime.js +335 -0
- package/v3/dist/internal/transition.d.ts +10 -4
- package/v3/dist/internal/transition.js +23 -11
- package/v3/dist/internal/utils.d.ts +42 -6
- package/v3/dist/internal/utils.js +27 -1
- package/v3/dist/machine.d.ts +35 -47
- package/v3/dist/machine.js +62 -64
- package/v3/dist/schema.d.ts +35 -34
- package/v3/dist/schema.js +29 -3
- 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/v3/dist/actor.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ActorStoppedError, DuplicateActorError, NoReplyError } from "./errors.js";
|
|
1
|
+
import { ActorStoppedError, DuplicateActorError } 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 { Inspector } from "./inspection.js";
|
|
5
|
+
import { materializeMachine } from "./machine.js";
|
|
6
|
+
import { createRuntime } from "./internal/runtime.js";
|
|
7
|
+
import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Stream, SubscriptionRef } from "effect";
|
|
7
8
|
//#region src/actor.ts
|
|
8
9
|
/**
|
|
9
10
|
* Actor system: spawning, lifecycle, and event processing.
|
|
@@ -11,7 +12,7 @@ import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, O
|
|
|
11
12
|
* Combines:
|
|
12
13
|
* - ActorRef interface (running actor handle)
|
|
13
14
|
* - ActorSystem service (spawn/stop/get actors)
|
|
14
|
-
* - Actor creation
|
|
15
|
+
* - Actor creation (delegates to runtime kernel)
|
|
15
16
|
*/
|
|
16
17
|
/**
|
|
17
18
|
* ActorSystem service tag
|
|
@@ -26,12 +27,13 @@ const notifyListeners = (listeners, state) => {
|
|
|
26
27
|
} catch {}
|
|
27
28
|
};
|
|
28
29
|
/**
|
|
29
|
-
* Build core ActorRef methods
|
|
30
|
+
* Build core ActorRef methods.
|
|
30
31
|
*/
|
|
31
|
-
const buildActorRefCore = (id, machine, stateRef,
|
|
32
|
+
const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, listeners, stop, system, childrenMap, pendingReplies, transitionsPubSub, exitDeferred) => {
|
|
32
33
|
const send = Effect.fn("effect-machine.actor.send")(function* (event) {
|
|
33
34
|
if (yield* Ref.get(stoppedRef)) return;
|
|
34
|
-
yield*
|
|
35
|
+
const q = yield* Ref.get(eventQueueRef);
|
|
36
|
+
yield* Queue.offer(q, {
|
|
35
37
|
_tag: "send",
|
|
36
38
|
event
|
|
37
39
|
});
|
|
@@ -49,7 +51,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
49
51
|
}
|
|
50
52
|
const reply = yield* Deferred.make();
|
|
51
53
|
pendingReplies.add(reply);
|
|
52
|
-
yield*
|
|
54
|
+
const q = yield* Ref.get(eventQueueRef);
|
|
55
|
+
yield* Queue.offer(q, {
|
|
53
56
|
_tag: "call",
|
|
54
57
|
event,
|
|
55
58
|
reply
|
|
@@ -66,7 +69,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
66
69
|
if (yield* Ref.get(stoppedRef)) return yield* new ActorStoppedError({ actorId: id });
|
|
67
70
|
const reply = yield* Deferred.make();
|
|
68
71
|
pendingReplies.add(reply);
|
|
69
|
-
yield*
|
|
72
|
+
const q = yield* Ref.get(eventQueueRef);
|
|
73
|
+
yield* Queue.offer(q, {
|
|
70
74
|
_tag: "ask",
|
|
71
75
|
event,
|
|
72
76
|
reply
|
|
@@ -85,10 +89,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
85
89
|
const current = yield* SubscriptionRef.get(stateRef);
|
|
86
90
|
if (predicate(current)) return current;
|
|
87
91
|
const done = yield* Deferred.make();
|
|
88
|
-
const rt = yield* Effect.runtime();
|
|
89
|
-
const runFork = Runtime.runFork(rt);
|
|
90
92
|
const listener = (state) => {
|
|
91
|
-
if (predicate(state)) runFork(Deferred.succeed(done, state));
|
|
93
|
+
if (predicate(state)) Effect.runFork(Deferred.succeed(done, state));
|
|
92
94
|
};
|
|
93
95
|
listeners.add(listener);
|
|
94
96
|
const afterSubscribe = yield* SubscriptionRef.get(stateRef);
|
|
@@ -128,12 +130,27 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
128
130
|
listeners.delete(fn);
|
|
129
131
|
};
|
|
130
132
|
},
|
|
133
|
+
awaitExit: Deferred.await(exitDeferred),
|
|
134
|
+
watch: (other) => other.awaitExit,
|
|
135
|
+
drain: Effect.gen(function* () {
|
|
136
|
+
if (yield* Ref.get(stoppedRef)) return;
|
|
137
|
+
const q = yield* Ref.get(eventQueueRef);
|
|
138
|
+
const done = yield* Deferred.make();
|
|
139
|
+
yield* Queue.offer(q, {
|
|
140
|
+
_tag: "drain",
|
|
141
|
+
done
|
|
142
|
+
});
|
|
143
|
+
yield* Deferred.await(done);
|
|
144
|
+
}).pipe(Effect.asVoid),
|
|
131
145
|
sync: {
|
|
132
146
|
send: (event) => {
|
|
133
|
-
if (!Effect.runSync(Ref.get(stoppedRef)))
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
if (!Effect.runSync(Ref.get(stoppedRef))) {
|
|
148
|
+
const q = Effect.runSync(Ref.get(eventQueueRef));
|
|
149
|
+
Effect.runSync(Queue.offer(q, {
|
|
150
|
+
_tag: "send",
|
|
151
|
+
event
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
137
154
|
},
|
|
138
155
|
stop: () => Effect.runFork(stop),
|
|
139
156
|
snapshot: () => Effect.runSync(SubscriptionRef.get(stateRef)),
|
|
@@ -147,11 +164,13 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
147
164
|
};
|
|
148
165
|
};
|
|
149
166
|
/**
|
|
150
|
-
* Create and start an actor for a machine
|
|
167
|
+
* Create and start an actor for a machine.
|
|
168
|
+
* Uses the shared runtime kernel with lifecycle hooks for actor-specific concerns.
|
|
151
169
|
*/
|
|
152
170
|
const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine, options) {
|
|
153
171
|
const initial = options?.initialState ?? machine.initial;
|
|
154
172
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
|
|
173
|
+
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
|
|
155
174
|
const existingSystem = yield* Effect.serviceOption(ActorSystem);
|
|
156
175
|
let system;
|
|
157
176
|
let implicitSystemScope;
|
|
@@ -162,286 +181,180 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
162
181
|
implicitSystemScope = scope;
|
|
163
182
|
}
|
|
164
183
|
const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
|
|
165
|
-
const eventQueue = yield* Queue.unbounded();
|
|
166
|
-
const stoppedRef = yield* Ref.make(false);
|
|
167
184
|
const childrenMap = /* @__PURE__ */ new Map();
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
_tag: "send",
|
|
172
|
-
event
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
const self = {
|
|
176
|
-
send: selfSend,
|
|
177
|
-
cast: selfSend,
|
|
178
|
-
spawn: (childId, childMachine) => Effect.gen(function* () {
|
|
179
|
-
const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
|
|
180
|
-
childrenMap.set(childId, child);
|
|
181
|
-
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
182
|
-
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
|
|
183
|
-
childrenMap.delete(childId);
|
|
184
|
-
}));
|
|
185
|
-
return child;
|
|
186
|
-
})
|
|
187
|
-
};
|
|
188
|
-
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
|
|
185
|
+
const pendingReplies = /* @__PURE__ */ new Set();
|
|
186
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
187
|
+
const transitionsPubSub = yield* PubSub.unbounded();
|
|
189
188
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
190
189
|
type: "@machine.spawn",
|
|
191
190
|
actorId: id,
|
|
192
191
|
initialState: initial,
|
|
193
192
|
timestamp
|
|
194
193
|
}));
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
const backgroundFibers = [];
|
|
198
|
-
const initEvent = { _tag: INTERNAL_INIT_EVENT };
|
|
199
|
-
const ctx = {
|
|
200
|
-
actorId: id,
|
|
201
|
-
state: initial,
|
|
202
|
-
event: initEvent,
|
|
203
|
-
self,
|
|
204
|
-
system
|
|
205
|
-
};
|
|
206
|
-
const { effects: effectSlots } = machine._slots;
|
|
207
|
-
for (const bg of machine.backgroundEffects) {
|
|
208
|
-
const fiber = yield* Effect.forkDaemon(bg.handler({
|
|
209
|
-
actorId: id,
|
|
210
|
-
state: initial,
|
|
211
|
-
event: initEvent,
|
|
212
|
-
self,
|
|
213
|
-
effects: effectSlots,
|
|
214
|
-
system
|
|
215
|
-
}).pipe(Effect.provideService(machine.Context, ctx)));
|
|
216
|
-
backgroundFibers.push(fiber);
|
|
217
|
-
}
|
|
218
|
-
const stateScopeRef = { current: yield* Scope.make() };
|
|
219
|
-
yield* runSpawnEffectsWithInspection(machine, initial, initEvent, self, stateScopeRef.current, id, inspectorValue, system);
|
|
220
|
-
if (machine.finalStates.has(initial._tag)) {
|
|
221
|
-
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
222
|
-
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
223
|
-
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
224
|
-
type: "@machine.stop",
|
|
225
|
-
actorId: id,
|
|
226
|
-
finalState: initial,
|
|
227
|
-
timestamp
|
|
228
|
-
}));
|
|
229
|
-
yield* Ref.set(stoppedRef, true);
|
|
230
|
-
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
231
|
-
return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap, /* @__PURE__ */ new Set());
|
|
232
|
-
}
|
|
233
|
-
const pendingReplies = /* @__PURE__ */ new Set();
|
|
234
|
-
const transitionsPubSub = yield* PubSub.unbounded();
|
|
235
|
-
const loopFiber = yield* Effect.forkDaemon(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue, system, pendingReplies, transitionsPubSub));
|
|
236
|
-
return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
|
|
237
|
-
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
238
|
-
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
239
|
-
type: "@machine.stop",
|
|
240
|
-
actorId: id,
|
|
241
|
-
finalState,
|
|
242
|
-
timestamp
|
|
243
|
-
}));
|
|
244
|
-
yield* Ref.set(stoppedRef, true);
|
|
245
|
-
yield* Fiber.interrupt(loopFiber);
|
|
246
|
-
yield* settlePendingReplies(pendingReplies, id);
|
|
247
|
-
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
248
|
-
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
249
|
-
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
250
|
-
}).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap, pendingReplies, transitionsPubSub);
|
|
251
|
-
});
|
|
252
|
-
/** Fail all pending call/ask Deferreds with ActorStoppedError. Safe to call multiple times. */
|
|
253
|
-
const settlePendingReplies = (pendingReplies, actorId) => Effect.sync(() => {
|
|
254
|
-
const error = new ActorStoppedError({ actorId });
|
|
255
|
-
for (const deferred of pendingReplies) Effect.runFork(Deferred.fail(deferred, error));
|
|
256
|
-
pendingReplies.clear();
|
|
257
|
-
});
|
|
258
|
-
/**
|
|
259
|
-
* Main event loop for the actor.
|
|
260
|
-
* Includes postpone buffer — events matching postpone rules are buffered
|
|
261
|
-
* and drained after state tag changes (gen_statem semantics).
|
|
262
|
-
*/
|
|
263
|
-
const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, actorId, inspector, system, pendingReplies, transitionsPubSub) {
|
|
264
|
-
const postponed = [];
|
|
265
|
-
const hasPostponeRules = machine.postponeRules.length > 0;
|
|
266
|
-
const processQueued = Effect.fn("effect-machine.actor.processQueued")(function* (queued) {
|
|
267
|
-
const event = queued.event;
|
|
268
|
-
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
269
|
-
if (hasPostponeRules && shouldPostpone(machine, currentState._tag, event._tag)) {
|
|
270
|
-
postponed.push(queued);
|
|
271
|
-
if (queued._tag === "call") {
|
|
272
|
-
const postponedResult = {
|
|
273
|
-
newState: currentState,
|
|
274
|
-
previousState: currentState,
|
|
275
|
-
transitioned: false,
|
|
276
|
-
lifecycleRan: false,
|
|
277
|
-
isFinal: false,
|
|
278
|
-
hasReply: false,
|
|
279
|
-
reply: void 0,
|
|
280
|
-
postponed: true
|
|
281
|
-
};
|
|
282
|
-
yield* Deferred.succeed(queued.reply, postponedResult);
|
|
283
|
-
}
|
|
284
|
-
return {
|
|
285
|
-
shouldStop: false,
|
|
286
|
-
stateChanged: false
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
const { shouldStop, result } = yield* Effect.withSpan("effect-machine.event.process", { attributes: {
|
|
290
|
-
"effect_machine.actor.id": actorId,
|
|
291
|
-
"effect_machine.state.current": currentState._tag,
|
|
292
|
-
"effect_machine.event.type": event._tag
|
|
293
|
-
} })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system));
|
|
294
|
-
switch (queued._tag) {
|
|
295
|
-
case "call":
|
|
296
|
-
yield* Deferred.succeed(queued.reply, result);
|
|
297
|
-
break;
|
|
298
|
-
case "ask":
|
|
299
|
-
if (result.hasReply) yield* Deferred.succeed(queued.reply, result.reply);
|
|
300
|
-
else yield* Deferred.fail(queued.reply, new NoReplyError({
|
|
301
|
-
actorId,
|
|
302
|
-
eventTag: event._tag
|
|
303
|
-
}));
|
|
304
|
-
break;
|
|
305
|
-
}
|
|
306
|
-
if (result.transitioned) yield* PubSub.publish(transitionsPubSub, {
|
|
307
|
-
fromState: result.previousState,
|
|
308
|
-
toState: result.newState,
|
|
309
|
-
event
|
|
310
|
-
});
|
|
311
|
-
return {
|
|
312
|
-
shouldStop,
|
|
313
|
-
stateChanged: result.lifecycleRan
|
|
314
|
-
};
|
|
315
|
-
});
|
|
316
|
-
while (true) {
|
|
317
|
-
const { shouldStop, stateChanged } = yield* processQueued(yield* Queue.take(eventQueue));
|
|
318
|
-
if (shouldStop) {
|
|
319
|
-
yield* Ref.set(stoppedRef, true);
|
|
320
|
-
settlePostponedBuffer(postponed, pendingReplies, actorId);
|
|
321
|
-
yield* settlePendingReplies(pendingReplies, actorId);
|
|
322
|
-
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
323
|
-
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
let drainTriggered = stateChanged;
|
|
327
|
-
while (drainTriggered && postponed.length > 0) {
|
|
328
|
-
drainTriggered = false;
|
|
329
|
-
const drained = postponed.splice(0);
|
|
330
|
-
for (const entry of drained) {
|
|
331
|
-
const drain = yield* processQueued(entry);
|
|
332
|
-
if (drain.shouldStop) {
|
|
333
|
-
yield* Ref.set(stoppedRef, true);
|
|
334
|
-
settlePostponedBuffer(postponed, pendingReplies, actorId);
|
|
335
|
-
yield* settlePendingReplies(pendingReplies, actorId);
|
|
336
|
-
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
337
|
-
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
if (drain.stateChanged) drainTriggered = true;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
/**
|
|
346
|
-
* Settle all reply-bearing entries in the postpone buffer on shutdown.
|
|
347
|
-
* Call entries already had their Deferred settled with the postponed result
|
|
348
|
-
* (so their pendingReplies entry is already removed). Ask/send entries
|
|
349
|
-
* with Deferreds are settled via the pendingReplies registry.
|
|
350
|
-
*/
|
|
351
|
-
const settlePostponedBuffer = (postponed, _pendingReplies, _actorId) => {
|
|
352
|
-
postponed.length = 0;
|
|
353
|
-
};
|
|
354
|
-
/**
|
|
355
|
-
* Process a single event, returning true if the actor should stop.
|
|
356
|
-
* Wraps processEventCore with actor-specific concerns (inspection, listeners, state ref).
|
|
357
|
-
*/
|
|
358
|
-
const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system) {
|
|
359
|
-
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
360
|
-
type: "@machine.event",
|
|
361
|
-
actorId,
|
|
362
|
-
state: currentState,
|
|
363
|
-
event,
|
|
364
|
-
timestamp
|
|
365
|
-
}));
|
|
366
|
-
const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, actorId, inspector === void 0 ? void 0 : {
|
|
367
|
-
onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
194
|
+
const hooks = inspectorValue === void 0 ? void 0 : {
|
|
195
|
+
onSpawnEffect: (state) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
368
196
|
type: "@machine.effect",
|
|
369
|
-
actorId,
|
|
197
|
+
actorId: id,
|
|
370
198
|
effectType: "spawn",
|
|
371
199
|
state,
|
|
372
200
|
timestamp
|
|
373
201
|
})),
|
|
374
|
-
onTransition: (from, to, ev) => emitWithTimestamp(
|
|
202
|
+
onTransition: (from, to, ev) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
375
203
|
type: "@machine.transition",
|
|
376
|
-
actorId,
|
|
204
|
+
actorId: id,
|
|
377
205
|
fromState: from,
|
|
378
206
|
toState: to,
|
|
379
207
|
event: ev,
|
|
380
208
|
timestamp
|
|
381
209
|
})),
|
|
382
|
-
onError: (info) => emitWithTimestamp(
|
|
210
|
+
onError: (info) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
383
211
|
type: "@machine.error",
|
|
384
|
-
actorId,
|
|
212
|
+
actorId: id,
|
|
385
213
|
phase: info.phase,
|
|
386
214
|
state: info.state,
|
|
387
215
|
event: info.event,
|
|
388
216
|
error: Cause.pretty(info.cause),
|
|
389
217
|
timestamp
|
|
390
218
|
}))
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
219
|
+
};
|
|
220
|
+
const machineWithState = initial !== machine.initial ? Object.create(machine, { initial: {
|
|
221
|
+
value: initial,
|
|
222
|
+
enumerable: true
|
|
223
|
+
} }) : machine;
|
|
224
|
+
const stateRef = yield* SubscriptionRef.make(initial);
|
|
225
|
+
const stoppedRef = yield* Ref.make(false);
|
|
226
|
+
const initialQueue = yield* Queue.unbounded();
|
|
227
|
+
const eventQueueRef = yield* Ref.make(initialQueue);
|
|
228
|
+
const terminalExitDeferred = yield* Deferred.make();
|
|
229
|
+
let stopEmitted = false;
|
|
230
|
+
const runtimeRef = { current: void 0 };
|
|
231
|
+
/** Build lifecycle hooks for a generation */
|
|
232
|
+
const buildLifecycle = () => {
|
|
233
|
+
stopEmitted = false;
|
|
394
234
|
return {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
yield* SubscriptionRef.set(stateRef, result.newState);
|
|
401
|
-
notifyListeners(listeners, result.newState);
|
|
402
|
-
if (result.lifecycleRan) {
|
|
403
|
-
yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
|
|
404
|
-
yield* Effect.annotateCurrentSpan("effect_machine.state.to", result.newState._tag);
|
|
405
|
-
if (result.isFinal) {
|
|
406
|
-
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
407
|
-
type: "@machine.stop",
|
|
408
|
-
actorId,
|
|
409
|
-
finalState: result.newState,
|
|
235
|
+
onEvent: inspectorValue !== void 0 ? (state, event) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
236
|
+
type: "@machine.event",
|
|
237
|
+
actorId: id,
|
|
238
|
+
state,
|
|
239
|
+
event,
|
|
410
240
|
timestamp
|
|
241
|
+
})) : void 0,
|
|
242
|
+
onStateChange: (result, _event) => Effect.gen(function* () {
|
|
243
|
+
notifyListeners(listeners, result.newState);
|
|
244
|
+
yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
|
|
245
|
+
if (result.lifecycleRan) {
|
|
246
|
+
yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
|
|
247
|
+
yield* Effect.annotateCurrentSpan("effect_machine.state.to", result.newState._tag);
|
|
248
|
+
}
|
|
249
|
+
}),
|
|
250
|
+
onProcessed: (result, event) => result.transitioned ? PubSub.publish(transitionsPubSub, {
|
|
251
|
+
fromState: result.previousState,
|
|
252
|
+
toState: result.newState,
|
|
253
|
+
event
|
|
254
|
+
}).pipe(Effect.asVoid) : Effect.void,
|
|
255
|
+
onFinal: inspectorValue !== void 0 ? (state) => Effect.gen(function* () {
|
|
256
|
+
stopEmitted = true;
|
|
257
|
+
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
258
|
+
type: "@machine.stop",
|
|
259
|
+
actorId: id,
|
|
260
|
+
finalState: state,
|
|
261
|
+
timestamp
|
|
262
|
+
}));
|
|
263
|
+
}) : void 0,
|
|
264
|
+
onShutdown: () => Effect.gen(function* () {
|
|
265
|
+
if (!stopEmitted) {
|
|
266
|
+
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
267
|
+
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
268
|
+
type: "@machine.stop",
|
|
269
|
+
actorId: id,
|
|
270
|
+
finalState,
|
|
271
|
+
timestamp
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
yield* settlePendingReplies(pendingReplies, id);
|
|
275
|
+
}),
|
|
276
|
+
onInitialSpawnEffects: inspectorValue !== void 0 ? (state) => emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
277
|
+
type: "@machine.effect",
|
|
278
|
+
actorId: id,
|
|
279
|
+
effectType: "spawn",
|
|
280
|
+
state,
|
|
281
|
+
timestamp
|
|
282
|
+
})) : void 0
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
/** Create a single runtime generation */
|
|
286
|
+
const spawnGeneration = (machineForGen) => Ref.get(eventQueueRef).pipe(Effect.flatMap((currentQueue) => createRuntime(machineForGen, system, {
|
|
287
|
+
actorId: id,
|
|
288
|
+
hooks,
|
|
289
|
+
skipFinalizer: true,
|
|
290
|
+
cellResources: {
|
|
291
|
+
stateRef,
|
|
292
|
+
stoppedRef,
|
|
293
|
+
eventQueue: currentQueue
|
|
294
|
+
},
|
|
295
|
+
lifecycle: buildLifecycle(),
|
|
296
|
+
wrapProcess: (state, event, inner) => Effect.withSpan("effect-machine.event.process", { attributes: {
|
|
297
|
+
"effect_machine.actor.id": id,
|
|
298
|
+
"effect_machine.state.current": state._tag,
|
|
299
|
+
"effect_machine.event.type": event._tag
|
|
300
|
+
} })(inner.pipe(Effect.tap((r) => Effect.annotateCurrentSpan("effect_machine.transition.matched", r.result.transitioned)))),
|
|
301
|
+
onChildSpawned: (childId, child) => Effect.gen(function* () {
|
|
302
|
+
childrenMap.set(childId, child);
|
|
303
|
+
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
304
|
+
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
|
|
305
|
+
childrenMap.delete(childId);
|
|
411
306
|
}));
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
307
|
+
})
|
|
308
|
+
})));
|
|
309
|
+
const supervision = options?.supervision;
|
|
310
|
+
const runtime = yield* spawnGeneration(machineWithState);
|
|
311
|
+
runtimeRef.current = runtime;
|
|
312
|
+
let supervisorFiber;
|
|
313
|
+
if (supervision !== void 0) supervisorFiber = yield* Effect.forkDaemon(Effect.gen(function* () {
|
|
314
|
+
const driver = yield* Schedule.driver(supervision.schedule);
|
|
315
|
+
let generation = 0;
|
|
316
|
+
while (true) {
|
|
317
|
+
const currentRuntime = runtimeRef.current;
|
|
318
|
+
if (currentRuntime === void 0) return;
|
|
319
|
+
const generationExit = yield* Deferred.await(currentRuntime.exitDeferred);
|
|
320
|
+
if (generationExit._tag !== "Defect") {
|
|
321
|
+
yield* Deferred.succeed(terminalExitDeferred, generationExit);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (supervision.shouldRestart !== void 0 && !supervision.shouldRestart(generationExit)) {
|
|
325
|
+
yield* Deferred.succeed(terminalExitDeferred, generationExit);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if ((yield* driver.next(generationExit).pipe(Effect.exit))._tag === "Failure") {
|
|
329
|
+
yield* Deferred.succeed(terminalExitDeferred, generationExit);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
yield* settlePendingReplies(pendingReplies, id);
|
|
333
|
+
const freshQueue = yield* Queue.unbounded();
|
|
334
|
+
yield* Ref.set(eventQueueRef, freshQueue);
|
|
335
|
+
yield* SubscriptionRef.set(stateRef, machine.initial);
|
|
336
|
+
yield* Ref.set(stoppedRef, false);
|
|
337
|
+
childrenMap.clear();
|
|
338
|
+
runtimeRef.current = yield* spawnGeneration(machine);
|
|
339
|
+
generation++;
|
|
340
|
+
if (options?.onRestart !== void 0) yield* options.onRestart(generation, generationExit);
|
|
341
|
+
notifyListeners(listeners, machine.initial);
|
|
416
342
|
}
|
|
417
|
-
}
|
|
418
|
-
return {
|
|
419
|
-
shouldStop: false,
|
|
420
|
-
result
|
|
421
|
-
};
|
|
422
|
-
});
|
|
423
|
-
/**
|
|
424
|
-
* Run spawn effects with actor-specific inspection and tracing.
|
|
425
|
-
* Wraps the core runSpawnEffects with inspection events and spans.
|
|
426
|
-
* @internal
|
|
427
|
-
*/
|
|
428
|
-
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector, system) {
|
|
429
|
-
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
430
|
-
type: "@machine.effect",
|
|
431
|
-
actorId,
|
|
432
|
-
effectType: "spawn",
|
|
433
|
-
state,
|
|
434
|
-
timestamp
|
|
435
343
|
}));
|
|
436
|
-
yield*
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
344
|
+
else yield* Deferred.await(runtime.exitDeferred).pipe(Effect.tap((exit) => Deferred.succeed(terminalExitDeferred, exit)), Effect.catchAllCause(() => Effect.void), Effect.fork);
|
|
345
|
+
return buildActorRefCore(id, machine, stateRef, eventQueueRef, stoppedRef, listeners, Effect.gen(function* () {
|
|
346
|
+
if (supervisorFiber !== void 0) yield* Fiber.interrupt(supervisorFiber);
|
|
347
|
+
const currentRuntime = runtimeRef.current;
|
|
348
|
+
if (currentRuntime !== void 0) yield* currentRuntime.stop;
|
|
349
|
+
yield* Deferred.succeed(terminalExitDeferred, { _tag: "Stopped" });
|
|
350
|
+
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
351
|
+
}).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap, pendingReplies, transitionsPubSub, terminalExitDeferred);
|
|
352
|
+
});
|
|
353
|
+
/** Fail all pending call/ask Deferreds with ActorStoppedError. Safe to call multiple times. */
|
|
354
|
+
const settlePendingReplies = (pendingReplies, actorId) => Effect.sync(() => {
|
|
355
|
+
const error = new ActorStoppedError({ actorId });
|
|
356
|
+
for (const deferred of pendingReplies) Effect.runFork(Deferred.fail(deferred, error));
|
|
357
|
+
pendingReplies.clear();
|
|
445
358
|
});
|
|
446
359
|
/** Notify all system event listeners (sync). */
|
|
447
360
|
const notifySystemListeners = (listeners, event) => {
|
|
@@ -462,7 +375,6 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
462
375
|
});
|
|
463
376
|
return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.zipRight(PubSub.shutdown(eventPubSub)), Effect.asVoid);
|
|
464
377
|
});
|
|
465
|
-
/** Check for duplicate ID, register actor, attach scope cleanup if available */
|
|
466
378
|
const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* (id, actor) {
|
|
467
379
|
if (MutableHashMap.has(actorsMap, id)) {
|
|
468
380
|
yield* actor.stop;
|
|
@@ -481,7 +393,8 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
481
393
|
yield* emitSystemEvent({
|
|
482
394
|
_tag: "ActorStopped",
|
|
483
395
|
id,
|
|
484
|
-
actor: actorRef
|
|
396
|
+
actor: actorRef,
|
|
397
|
+
exit: { _tag: "Stopped" }
|
|
485
398
|
});
|
|
486
399
|
MutableHashMap.remove(actorsMap, id);
|
|
487
400
|
}
|
|
@@ -489,11 +402,24 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
489
402
|
}));
|
|
490
403
|
return actor;
|
|
491
404
|
});
|
|
492
|
-
const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id,
|
|
405
|
+
const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id, machine, spawnOptions) {
|
|
493
406
|
if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
|
|
494
|
-
|
|
407
|
+
const materialized = materializeMachine(machine, spawnOptions?.slots);
|
|
408
|
+
let actorRef;
|
|
409
|
+
const actor = yield* createActor(id, materialized, {
|
|
410
|
+
supervision: spawnOptions?.supervision,
|
|
411
|
+
onRestart: spawnOptions?.supervision !== void 0 ? (generation, exit) => actorRef !== void 0 ? emitSystemEvent({
|
|
412
|
+
_tag: "ActorRestarted",
|
|
413
|
+
id,
|
|
414
|
+
actor: actorRef,
|
|
415
|
+
generation,
|
|
416
|
+
exit
|
|
417
|
+
}) : Effect.void : void 0
|
|
418
|
+
});
|
|
419
|
+
actorRef = actor;
|
|
420
|
+
return yield* registerActor(id, actor);
|
|
495
421
|
});
|
|
496
|
-
const spawn = (id, machine) => withSpawnGate(spawnRegular(id, machine));
|
|
422
|
+
const spawn = (id, machine, options) => withSpawnGate(spawnRegular(id, machine, options));
|
|
497
423
|
const get = Effect.fn("effect-machine.actorSystem.get")(function* (id) {
|
|
498
424
|
return yield* Effect.sync(() => MutableHashMap.get(actorsMap, id));
|
|
499
425
|
});
|
|
@@ -505,7 +431,8 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
505
431
|
yield* emitSystemEvent({
|
|
506
432
|
_tag: "ActorStopped",
|
|
507
433
|
id,
|
|
508
|
-
actor
|
|
434
|
+
actor,
|
|
435
|
+
exit: { _tag: "Stopped" }
|
|
509
436
|
});
|
|
510
437
|
yield* actor.stop;
|
|
511
438
|
return true;
|
|
@@ -531,8 +458,13 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
531
458
|
});
|
|
532
459
|
});
|
|
533
460
|
/**
|
|
461
|
+
* Create an ActorSystem instance. Must be run in a Scope.
|
|
462
|
+
* @internal — use Default layer for normal usage
|
|
463
|
+
*/
|
|
464
|
+
const makeSystem = make;
|
|
465
|
+
/**
|
|
534
466
|
* Default ActorSystem layer
|
|
535
467
|
*/
|
|
536
|
-
const Default = Layer.
|
|
468
|
+
const Default = Layer.effect(ActorSystem, make());
|
|
537
469
|
//#endregion
|
|
538
|
-
export { ActorSystem, Default, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
|
|
470
|
+
export { ActorSystem, Default, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PersistedEvent, PersistenceAdapter, Snapshot } from "../persistence.js";
|
|
2
|
+
import { Effect, Layer, Ref } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region src/cluster/adapters/in-memory.d.ts
|
|
5
|
+
interface EntityStore {
|
|
6
|
+
snapshot: Snapshot<unknown> | undefined;
|
|
7
|
+
events: Array<PersistedEvent<unknown>>;
|
|
8
|
+
}
|
|
9
|
+
declare const makeInMemoryPersistenceAdapter: Effect.Effect<{
|
|
10
|
+
adapter: PersistenceAdapter;
|
|
11
|
+
storeRef: Ref.Ref<Map<string, EntityStore>>;
|
|
12
|
+
layer: Layer.Layer<PersistenceAdapter, never, never>;
|
|
13
|
+
}, never, never>;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { makeInMemoryPersistenceAdapter };
|