effect-machine 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +118 -55
  2. package/dist/actor.d.ts +77 -179
  3. package/dist/actor.js +161 -113
  4. package/dist/cluster/entity-machine.js +5 -3
  5. package/dist/errors.d.ts +12 -1
  6. package/dist/errors.js +8 -1
  7. package/dist/index.d.ts +4 -8
  8. package/dist/index.js +2 -7
  9. package/dist/internal/transition.d.ts +27 -3
  10. package/dist/internal/transition.js +38 -9
  11. package/dist/internal/utils.d.ts +7 -2
  12. package/dist/internal/utils.js +1 -5
  13. package/dist/machine.d.ts +94 -35
  14. package/dist/machine.js +128 -13
  15. package/dist/testing.js +57 -3
  16. package/package.json +10 -9
  17. package/v3/dist/actor.d.ts +210 -0
  18. package/{dist-v3 → v3/dist}/actor.js +198 -117
  19. package/{dist-v3 → v3/dist}/cluster/entity-machine.d.ts +1 -1
  20. package/{dist-v3 → v3/dist}/cluster/entity-machine.js +8 -6
  21. package/{dist-v3 → v3/dist}/cluster/to-entity.d.ts +1 -1
  22. package/{dist-v3 → v3/dist}/cluster/to-entity.js +1 -1
  23. package/v3/dist/errors.d.ts +76 -0
  24. package/{dist-v3 → v3/dist}/errors.js +9 -2
  25. package/v3/dist/index.d.ts +9 -0
  26. package/v3/dist/index.js +8 -0
  27. package/{dist-v3 → v3/dist}/inspection.d.ts +53 -8
  28. package/v3/dist/inspection.js +156 -0
  29. package/{dist-v3 → v3/dist}/internal/brands.d.ts +1 -1
  30. package/{dist-v3 → v3/dist}/internal/inspection.d.ts +1 -1
  31. package/v3/dist/internal/inspection.js +20 -0
  32. package/{dist-v3 → v3/dist}/internal/transition.d.ts +35 -11
  33. package/{dist-v3 → v3/dist}/internal/transition.js +47 -15
  34. package/{dist-v3 → v3/dist}/internal/utils.d.ts +9 -4
  35. package/{dist-v3 → v3/dist}/internal/utils.js +2 -6
  36. package/{dist-v3 → v3/dist}/machine.d.ts +113 -40
  37. package/{dist-v3 → v3/dist}/machine.js +191 -15
  38. package/{dist-v3 → v3/dist}/schema.d.ts +1 -1
  39. package/{dist-v3 → v3/dist}/schema.js +5 -2
  40. package/{dist-v3 → v3/dist}/slot.d.ts +4 -3
  41. package/{dist-v3 → v3/dist}/slot.js +1 -1
  42. package/{dist-v3 → v3/dist}/testing.d.ts +14 -8
  43. package/{dist-v3 → v3/dist}/testing.js +60 -6
  44. package/dist/persistence/adapter.d.ts +0 -135
  45. package/dist/persistence/adapter.js +0 -25
  46. package/dist/persistence/adapters/in-memory.d.ts +0 -32
  47. package/dist/persistence/adapters/in-memory.js +0 -174
  48. package/dist/persistence/index.d.ts +0 -5
  49. package/dist/persistence/index.js +0 -5
  50. package/dist/persistence/persistent-actor.d.ts +0 -50
  51. package/dist/persistence/persistent-actor.js +0 -368
  52. package/dist/persistence/persistent-machine.d.ts +0 -105
  53. package/dist/persistence/persistent-machine.js +0 -22
  54. package/dist-v3/actor.d.ts +0 -291
  55. package/dist-v3/errors.d.ts +0 -27
  56. package/dist-v3/index.d.ts +0 -12
  57. package/dist-v3/index.js +0 -13
  58. package/dist-v3/inspection.js +0 -48
  59. package/dist-v3/internal/inspection.js +0 -13
  60. package/dist-v3/persistence/adapter.d.ts +0 -125
  61. package/dist-v3/persistence/adapter.js +0 -25
  62. package/dist-v3/persistence/adapters/in-memory.d.ts +0 -32
  63. package/dist-v3/persistence/adapters/in-memory.js +0 -174
  64. package/dist-v3/persistence/index.d.ts +0 -5
  65. package/dist-v3/persistence/index.js +0 -5
  66. package/dist-v3/persistence/persistent-actor.d.ts +0 -49
  67. package/dist-v3/persistence/persistent-actor.js +0 -365
  68. package/dist-v3/persistence/persistent-machine.d.ts +0 -105
  69. package/dist-v3/persistence/persistent-machine.js +0 -22
  70. /package/{dist-v3 → v3/dist}/_virtual/_rolldown/runtime.js +0 -0
  71. /package/{dist-v3 → v3/dist}/cluster/index.d.ts +0 -0
  72. /package/{dist-v3 → v3/dist}/cluster/index.js +0 -0
  73. /package/{dist-v3 → v3/dist}/internal/brands.js +0 -0
@@ -1,13 +1,10 @@
1
1
  import { Inspector } from "./inspection.js";
2
2
  import { INTERNAL_INIT_EVENT } from "./internal/utils.js";
3
- import { DuplicateActorError } from "./errors.js";
4
- import { isPersistentMachine } from "./persistence/persistent-machine.js";
5
- import { processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
3
+ import { ActorStoppedError, DuplicateActorError, NoReplyError } from "./errors.js";
6
4
  import { emitWithTimestamp } from "./internal/inspection.js";
7
- import { PersistenceAdapterTag, PersistenceError } from "./persistence/adapter.js";
8
- import { createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
5
+ import { processEventCore, resolveTransition, runSpawnEffects, shouldPostpone } from "./internal/transition.js";
9
6
  import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect";
10
- //#region src-v3/actor.ts
7
+ //#region src/actor.ts
11
8
  /**
12
9
  * Actor system: spawning, lifecycle, and event processing.
13
10
  *
@@ -31,10 +28,50 @@ const notifyListeners = (listeners, state) => {
31
28
  /**
32
29
  * Build core ActorRef methods shared between regular and persistent actors.
33
30
  */
34
- const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop, system, childrenMap) => {
31
+ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop, system, childrenMap, pendingReplies, transitionsPubSub) => {
35
32
  const send = Effect.fn("effect-machine.actor.send")(function* (event) {
36
33
  if (yield* Ref.get(stoppedRef)) return;
37
- yield* Queue.offer(eventQueue, event);
34
+ yield* Queue.offer(eventQueue, {
35
+ _tag: "send",
36
+ event
37
+ });
38
+ });
39
+ const call = Effect.fn("effect-machine.actor.call")(function* (event) {
40
+ if (yield* Ref.get(stoppedRef)) {
41
+ const currentState = yield* SubscriptionRef.get(stateRef);
42
+ return {
43
+ newState: currentState,
44
+ previousState: currentState,
45
+ transitioned: false,
46
+ lifecycleRan: false,
47
+ isFinal: machine.finalStates.has(currentState._tag)
48
+ };
49
+ }
50
+ const reply = yield* Deferred.make();
51
+ pendingReplies.add(reply);
52
+ yield* Queue.offer(eventQueue, {
53
+ _tag: "call",
54
+ event,
55
+ reply
56
+ });
57
+ return yield* Deferred.await(reply).pipe(Effect.ensuring(Effect.sync(() => pendingReplies.delete(reply))), Effect.catchTag("ActorStoppedError", () => SubscriptionRef.get(stateRef).pipe(Effect.map((currentState) => ({
58
+ newState: currentState,
59
+ previousState: currentState,
60
+ transitioned: false,
61
+ lifecycleRan: false,
62
+ isFinal: machine.finalStates.has(currentState._tag)
63
+ })))));
64
+ });
65
+ const ask = Effect.fn("effect-machine.actor.ask")(function* (event) {
66
+ if (yield* Ref.get(stoppedRef)) return yield* new ActorStoppedError({ actorId: id });
67
+ const reply = yield* Deferred.make();
68
+ pendingReplies.add(reply);
69
+ yield* Queue.offer(eventQueue, {
70
+ _tag: "ask",
71
+ event,
72
+ reply
73
+ });
74
+ return yield* Deferred.await(reply).pipe(Effect.ensuring(Effect.sync(() => pendingReplies.delete(reply))));
38
75
  });
39
76
  const snapshot = SubscriptionRef.get(stateRef).pipe(Effect.withSpan("effect-machine.actor.snapshot"));
40
77
  const matches = Effect.fn("effect-machine.actor.matches")(function* (tag) {
@@ -72,30 +109,39 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
72
109
  return {
73
110
  id,
74
111
  send,
112
+ cast: send,
113
+ call,
114
+ ask,
75
115
  state: stateRef,
76
116
  stop,
77
- stopSync: () => Effect.runFork(stop),
78
117
  snapshot,
79
- snapshotSync: () => Effect.runSync(SubscriptionRef.get(stateRef)),
80
118
  matches,
81
- matchesSync: (tag) => Effect.runSync(SubscriptionRef.get(stateRef))._tag === tag,
82
119
  can,
83
- canSync: (event) => {
84
- return resolveTransition(machine, Effect.runSync(SubscriptionRef.get(stateRef)), event) !== void 0;
85
- },
86
120
  changes: stateRef.changes,
121
+ transitions: transitionsPubSub !== void 0 ? Stream.fromPubSub(transitionsPubSub) : Stream.empty,
87
122
  waitFor,
88
123
  awaitFinal,
89
124
  sendAndWait,
90
- sendSync: (event) => {
91
- if (!Effect.runSync(Ref.get(stoppedRef))) Effect.runSync(Queue.offer(eventQueue, event));
92
- },
93
125
  subscribe: (fn) => {
94
126
  listeners.add(fn);
95
127
  return () => {
96
128
  listeners.delete(fn);
97
129
  };
98
130
  },
131
+ sync: {
132
+ send: (event) => {
133
+ if (!Effect.runSync(Ref.get(stoppedRef))) Effect.runSync(Queue.offer(eventQueue, {
134
+ _tag: "send",
135
+ event
136
+ }));
137
+ },
138
+ stop: () => Effect.runFork(stop),
139
+ snapshot: () => Effect.runSync(SubscriptionRef.get(stateRef)),
140
+ matches: (tag) => Effect.runSync(SubscriptionRef.get(stateRef))._tag === tag,
141
+ can: (event) => {
142
+ return resolveTransition(machine, Effect.runSync(SubscriptionRef.get(stateRef)), event) !== void 0;
143
+ }
144
+ },
99
145
  system,
100
146
  children: childrenMap
101
147
  };
@@ -103,7 +149,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
103
149
  /**
104
150
  * Create and start an actor for a machine
105
151
  */
106
- const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine) {
152
+ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine, options) {
153
+ const initial = options?.initialState ?? machine.initial;
107
154
  yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
108
155
  const existingSystem = yield* Effect.serviceOption(ActorSystem);
109
156
  let system;
@@ -118,11 +165,16 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
118
165
  const eventQueue = yield* Queue.unbounded();
119
166
  const stoppedRef = yield* Ref.make(false);
120
167
  const childrenMap = /* @__PURE__ */ new Map();
168
+ const selfSend = Effect.fn("effect-machine.actor.self.send")(function* (event) {
169
+ if (yield* Ref.get(stoppedRef)) return;
170
+ yield* Queue.offer(eventQueue, {
171
+ _tag: "send",
172
+ event
173
+ });
174
+ });
121
175
  const self = {
122
- send: Effect.fn("effect-machine.actor.self.send")(function* (event) {
123
- if (yield* Ref.get(stoppedRef)) return;
124
- yield* Queue.offer(eventQueue, event);
125
- }),
176
+ send: selfSend,
177
+ cast: selfSend,
126
178
  spawn: (childId, childMachine) => Effect.gen(function* () {
127
179
  const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
128
180
  childrenMap.set(childId, child);
@@ -133,19 +185,20 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
133
185
  return child;
134
186
  })
135
187
  };
136
- yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", machine.initial._tag);
188
+ yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
137
189
  yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
138
190
  type: "@machine.spawn",
139
191
  actorId: id,
140
- initialState: machine.initial,
192
+ initialState: initial,
141
193
  timestamp
142
194
  }));
143
- const stateRef = yield* SubscriptionRef.make(machine.initial);
195
+ const stateRef = yield* SubscriptionRef.make(initial);
144
196
  const listeners = /* @__PURE__ */ new Set();
145
197
  const backgroundFibers = [];
146
198
  const initEvent = { _tag: INTERNAL_INIT_EVENT };
147
199
  const ctx = {
148
- state: machine.initial,
200
+ actorId: id,
201
+ state: initial,
149
202
  event: initEvent,
150
203
  self,
151
204
  system
@@ -153,7 +206,8 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
153
206
  const { effects: effectSlots } = machine._slots;
154
207
  for (const bg of machine.backgroundEffects) {
155
208
  const fiber = yield* Effect.forkDaemon(bg.handler({
156
- state: machine.initial,
209
+ actorId: id,
210
+ state: initial,
157
211
  event: initEvent,
158
212
  self,
159
213
  effects: effectSlots,
@@ -162,21 +216,23 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
162
216
  backgroundFibers.push(fiber);
163
217
  }
164
218
  const stateScopeRef = { current: yield* Scope.make() };
165
- yield* runSpawnEffectsWithInspection(machine, machine.initial, initEvent, self, stateScopeRef.current, id, inspectorValue, system);
166
- if (machine.finalStates.has(machine.initial._tag)) {
219
+ yield* runSpawnEffectsWithInspection(machine, initial, initEvent, self, stateScopeRef.current, id, inspectorValue, system);
220
+ if (machine.finalStates.has(initial._tag)) {
167
221
  yield* Scope.close(stateScopeRef.current, Exit.void);
168
222
  yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
169
223
  yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
170
224
  type: "@machine.stop",
171
225
  actorId: id,
172
- finalState: machine.initial,
226
+ finalState: initial,
173
227
  timestamp
174
228
  }));
175
229
  yield* Ref.set(stoppedRef, true);
176
230
  if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
177
- return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap);
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());
178
232
  }
179
- const loopFiber = yield* Effect.forkDaemon(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue, system));
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));
180
236
  return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
181
237
  const finalState = yield* SubscriptionRef.get(stateRef);
182
238
  yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
@@ -187,31 +243,115 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
187
243
  }));
188
244
  yield* Ref.set(stoppedRef, true);
189
245
  yield* Fiber.interrupt(loopFiber);
246
+ yield* settlePendingReplies(pendingReplies, id);
190
247
  yield* Scope.close(stateScopeRef.current, Exit.void);
191
248
  yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
192
249
  if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
193
- }).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap);
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();
194
257
  });
195
258
  /**
196
- * Main event loop for the actor
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).
197
262
  */
198
- const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, actorId, inspector, system) {
199
- while (true) {
200
- const event = yield* Queue.take(eventQueue);
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;
201
268
  const currentState = yield* SubscriptionRef.get(stateRef);
202
- if (yield* Effect.withSpan("effect-machine.event.process", { attributes: {
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: {
203
290
  "effect_machine.actor.id": actorId,
204
291
  "effect_machine.state.current": currentState._tag,
205
292
  "effect_machine.event.type": event._tag
206
- } })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system))) {
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) {
207
319
  yield* Ref.set(stoppedRef, true);
320
+ settlePostponedBuffer(postponed, pendingReplies, actorId);
321
+ yield* settlePendingReplies(pendingReplies, actorId);
208
322
  yield* Scope.close(stateScopeRef.current, Exit.void);
209
323
  yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
210
324
  return;
211
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
+ }
212
343
  }
213
344
  });
214
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
+ /**
215
355
  * Process a single event, returning true if the actor should stop.
216
356
  * Wraps processEventCore with actor-specific concerns (inspection, listeners, state ref).
217
357
  */
@@ -223,7 +363,7 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
223
363
  event,
224
364
  timestamp
225
365
  }));
226
- const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, inspector === void 0 ? void 0 : {
366
+ const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, actorId, inspector === void 0 ? void 0 : {
227
367
  onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
228
368
  type: "@machine.effect",
229
369
  actorId,
@@ -251,7 +391,10 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
251
391
  });
252
392
  if (!result.transitioned) {
253
393
  yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", false);
254
- return false;
394
+ return {
395
+ shouldStop: false,
396
+ result
397
+ };
255
398
  }
256
399
  yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
257
400
  yield* SubscriptionRef.set(stateRef, result.newState);
@@ -266,10 +409,16 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
266
409
  finalState: result.newState,
267
410
  timestamp
268
411
  }));
269
- return true;
412
+ return {
413
+ shouldStop: true,
414
+ result
415
+ };
270
416
  }
271
417
  }
272
- return false;
418
+ return {
419
+ shouldStop: false,
420
+ result
421
+ };
273
422
  });
274
423
  /**
275
424
  * Run spawn effects with actor-specific inspection and tracing.
@@ -284,7 +433,7 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffec
284
433
  state,
285
434
  timestamp
286
435
  }));
287
- yield* runSpawnEffects(machine, state, event, self, stateScope, system, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
436
+ yield* runSpawnEffects(machine, state, event, self, stateScope, system, actorId, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
288
437
  type: "@machine.error",
289
438
  actorId,
290
439
  phase: info.phase,
@@ -305,13 +454,13 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
305
454
  const withSpawnGate = (yield* Effect.makeSemaphore(1)).withPermits(1);
306
455
  const eventPubSub = yield* PubSub.unbounded();
307
456
  const eventListeners = /* @__PURE__ */ new Set();
308
- const emitSystemEvent = (event) => Effect.sync(() => notifySystemListeners(eventListeners, event)).pipe(Effect.andThen(PubSub.publish(eventPubSub, event)), Effect.catchAllCause(() => Effect.void), Effect.asVoid);
457
+ const emitSystemEvent = (event) => Effect.sync(() => notifySystemListeners(eventListeners, event)).pipe(Effect.zipRight(PubSub.publish(eventPubSub, event)), Effect.catchAllCause(() => Effect.void), Effect.asVoid);
309
458
  yield* Effect.addFinalizer(() => {
310
459
  const stops = [];
311
460
  MutableHashMap.forEach(actorsMap, (actor) => {
312
461
  stops.push(actor.stop);
313
462
  });
314
- return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.andThen(PubSub.shutdown(eventPubSub)), Effect.asVoid);
463
+ return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.zipRight(PubSub.shutdown(eventPubSub)), Effect.asVoid);
315
464
  });
316
465
  /** Check for duplicate ID, register actor, attach scope cleanup if available */
317
466
  const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* (id, actor) {
@@ -344,25 +493,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
344
493
  if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
345
494
  return yield* registerActor(id, yield* createActor(id, built._inner));
346
495
  });
347
- const spawnPersistent = Effect.fn("effect-machine.actorSystem.spawnPersistent")(function* (id, persistentMachine) {
348
- if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
349
- const adapter = yield* PersistenceAdapterTag;
350
- const maybeSnapshot = yield* adapter.loadSnapshot(id, persistentMachine.persistence.stateSchema);
351
- return yield* registerActor(id, yield* createPersistentActor(id, persistentMachine, maybeSnapshot, yield* adapter.loadEvents(id, persistentMachine.persistence.eventSchema, Option.isSome(maybeSnapshot) ? maybeSnapshot.value.version : void 0)));
352
- });
353
- const spawnImpl = Effect.fn("effect-machine.actorSystem.spawn")(function* (id, machine) {
354
- if (isPersistentMachine(machine)) return yield* spawnPersistent(id, machine);
355
- return yield* spawnRegular(id, machine);
356
- });
357
- function spawn(id, machine) {
358
- return withSpawnGate(spawnImpl(id, machine));
359
- }
360
- const restoreImpl = Effect.fn("effect-machine.actorSystem.restore")(function* (id, persistentMachine) {
361
- const maybeActor = yield* restorePersistentActor(id, persistentMachine);
362
- if (Option.isSome(maybeActor)) yield* registerActor(id, maybeActor.value);
363
- return maybeActor;
364
- });
365
- const restore = (id, persistentMachine) => withSpawnGate(restoreImpl(id, persistentMachine));
496
+ const spawn = (id, machine) => withSpawnGate(spawnRegular(id, machine));
366
497
  const get = Effect.fn("effect-machine.actorSystem.get")(function* (id) {
367
498
  return yield* Effect.sync(() => MutableHashMap.get(actorsMap, id));
368
499
  });
@@ -379,55 +510,8 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
379
510
  yield* actor.stop;
380
511
  return true;
381
512
  });
382
- const listPersisted = Effect.fn("effect-machine.actorSystem.listPersisted")(function* () {
383
- const adapter = yield* PersistenceAdapterTag;
384
- if (adapter.listActors === void 0) return [];
385
- return yield* adapter.listActors();
386
- });
387
- const restoreMany = Effect.fn("effect-machine.actorSystem.restoreMany")(function* (ids, persistentMachine) {
388
- const restored = [];
389
- const failed = [];
390
- for (const id of ids) {
391
- if (MutableHashMap.has(actorsMap, id)) continue;
392
- const result = yield* Effect.either(restore(id, persistentMachine));
393
- if (result._tag === "Left") failed.push({
394
- id,
395
- error: result.left
396
- });
397
- else if (Option.isSome(result.right)) restored.push(result.right.value);
398
- else failed.push({
399
- id,
400
- error: new PersistenceError({
401
- operation: "restore",
402
- actorId: id,
403
- message: "No persisted state found"
404
- })
405
- });
406
- }
407
- return {
408
- restored,
409
- failed
410
- };
411
- });
412
- const restoreAll = Effect.fn("effect-machine.actorSystem.restoreAll")(function* (persistentMachine, options) {
413
- const adapter = yield* PersistenceAdapterTag;
414
- if (adapter.listActors === void 0) return {
415
- restored: [],
416
- failed: []
417
- };
418
- const machineType = persistentMachine.persistence.machineType;
419
- if (machineType === void 0) return yield* new PersistenceError({
420
- operation: "restoreAll",
421
- actorId: "*",
422
- message: "restoreAll requires explicit machineType in persistence config"
423
- });
424
- let filtered = (yield* adapter.listActors()).filter((meta) => meta.machineType === machineType);
425
- if (options?.filter !== void 0) filtered = filtered.filter(options.filter);
426
- return yield* restoreMany(filtered.map((meta) => meta.id), persistentMachine);
427
- });
428
513
  return ActorSystem.of({
429
514
  spawn,
430
- restore,
431
515
  get,
432
516
  stop,
433
517
  events: Stream.fromPubSub(eventPubSub),
@@ -443,10 +527,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
443
527
  return () => {
444
528
  eventListeners.delete(fn);
445
529
  };
446
- },
447
- listPersisted,
448
- restoreMany,
449
- restoreAll
530
+ }
450
531
  });
451
532
  });
452
533
  /**
@@ -454,4 +535,4 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
454
535
  */
455
536
  const Default = Layer.scoped(ActorSystem, make());
456
537
  //#endregion
457
- export { ActorSystem, Default, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
538
+ export { ActorSystem, Default, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
@@ -5,7 +5,7 @@ import { Layer } from "effect";
5
5
  import { Entity } from "@effect/cluster";
6
6
  import { Rpc } from "@effect/rpc";
7
7
 
8
- //#region src-v3/cluster/entity-machine.d.ts
8
+ //#region src/cluster/entity-machine.d.ts
9
9
  /**
10
10
  * Options for EntityMachine.layer
11
11
  */
@@ -2,7 +2,7 @@ import { processEventCore, runSpawnEffects } from "../internal/transition.js";
2
2
  import { ActorSystem } from "../actor.js";
3
3
  import { Effect, Option, Queue, Ref, Scope } from "effect";
4
4
  import { Entity } from "@effect/cluster";
5
- //#region src-v3/cluster/entity-machine.ts
5
+ //#region src/cluster/entity-machine.ts
6
6
  /**
7
7
  * EntityMachine adapter - wires a machine to a cluster Entity layer.
8
8
  *
@@ -13,7 +13,7 @@ import { Entity } from "@effect/cluster";
13
13
  * Returns the new state after processing.
14
14
  */
15
15
  const processEvent = Effect.fn("effect-machine.cluster.processEvent")(function* (machine, stateRef, event, self, stateScopeRef, system, hooks) {
16
- const result = yield* processEventCore(machine, yield* Ref.get(stateRef), event, self, stateScopeRef, system, hooks);
16
+ const result = yield* processEventCore(machine, yield* Ref.get(stateRef), event, self, stateScopeRef, system, "*", hooks);
17
17
  if (result.transitioned) yield* Ref.set(stateRef, result.newState);
18
18
  return result.newState;
19
19
  });
@@ -54,15 +54,17 @@ const EntityMachine = { layer: (entity, machine, options) => {
54
54
  if (Option.isNone(existingSystem)) return yield* Effect.die("EntityMachine requires ActorSystem in context");
55
55
  const system = existingSystem.value;
56
56
  const internalQueue = yield* Queue.unbounded();
57
+ const clusterSend = Effect.fn("effect-machine.cluster.self.send")(function* (event) {
58
+ yield* Queue.offer(internalQueue, event);
59
+ });
57
60
  const self = {
58
- send: Effect.fn("effect-machine.cluster.self.send")(function* (event) {
59
- yield* Queue.offer(internalQueue, event);
60
- }),
61
+ send: clusterSend,
62
+ cast: clusterSend,
61
63
  spawn: (childId, childMachine) => system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system))
62
64
  };
63
65
  const stateRef = yield* Ref.make(initialState);
64
66
  const stateScopeRef = { current: yield* Scope.make() };
65
- yield* runSpawnEffects(machine, initialState, { _tag: "$init" }, self, stateScopeRef.current, system, options?.hooks?.onError);
67
+ yield* runSpawnEffects(machine, initialState, { _tag: "$init" }, self, stateScopeRef.current, system, entityId, options?.hooks?.onError);
66
68
  const runInternalEvent = Effect.fn("effect-machine.cluster.internalEvent")(function* () {
67
69
  yield* processEvent(machine, stateRef, yield* Queue.take(internalQueue), self, stateScopeRef, system, options?.hooks);
68
70
  });
@@ -2,7 +2,7 @@ import { Machine } from "../machine.js";
2
2
  import { Schema } from "effect";
3
3
  import { Rpc } from "@effect/rpc";
4
4
 
5
- //#region src-v3/cluster/to-entity.d.ts
5
+ //#region src/cluster/to-entity.d.ts
6
6
  /**
7
7
  * Options for toEntity.
8
8
  */
@@ -1,7 +1,7 @@
1
1
  import { MissingSchemaError } from "../errors.js";
2
2
  import { Entity } from "@effect/cluster";
3
3
  import { Rpc } from "@effect/rpc";
4
- //#region src-v3/cluster/to-entity.ts
4
+ //#region src/cluster/to-entity.ts
5
5
  /**
6
6
  * Generate Entity definition from a machine.
7
7
  *
@@ -0,0 +1,76 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/errors.d.ts
4
+ declare const DuplicateActorError_base: Schema.TaggedErrorClass<DuplicateActorError, "DuplicateActorError", {
5
+ readonly _tag: Schema.tag<"DuplicateActorError">;
6
+ } & {
7
+ actorId: typeof Schema.String;
8
+ }>;
9
+ /** Attempted to spawn/restore actor with ID already in use */
10
+ declare class DuplicateActorError extends DuplicateActorError_base {}
11
+ declare const UnprovidedSlotsError_base: Schema.TaggedErrorClass<UnprovidedSlotsError, "UnprovidedSlotsError", {
12
+ readonly _tag: Schema.tag<"UnprovidedSlotsError">;
13
+ } & {
14
+ slots: Schema.Array$<typeof Schema.String>;
15
+ }>;
16
+ /** Machine has unprovided effect slots */
17
+ declare class UnprovidedSlotsError extends UnprovidedSlotsError_base {}
18
+ declare const MissingSchemaError_base: Schema.TaggedErrorClass<MissingSchemaError, "MissingSchemaError", {
19
+ readonly _tag: Schema.tag<"MissingSchemaError">;
20
+ } & {
21
+ operation: typeof Schema.String;
22
+ }>;
23
+ /** Operation requires schemas attached to machine */
24
+ declare class MissingSchemaError extends MissingSchemaError_base {}
25
+ declare const InvalidSchemaError_base: Schema.TaggedErrorClass<InvalidSchemaError, "InvalidSchemaError", {
26
+ readonly _tag: Schema.tag<"InvalidSchemaError">;
27
+ }>;
28
+ /** State/Event schema has no variants */
29
+ declare class InvalidSchemaError extends InvalidSchemaError_base {}
30
+ declare const MissingMatchHandlerError_base: Schema.TaggedErrorClass<MissingMatchHandlerError, "MissingMatchHandlerError", {
31
+ readonly _tag: Schema.tag<"MissingMatchHandlerError">;
32
+ } & {
33
+ tag: typeof Schema.String;
34
+ }>;
35
+ /** $match called with missing handler for tag */
36
+ declare class MissingMatchHandlerError extends MissingMatchHandlerError_base {}
37
+ declare const SlotProvisionError_base: Schema.TaggedErrorClass<SlotProvisionError, "SlotProvisionError", {
38
+ readonly _tag: Schema.tag<"SlotProvisionError">;
39
+ } & {
40
+ slotName: typeof Schema.String;
41
+ slotType: Schema.Literal<["guard", "effect"]>;
42
+ }>;
43
+ /** Slot handler not found at runtime (internal error) */
44
+ declare class SlotProvisionError extends SlotProvisionError_base {}
45
+ declare const ProvisionValidationError_base: Schema.TaggedErrorClass<ProvisionValidationError, "ProvisionValidationError", {
46
+ readonly _tag: Schema.tag<"ProvisionValidationError">;
47
+ } & {
48
+ missing: Schema.Array$<typeof Schema.String>;
49
+ extra: Schema.Array$<typeof Schema.String>;
50
+ }>;
51
+ /** Machine.build() validation failed - missing or extra handlers */
52
+ declare class ProvisionValidationError extends ProvisionValidationError_base {}
53
+ declare const AssertionError_base: Schema.TaggedErrorClass<AssertionError, "AssertionError", {
54
+ readonly _tag: Schema.tag<"AssertionError">;
55
+ } & {
56
+ message: typeof Schema.String;
57
+ }>;
58
+ /** Assertion failed in testing utilities */
59
+ declare class AssertionError extends AssertionError_base {}
60
+ declare const ActorStoppedError_base: Schema.TaggedErrorClass<ActorStoppedError, "ActorStoppedError", {
61
+ readonly _tag: Schema.tag<"ActorStoppedError">;
62
+ } & {
63
+ actorId: typeof Schema.String;
64
+ }>;
65
+ /** Actor was stopped while a call/ask was pending */
66
+ declare class ActorStoppedError extends ActorStoppedError_base {}
67
+ declare const NoReplyError_base: Schema.TaggedErrorClass<NoReplyError, "NoReplyError", {
68
+ readonly _tag: Schema.tag<"NoReplyError">;
69
+ } & {
70
+ actorId: typeof Schema.String;
71
+ eventTag: typeof Schema.String;
72
+ }>;
73
+ /** ask() was used but the transition handler did not call reply */
74
+ declare class NoReplyError extends NoReplyError_base {}
75
+ //#endregion
76
+ export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError };
@@ -1,5 +1,5 @@
1
1
  import { Schema } from "effect";
2
- //#region src-v3/errors.ts
2
+ //#region src/errors.ts
3
3
  /**
4
4
  * Typed error classes for effect-machine.
5
5
  *
@@ -32,5 +32,12 @@ var ProvisionValidationError = class extends Schema.TaggedError()("ProvisionVali
32
32
  }) {};
33
33
  /** Assertion failed in testing utilities */
34
34
  var AssertionError = class extends Schema.TaggedError()("AssertionError", { message: Schema.String }) {};
35
+ /** Actor was stopped while a call/ask was pending */
36
+ var ActorStoppedError = class extends Schema.TaggedError()("ActorStoppedError", { actorId: Schema.String }) {};
37
+ /** ask() was used but the transition handler did not call reply */
38
+ var NoReplyError = class extends Schema.TaggedError()("NoReplyError", {
39
+ actorId: Schema.String,
40
+ eventTag: Schema.String
41
+ }) {};
35
42
  //#endregion
36
- export { AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError };
43
+ export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError };