effect-machine 0.4.0 → 0.7.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 (70) hide show
  1. package/README.md +36 -0
  2. package/dist/actor.d.ts +40 -4
  3. package/dist/actor.js +89 -34
  4. package/dist/cluster/entity-machine.d.ts +2 -2
  5. package/dist/cluster/entity-machine.js +1 -1
  6. package/dist/cluster/to-entity.d.ts +5 -5
  7. package/dist/cluster/to-entity.js +2 -2
  8. package/dist/errors.d.ts +25 -40
  9. package/dist/errors.js +10 -10
  10. package/dist/index.d.ts +2 -2
  11. package/dist/inspection.d.ts +3 -3
  12. package/dist/inspection.js +2 -2
  13. package/dist/internal/brands.d.ts +3 -6
  14. package/dist/internal/inspection.js +5 -1
  15. package/dist/internal/transition.d.ts +2 -2
  16. package/dist/internal/transition.js +6 -6
  17. package/dist/internal/utils.js +11 -2
  18. package/dist/machine.d.ts +5 -5
  19. package/dist/machine.js +9 -5
  20. package/dist/persistence/adapter.d.ts +18 -21
  21. package/dist/persistence/adapter.js +4 -4
  22. package/dist/persistence/adapters/in-memory.js +4 -4
  23. package/dist/persistence/persistent-actor.js +23 -14
  24. package/dist/persistence/persistent-machine.d.ts +3 -3
  25. package/dist/schema.d.ts +4 -4
  26. package/dist/schema.js +2 -2
  27. package/dist/slot.d.ts +3 -3
  28. package/dist/slot.js +2 -2
  29. package/dist-v3/_virtual/_rolldown/runtime.js +18 -0
  30. package/dist-v3/actor.d.ts +291 -0
  31. package/dist-v3/actor.js +459 -0
  32. package/dist-v3/cluster/entity-machine.d.ts +90 -0
  33. package/dist-v3/cluster/entity-machine.js +80 -0
  34. package/dist-v3/cluster/index.d.ts +3 -0
  35. package/dist-v3/cluster/index.js +4 -0
  36. package/dist-v3/cluster/to-entity.d.ts +61 -0
  37. package/dist-v3/cluster/to-entity.js +53 -0
  38. package/dist-v3/errors.d.ts +27 -0
  39. package/dist-v3/errors.js +38 -0
  40. package/dist-v3/index.d.ts +13 -0
  41. package/dist-v3/index.js +14 -0
  42. package/dist-v3/inspection.d.ts +125 -0
  43. package/dist-v3/inspection.js +50 -0
  44. package/dist-v3/internal/brands.d.ts +40 -0
  45. package/dist-v3/internal/brands.js +0 -0
  46. package/dist-v3/internal/inspection.d.ts +11 -0
  47. package/dist-v3/internal/inspection.js +15 -0
  48. package/dist-v3/internal/transition.d.ts +160 -0
  49. package/dist-v3/internal/transition.js +238 -0
  50. package/dist-v3/internal/utils.d.ts +60 -0
  51. package/dist-v3/internal/utils.js +51 -0
  52. package/dist-v3/machine.d.ts +278 -0
  53. package/dist-v3/machine.js +317 -0
  54. package/dist-v3/persistence/adapter.d.ts +125 -0
  55. package/dist-v3/persistence/adapter.js +27 -0
  56. package/dist-v3/persistence/adapters/in-memory.d.ts +32 -0
  57. package/dist-v3/persistence/adapters/in-memory.js +176 -0
  58. package/dist-v3/persistence/index.d.ts +5 -0
  59. package/dist-v3/persistence/index.js +6 -0
  60. package/dist-v3/persistence/persistent-actor.d.ts +49 -0
  61. package/dist-v3/persistence/persistent-actor.js +367 -0
  62. package/dist-v3/persistence/persistent-machine.d.ts +105 -0
  63. package/dist-v3/persistence/persistent-machine.js +24 -0
  64. package/dist-v3/schema.d.ts +141 -0
  65. package/dist-v3/schema.js +165 -0
  66. package/dist-v3/slot.d.ts +130 -0
  67. package/dist-v3/slot.js +99 -0
  68. package/dist-v3/testing.d.ts +136 -0
  69. package/dist-v3/testing.js +138 -0
  70. package/package.json +29 -21
package/README.md CHANGED
@@ -206,6 +206,30 @@ const child = yield * parent.system.get("worker-1"); // Option<ActorRef>
206
206
 
207
207
  Every actor always has a system — `Machine.spawn` creates an implicit one if no `ActorSystem` is in context.
208
208
 
209
+ ### System Observation
210
+
211
+ React to actors joining and leaving the system:
212
+
213
+ ```ts
214
+ const system = yield * ActorSystemService;
215
+
216
+ // Sync callback — like ActorRef.subscribe
217
+ const unsub = system.subscribe((event) => {
218
+ // event._tag: "ActorSpawned" | "ActorStopped"
219
+ console.log(`${event._tag}: ${event.id}`);
220
+ });
221
+
222
+ // Sync snapshot of all registered actors
223
+ const actors = system.actors; // ReadonlyMap<string, ActorRef>
224
+
225
+ // Async stream (each subscriber gets own queue)
226
+ yield *
227
+ system.events.pipe(
228
+ Stream.tap((e) => Effect.log(e._tag, e.id)),
229
+ Stream.runDrain,
230
+ );
231
+ ```
232
+
209
233
  ### Testing
210
234
 
211
235
  Test transitions without actors:
@@ -297,6 +321,18 @@ See the [primer](./primer/) for comprehensive documentation:
297
321
  | `actor.sendAndWait(ev, State.X)` | Send + wait for state |
298
322
  | `actor.subscribe(fn)` | Sync callback |
299
323
  | `actor.system` | Access the actor's `ActorSystem` |
324
+ | `actor.children` | Child actors (`ReadonlyMap`) |
325
+
326
+ ### ActorSystem
327
+
328
+ | Method / Property | Description |
329
+ | ---------------------- | ------------------------------------------- |
330
+ | `system.spawn(id, m)` | Spawn actor |
331
+ | `system.get(id)` | Get actor by ID |
332
+ | `system.stop(id)` | Stop actor by ID |
333
+ | `system.actors` | Sync snapshot of all actors (`ReadonlyMap`) |
334
+ | `system.subscribe(fn)` | Sync callback for spawn/stop events |
335
+ | `system.events` | Async `Stream<SystemEvent>` for spawn/stop |
300
336
 
301
337
  ## License
302
338
 
package/dist/actor.d.ts CHANGED
@@ -5,7 +5,7 @@ import { ProcessEventError, ProcessEventHooks, ProcessEventResult, processEventC
5
5
  import { PersistentActorRef } from "./persistence/persistent-actor.js";
6
6
  import { ActorMetadata, PersistenceAdapterTag, PersistenceError, RestoreResult, VersionConflictError } from "./persistence/adapter.js";
7
7
  import { BuiltMachine, Machine, MachineRef } from "./machine.js";
8
- import { Context, Effect, Layer, Option, Queue, Ref, Scope, Stream, SubscriptionRef } from "effect";
8
+ import { Effect, Layer, Option, Queue, Ref, Scope, ServiceMap, Stream, SubscriptionRef } from "effect";
9
9
  import * as effect_Tracer0 from "effect/Tracer";
10
10
 
11
11
  //#region src/actor.d.ts
@@ -106,11 +106,32 @@ interface ActorRef<State extends {
106
106
  * Every actor always has a system — either inherited from context or implicitly created.
107
107
  */
108
108
  readonly system: ActorSystem;
109
+ /**
110
+ * Child actors spawned via `self.spawn` in this actor's handlers.
111
+ * State-scoped children are auto-removed on state exit.
112
+ */
113
+ readonly children: ReadonlyMap<string, ActorRef<AnyState, unknown>>;
109
114
  }
110
115
  /** Base type for stored actors (internal) */
111
116
  type AnyState = {
112
117
  readonly _tag: string;
113
118
  };
119
+ /**
120
+ * Events emitted by the ActorSystem when actors are spawned or stopped.
121
+ */
122
+ type SystemEvent = {
123
+ readonly _tag: "ActorSpawned";
124
+ readonly id: string;
125
+ readonly actor: ActorRef<AnyState, unknown>;
126
+ } | {
127
+ readonly _tag: "ActorStopped";
128
+ readonly id: string;
129
+ readonly actor: ActorRef<AnyState, unknown>;
130
+ };
131
+ /**
132
+ * Listener callback for system events.
133
+ */
134
+ type SystemEventListener = (event: SystemEvent) => void;
114
135
  /**
115
136
  * Actor system for managing actor lifecycles
116
137
  */
@@ -174,6 +195,21 @@ interface ActorSystem {
174
195
  * Stop an actor by ID
175
196
  */
176
197
  readonly stop: (id: string) => Effect.Effect<boolean>;
198
+ /**
199
+ * Async stream of system events (actor spawned/stopped).
200
+ * Each subscriber gets their own queue — late subscribers miss prior events.
201
+ */
202
+ readonly events: Stream.Stream<SystemEvent>;
203
+ /**
204
+ * Sync snapshot of all currently registered actors.
205
+ * Returns a new Map on each access (not live).
206
+ */
207
+ readonly actors: ReadonlyMap<string, ActorRef<AnyState, unknown>>;
208
+ /**
209
+ * Subscribe to system events synchronously.
210
+ * Returns an unsubscribe function.
211
+ */
212
+ readonly subscribe: (fn: SystemEventListener) => () => void;
177
213
  /**
178
214
  * List all persisted actor metadata.
179
215
  * Returns empty array if adapter doesn't support registry.
@@ -225,7 +261,7 @@ interface ActorSystem {
225
261
  /**
226
262
  * ActorSystem service tag
227
263
  */
228
- declare const ActorSystem: Context.Tag<ActorSystem, ActorSystem>;
264
+ declare const ActorSystem: ServiceMap.Service<ActorSystem, ActorSystem>;
229
265
  /** Listener set for sync subscriptions */
230
266
  type Listeners<S> = Set<(state: S) => void>;
231
267
  /**
@@ -239,7 +275,7 @@ declare const buildActorRefCore: <S extends {
239
275
  readonly _tag: string;
240
276
  }, E extends {
241
277
  readonly _tag: string;
242
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, any, any, GD, EFD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueue: Queue.Queue<E>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem) => ActorRef<S, E>;
278
+ }, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, any, any, GD, EFD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueue: Queue.Queue<E>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>) => ActorRef<S, E>;
243
279
  /**
244
280
  * Create and start an actor for a machine
245
281
  */
@@ -253,4 +289,4 @@ declare const createActor: <S extends {
253
289
  */
254
290
  declare const Default: Layer.Layer<ActorSystem, never, never>;
255
291
  //#endregion
256
- export { ActorRef, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
292
+ export { ActorRef, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, SystemEvent, SystemEventListener, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
package/dist/actor.js CHANGED
@@ -6,13 +6,21 @@ import { processEventCore, resolveTransition, runSpawnEffects } from "./internal
6
6
  import { emitWithTimestamp } from "./internal/inspection.js";
7
7
  import { PersistenceAdapterTag, PersistenceError } from "./persistence/adapter.js";
8
8
  import { createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
9
- import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, Queue, Ref, Runtime, Scope, SubscriptionRef } from "effect";
9
+ import { Cause, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Scope, ServiceMap, Stream, SubscriptionRef } from "effect";
10
10
 
11
11
  //#region src/actor.ts
12
12
  /**
13
+ * Actor system: spawning, lifecycle, and event processing.
14
+ *
15
+ * Combines:
16
+ * - ActorRef interface (running actor handle)
17
+ * - ActorSystem service (spawn/stop/get actors)
18
+ * - Actor creation and event loop
19
+ */
20
+ /**
13
21
  * ActorSystem service tag
14
22
  */
15
- const ActorSystem = Context.GenericTag("@effect/machine/ActorSystem");
23
+ const ActorSystem = ServiceMap.Service("@effect/machine/ActorSystem");
16
24
  /**
17
25
  * Notify all listeners of state change.
18
26
  */
@@ -24,7 +32,7 @@ const notifyListeners = (listeners, state) => {
24
32
  /**
25
33
  * Build core ActorRef methods shared between regular and persistent actors.
26
34
  */
27
- const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop, system) => {
35
+ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop, system, childrenMap) => {
28
36
  const send = Effect.fn("effect-machine.actor.send")(function* (event) {
29
37
  if (yield* Ref.get(stoppedRef)) return;
30
38
  yield* Queue.offer(eventQueue, event);
@@ -41,10 +49,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
41
49
  const current = yield* SubscriptionRef.get(stateRef);
42
50
  if (predicate(current)) return current;
43
51
  const done = yield* Deferred.make();
44
- const rt = yield* Effect.runtime();
45
- const runFork = Runtime.runFork(rt);
46
52
  const listener = (state) => {
47
- if (predicate(state)) runFork(Deferred.succeed(done, state));
53
+ if (predicate(state)) Effect.runFork(Deferred.succeed(done, state));
48
54
  };
49
55
  listeners.add(listener);
50
56
  const afterSubscribe = yield* SubscriptionRef.get(stateRef);
@@ -76,7 +82,7 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
76
82
  canSync: (event) => {
77
83
  return resolveTransition(machine, Effect.runSync(SubscriptionRef.get(stateRef)), event) !== void 0;
78
84
  },
79
- changes: stateRef.changes,
85
+ changes: SubscriptionRef.changes(stateRef),
80
86
  waitFor,
81
87
  awaitFinal,
82
88
  sendAndWait,
@@ -89,7 +95,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
89
95
  listeners.delete(fn);
90
96
  };
91
97
  },
92
- system
98
+ system,
99
+ children: childrenMap
93
100
  };
94
101
  };
95
102
  /**
@@ -109,12 +116,21 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
109
116
  const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
110
117
  const eventQueue = yield* Queue.unbounded();
111
118
  const stoppedRef = yield* Ref.make(false);
119
+ const childrenMap = /* @__PURE__ */ new Map();
112
120
  const self = {
113
121
  send: Effect.fn("effect-machine.actor.self.send")(function* (event) {
114
122
  if (yield* Ref.get(stoppedRef)) return;
115
123
  yield* Queue.offer(eventQueue, event);
116
124
  }),
117
- spawn: (childId, childMachine) => system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system))
125
+ spawn: (childId, childMachine) => Effect.gen(function* () {
126
+ const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
127
+ childrenMap.set(childId, child);
128
+ const maybeScope = yield* Effect.serviceOption(Scope.Scope);
129
+ if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
130
+ childrenMap.delete(childId);
131
+ }));
132
+ return child;
133
+ })
118
134
  };
119
135
  yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", machine.initial._tag);
120
136
  yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
@@ -135,7 +151,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
135
151
  };
136
152
  const { effects: effectSlots } = machine._slots;
137
153
  for (const bg of machine.backgroundEffects) {
138
- const fiber = yield* Effect.forkDaemon(bg.handler({
154
+ const fiber = yield* Effect.forkDetach(bg.handler({
139
155
  state: machine.initial,
140
156
  event: initEvent,
141
157
  self,
@@ -157,9 +173,9 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
157
173
  }));
158
174
  yield* Ref.set(stoppedRef, true);
159
175
  if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
160
- return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system);
176
+ return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap);
161
177
  }
162
- const loopFiber = yield* Effect.forkDaemon(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue, system));
178
+ const loopFiber = yield* Effect.forkDetach(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue, system));
163
179
  return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
164
180
  const finalState = yield* SubscriptionRef.get(stateRef);
165
181
  yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
@@ -173,7 +189,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
173
189
  yield* Scope.close(stateScopeRef.current, Exit.void);
174
190
  yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
175
191
  if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
176
- }).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system);
192
+ }).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap);
177
193
  });
178
194
  /**
179
195
  * Main event loop for the actor
@@ -277,39 +293,58 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffec
277
293
  timestamp
278
294
  })));
279
295
  });
280
- /**
281
- * Internal implementation
282
- */
296
+ /** Notify all system event listeners (sync). */
297
+ const notifySystemListeners = (listeners, event) => {
298
+ for (const listener of listeners) try {
299
+ listener(event);
300
+ } catch {}
301
+ };
283
302
  const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
284
- const actors = MutableHashMap.empty();
303
+ const actorsMap = MutableHashMap.empty();
285
304
  const withSpawnGate = (yield* Effect.makeSemaphore(1)).withPermits(1);
305
+ const eventPubSub = yield* PubSub.unbounded();
306
+ const eventListeners = /* @__PURE__ */ new Set();
307
+ const emitSystemEvent = (event) => Effect.sync(() => notifySystemListeners(eventListeners, event)).pipe(Effect.andThen(PubSub.publish(eventPubSub, event)), Effect.catchCause(() => Effect.void), Effect.asVoid);
286
308
  yield* Effect.addFinalizer(() => {
287
309
  const stops = [];
288
- MutableHashMap.forEach(actors, (actor) => {
310
+ MutableHashMap.forEach(actorsMap, (actor) => {
289
311
  stops.push(actor.stop);
290
312
  });
291
- return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.asVoid);
313
+ return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.andThen(PubSub.shutdown(eventPubSub)), Effect.asVoid);
292
314
  });
293
315
  /** Check for duplicate ID, register actor, attach scope cleanup if available */
294
316
  const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* (id, actor) {
295
- if (MutableHashMap.has(actors, id)) {
317
+ if (MutableHashMap.has(actorsMap, id)) {
296
318
  yield* actor.stop;
297
319
  return yield* new DuplicateActorError({ actorId: id });
298
320
  }
299
- MutableHashMap.set(actors, id, actor);
321
+ const actorRef = actor;
322
+ MutableHashMap.set(actorsMap, id, actorRef);
323
+ yield* emitSystemEvent({
324
+ _tag: "ActorSpawned",
325
+ id,
326
+ actor: actorRef
327
+ });
300
328
  const maybeScope = yield* Effect.serviceOption(Scope.Scope);
301
329
  if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.gen(function* () {
330
+ if (MutableHashMap.has(actorsMap, id)) {
331
+ yield* emitSystemEvent({
332
+ _tag: "ActorStopped",
333
+ id,
334
+ actor: actorRef
335
+ });
336
+ MutableHashMap.remove(actorsMap, id);
337
+ }
302
338
  yield* actor.stop;
303
- MutableHashMap.remove(actors, id);
304
339
  }));
305
340
  return actor;
306
341
  });
307
342
  const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id, built) {
308
- if (MutableHashMap.has(actors, id)) return yield* new DuplicateActorError({ actorId: id });
343
+ if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
309
344
  return yield* registerActor(id, yield* createActor(id, built._inner));
310
345
  });
311
346
  const spawnPersistent = Effect.fn("effect-machine.actorSystem.spawnPersistent")(function* (id, persistentMachine) {
312
- if (MutableHashMap.has(actors, id)) return yield* new DuplicateActorError({ actorId: id });
347
+ if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
313
348
  const adapter = yield* PersistenceAdapterTag;
314
349
  const maybeSnapshot = yield* adapter.loadSnapshot(id, persistentMachine.persistence.stateSchema);
315
350
  return yield* registerActor(id, yield* createPersistentActor(id, persistentMachine, maybeSnapshot, yield* adapter.loadEvents(id, persistentMachine.persistence.eventSchema, Option.isSome(maybeSnapshot) ? maybeSnapshot.value.version : void 0)));
@@ -328,13 +363,19 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
328
363
  });
329
364
  const restore = (id, persistentMachine) => withSpawnGate(restoreImpl(id, persistentMachine));
330
365
  const get = Effect.fn("effect-machine.actorSystem.get")(function* (id) {
331
- return yield* Effect.sync(() => MutableHashMap.get(actors, id));
366
+ return yield* Effect.sync(() => MutableHashMap.get(actorsMap, id));
332
367
  });
333
368
  const stop = Effect.fn("effect-machine.actorSystem.stop")(function* (id) {
334
- const maybeActor = MutableHashMap.get(actors, id);
369
+ const maybeActor = MutableHashMap.get(actorsMap, id);
335
370
  if (Option.isNone(maybeActor)) return false;
336
- yield* maybeActor.value.stop;
337
- MutableHashMap.remove(actors, id);
371
+ const actor = maybeActor.value;
372
+ MutableHashMap.remove(actorsMap, id);
373
+ yield* emitSystemEvent({
374
+ _tag: "ActorStopped",
375
+ id,
376
+ actor
377
+ });
378
+ yield* actor.stop;
338
379
  return true;
339
380
  });
340
381
  const listPersisted = Effect.fn("effect-machine.actorSystem.listPersisted")(function* () {
@@ -346,13 +387,13 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
346
387
  const restored = [];
347
388
  const failed = [];
348
389
  for (const id of ids) {
349
- if (MutableHashMap.has(actors, id)) continue;
350
- const result = yield* Effect.either(restore(id, persistentMachine));
351
- if (result._tag === "Left") failed.push({
390
+ if (MutableHashMap.has(actorsMap, id)) continue;
391
+ const result = yield* Effect.result(restore(id, persistentMachine));
392
+ if (result._tag === "Failure") failed.push({
352
393
  id,
353
- error: result.left
394
+ error: result.failure
354
395
  });
355
- else if (Option.isSome(result.right)) restored.push(result.right.value);
396
+ else if (Option.isSome(result.success)) restored.push(result.success.value);
356
397
  else failed.push({
357
398
  id,
358
399
  error: new PersistenceError({
@@ -388,6 +429,20 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
388
429
  restore,
389
430
  get,
390
431
  stop,
432
+ events: Stream.fromPubSub(eventPubSub),
433
+ get actors() {
434
+ const snapshot = /* @__PURE__ */ new Map();
435
+ MutableHashMap.forEach(actorsMap, (actor, id) => {
436
+ snapshot.set(id, actor);
437
+ });
438
+ return snapshot;
439
+ },
440
+ subscribe: (fn) => {
441
+ eventListeners.add(fn);
442
+ return () => {
443
+ eventListeners.delete(fn);
444
+ };
445
+ },
391
446
  listPersisted,
392
447
  restoreMany,
393
448
  restoreAll
@@ -396,7 +451,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
396
451
  /**
397
452
  * Default ActorSystem layer
398
453
  */
399
- const Default = Layer.scoped(ActorSystem, make());
454
+ const Default = Layer.effect(ActorSystem, make());
400
455
 
401
456
  //#endregion
402
457
  export { ActorSystem, Default, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
@@ -3,8 +3,8 @@ import { ProcessEventHooks } from "../internal/transition.js";
3
3
  import { Machine } from "../machine.js";
4
4
  import "../actor.js";
5
5
  import { Layer } from "effect";
6
- import { Entity } from "@effect/cluster";
7
- import { Rpc } from "@effect/rpc";
6
+ import { Entity } from "effect/unstable/cluster";
7
+ import { Rpc } from "effect/unstable/rpc";
8
8
 
9
9
  //#region src/cluster/entity-machine.d.ts
10
10
  /**
@@ -1,7 +1,7 @@
1
1
  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
- import { Entity } from "@effect/cluster";
4
+ import { Entity } from "effect/unstable/cluster";
5
5
 
6
6
  //#region src/cluster/entity-machine.ts
7
7
  /**
@@ -1,7 +1,7 @@
1
1
  import { Machine } from "../machine.js";
2
2
  import { Schema } from "effect";
3
- import { Entity } from "@effect/cluster";
4
- import { Rpc } from "@effect/rpc";
3
+ import { Entity } from "effect/unstable/cluster";
4
+ import { Rpc } from "effect/unstable/rpc";
5
5
 
6
6
  //#region src/cluster/to-entity.d.ts
7
7
  /**
@@ -19,7 +19,7 @@ interface ToEntityOptions {
19
19
  * - `Send` - Send event to machine, returns new state
20
20
  * - `GetState` - Get current state
21
21
  */
22
- type EntityRpcs<StateSchema extends Schema.Schema.Any, EventSchema extends Schema.Schema.Any> = readonly [Rpc.Rpc<"Send", Schema.Struct<{
22
+ type EntityRpcs<StateSchema extends Schema.Top, EventSchema extends Schema.Top> = readonly [Rpc.Rpc<"Send", Schema.Struct<{
23
23
  readonly event: EventSchema;
24
24
  }>, StateSchema, typeof Schema.Never, never>, Rpc.Rpc<"GetState", typeof Schema.Void, StateSchema, typeof Schema.Never, never>];
25
25
  /**
@@ -58,7 +58,7 @@ declare const toEntity: <S extends {
58
58
  }, E extends {
59
59
  readonly _tag: string;
60
60
  }, R>(machine: Machine<S, E, R, any, any, any, any>, options: ToEntityOptions) => Entity.Entity<string, Rpc.Rpc<"Send", Schema.Struct<{
61
- event: Schema.Schema<E, unknown, never>;
62
- }>, Schema.Schema<S, unknown, never>, typeof Schema.Never, never> | Rpc.Rpc<"GetState", typeof Schema.Void, Schema.Schema<S, unknown, never>, typeof Schema.Never, never>>;
61
+ event: Schema.Schema<E>;
62
+ }>, Schema.Schema<S>, Schema.Never, never, never> | Rpc.Rpc<"GetState", Schema.Void, Schema.Schema<S>, Schema.Never, never, never>>;
63
63
  //#endregion
64
64
  export { EntityRpcs, ToEntityOptions, toEntity };
@@ -1,6 +1,6 @@
1
1
  import { MissingSchemaError } from "../errors.js";
2
- import { Entity } from "@effect/cluster";
3
- import { Rpc } from "@effect/rpc";
2
+ import { Entity } from "effect/unstable/cluster";
3
+ import { Rpc } from "effect/unstable/rpc";
4
4
 
5
5
  //#region src/cluster/to-entity.ts
6
6
  /**
package/dist/errors.d.ts CHANGED
@@ -1,60 +1,45 @@
1
1
  import { Schema } from "effect";
2
+ import * as effect_Cause0 from "effect/Cause";
2
3
 
3
4
  //#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
- }>;
5
+ declare const DuplicateActorError_base: Schema.ErrorClass<DuplicateActorError, Schema.TaggedStruct<"DuplicateActorError", {
6
+ readonly actorId: Schema.String;
7
+ }>, effect_Cause0.YieldableError>;
9
8
  /** Attempted to spawn/restore actor with ID already in use */
10
9
  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
- }>;
10
+ declare const UnprovidedSlotsError_base: Schema.ErrorClass<UnprovidedSlotsError, Schema.TaggedStruct<"UnprovidedSlotsError", {
11
+ readonly slots: Schema.Array$<Schema.String>;
12
+ }>, effect_Cause0.YieldableError>;
16
13
  /** Machine has unprovided effect slots */
17
14
  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
- }>;
15
+ declare const MissingSchemaError_base: Schema.ErrorClass<MissingSchemaError, Schema.TaggedStruct<"MissingSchemaError", {
16
+ readonly operation: Schema.String;
17
+ }>, effect_Cause0.YieldableError>;
23
18
  /** Operation requires schemas attached to machine */
24
19
  declare class MissingSchemaError extends MissingSchemaError_base {}
25
- declare const InvalidSchemaError_base: Schema.TaggedErrorClass<InvalidSchemaError, "InvalidSchemaError", {
26
- readonly _tag: Schema.tag<"InvalidSchemaError">;
27
- }>;
20
+ declare const InvalidSchemaError_base: Schema.ErrorClass<InvalidSchemaError, Schema.TaggedStruct<"InvalidSchemaError", {}>, effect_Cause0.YieldableError>;
28
21
  /** State/Event schema has no variants */
29
22
  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
- }>;
23
+ declare const MissingMatchHandlerError_base: Schema.ErrorClass<MissingMatchHandlerError, Schema.TaggedStruct<"MissingMatchHandlerError", {
24
+ readonly tag: Schema.String;
25
+ }>, effect_Cause0.YieldableError>;
35
26
  /** $match called with missing handler for tag */
36
27
  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
- }>;
28
+ declare const SlotProvisionError_base: Schema.ErrorClass<SlotProvisionError, Schema.TaggedStruct<"SlotProvisionError", {
29
+ readonly slotName: Schema.String;
30
+ readonly slotType: Schema.Literals<readonly ["guard", "effect"]>;
31
+ }>, effect_Cause0.YieldableError>;
43
32
  /** Slot handler not found at runtime (internal error) */
44
33
  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
- }>;
34
+ declare const ProvisionValidationError_base: Schema.ErrorClass<ProvisionValidationError, Schema.TaggedStruct<"ProvisionValidationError", {
35
+ readonly missing: Schema.Array$<Schema.String>;
36
+ readonly extra: Schema.Array$<Schema.String>;
37
+ }>, effect_Cause0.YieldableError>;
51
38
  /** Machine.build() validation failed - missing or extra handlers */
52
39
  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
- }>;
40
+ declare const AssertionError_base: Schema.ErrorClass<AssertionError, Schema.TaggedStruct<"AssertionError", {
41
+ readonly message: Schema.String;
42
+ }>, effect_Cause0.YieldableError>;
58
43
  /** Assertion failed in testing utilities */
59
44
  declare class AssertionError extends AssertionError_base {}
60
45
  //#endregion
package/dist/errors.js CHANGED
@@ -4,7 +4,7 @@ import { Schema } from "effect";
4
4
  /**
5
5
  * Typed error classes for effect-machine.
6
6
  *
7
- * All errors extend Schema.TaggedError for:
7
+ * All errors extend Schema.TaggedErrorClassClass for:
8
8
  * - Type-safe catching via Effect.catchTag
9
9
  * - Serialization support
10
10
  * - Composable error handling
@@ -12,27 +12,27 @@ import { Schema } from "effect";
12
12
  * @module
13
13
  */
14
14
  /** Attempted to spawn/restore actor with ID already in use */
15
- var DuplicateActorError = class extends Schema.TaggedError()("DuplicateActorError", { actorId: Schema.String }) {};
15
+ var DuplicateActorError = class extends Schema.TaggedErrorClass()("DuplicateActorError", { actorId: Schema.String }) {};
16
16
  /** Machine has unprovided effect slots */
17
- var UnprovidedSlotsError = class extends Schema.TaggedError()("UnprovidedSlotsError", { slots: Schema.Array(Schema.String) }) {};
17
+ var UnprovidedSlotsError = class extends Schema.TaggedErrorClass()("UnprovidedSlotsError", { slots: Schema.Array(Schema.String) }) {};
18
18
  /** Operation requires schemas attached to machine */
19
- var MissingSchemaError = class extends Schema.TaggedError()("MissingSchemaError", { operation: Schema.String }) {};
19
+ var MissingSchemaError = class extends Schema.TaggedErrorClass()("MissingSchemaError", { operation: Schema.String }) {};
20
20
  /** State/Event schema has no variants */
21
- var InvalidSchemaError = class extends Schema.TaggedError()("InvalidSchemaError", {}) {};
21
+ var InvalidSchemaError = class extends Schema.TaggedErrorClass()("InvalidSchemaError", {}) {};
22
22
  /** $match called with missing handler for tag */
23
- var MissingMatchHandlerError = class extends Schema.TaggedError()("MissingMatchHandlerError", { tag: Schema.String }) {};
23
+ var MissingMatchHandlerError = class extends Schema.TaggedErrorClass()("MissingMatchHandlerError", { tag: Schema.String }) {};
24
24
  /** Slot handler not found at runtime (internal error) */
25
- var SlotProvisionError = class extends Schema.TaggedError()("SlotProvisionError", {
25
+ var SlotProvisionError = class extends Schema.TaggedErrorClass()("SlotProvisionError", {
26
26
  slotName: Schema.String,
27
- slotType: Schema.Literal("guard", "effect")
27
+ slotType: Schema.Literals(["guard", "effect"])
28
28
  }) {};
29
29
  /** Machine.build() validation failed - missing or extra handlers */
30
- var ProvisionValidationError = class extends Schema.TaggedError()("ProvisionValidationError", {
30
+ var ProvisionValidationError = class extends Schema.TaggedErrorClass()("ProvisionValidationError", {
31
31
  missing: Schema.Array(Schema.String),
32
32
  extra: Schema.Array(Schema.String)
33
33
  }) {};
34
34
  /** Assertion failed in testing utilities */
35
- var AssertionError = class extends Schema.TaggedError()("AssertionError", { message: Schema.String }) {};
35
+ var AssertionError = class extends Schema.TaggedErrorClass()("AssertionError", { message: Schema.String }) {};
36
36
 
37
37
  //#endregion
38
38
  export { AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError };
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ import { ActorMetadata, PersistedEvent, PersistenceAdapter, PersistenceAdapterTa
7
7
  import { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter } from "./persistence/adapters/in-memory.js";
8
8
  import "./persistence/index.js";
9
9
  import { BackgroundEffect, BuiltMachine, HandlerContext, Machine, MachineRef, MakeConfig, PersistOptions, ProvideHandlers, SpawnEffect, StateHandlerContext, Transition, machine_d_exports } from "./machine.js";
10
- import { ActorRef, ActorSystem, Default } from "./actor.js";
10
+ import { ActorRef, ActorSystem, Default, SystemEvent, SystemEventListener } from "./actor.js";
11
11
  import { SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
12
12
  import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, SpawnEvent, StopEvent, TransitionEvent, collectingInspector, consoleInspector, makeInspector } from "./inspection.js";
13
- export { type ActorMetadata, type ActorRef, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type BuiltMachine, DuplicateActorError, type EffectEvent, type EffectSlots, type EffectsDef, type EffectsSchema, type ErrorEvent, Event, type EventReceivedEvent, type GuardHandlers, type GuardSlot, type GuardSlots, type GuardsDef, type GuardsSchema, type HandlerContext, InMemoryPersistenceAdapter, type InspectionEvent, type Inspector, Inspector as InspectorService, InvalidSchemaError, machine_d_exports as Machine, type MachineContext, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, MissingSchemaError, type PersistOptions, type PersistedEvent, type PersistenceAdapter, PersistenceAdapterTag, type PersistenceConfig, PersistenceError, type PersistentActorRef, type PersistentMachine, type ProvideHandlers, ProvisionValidationError, type RestoreFailure, type RestoreResult, type SimulationResult, Slot, type EffectHandlers as SlotEffectHandlers, type EffectSlot as SlotEffectSlot, SlotProvisionError, type Snapshot, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, type TestHarness, type TestHarnessOptions, type Transition, type TransitionEvent, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, restorePersistentActor, simulate };
13
+ export { type ActorMetadata, type ActorRef, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type BuiltMachine, DuplicateActorError, type EffectEvent, type EffectSlots, type EffectsDef, type EffectsSchema, type ErrorEvent, Event, type EventReceivedEvent, type GuardHandlers, type GuardSlot, type GuardSlots, type GuardsDef, type GuardsSchema, type HandlerContext, InMemoryPersistenceAdapter, type InspectionEvent, type Inspector, Inspector as InspectorService, InvalidSchemaError, machine_d_exports as Machine, type MachineContext, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, MissingSchemaError, type PersistOptions, type PersistedEvent, type PersistenceAdapter, PersistenceAdapterTag, type PersistenceConfig, PersistenceError, type PersistentActorRef, type PersistentMachine, type ProvideHandlers, ProvisionValidationError, type RestoreFailure, type RestoreResult, type SimulationResult, Slot, type EffectHandlers as SlotEffectHandlers, type EffectSlot as SlotEffectSlot, SlotProvisionError, type Snapshot, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, type SystemEvent, type SystemEventListener, type TestHarness, type TestHarnessOptions, type Transition, type TransitionEvent, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, restorePersistentActor, simulate };
@@ -1,10 +1,10 @@
1
- import { Context, Schema } from "effect";
1
+ import { Schema, ServiceMap } from "effect";
2
2
 
3
3
  //#region src/inspection.d.ts
4
4
  /**
5
5
  * Resolve a type param: if it's a Schema, extract `.Type`; otherwise use as-is.
6
6
  */
7
- type ResolveType<T> = T extends Schema.Schema<infer A, infer _I, infer _R> ? A : T;
7
+ type ResolveType<T> = T extends Schema.Schema<infer A> ? A : T;
8
8
  /**
9
9
  * Event emitted when an actor is spawned
10
10
  */
@@ -91,7 +91,7 @@ interface Inspector<S, E> {
91
91
  * Inspector service tag - optional service for machine introspection
92
92
  * Uses `any` types to allow variance flexibility when providing the service
93
93
  */
94
- declare const Inspector: Context.Tag<Inspector<any, any>, Inspector<any, any>>;
94
+ declare const Inspector: ServiceMap.Service<Inspector<any, any>, Inspector<any, any>>;
95
95
  /**
96
96
  * Create an inspector from a callback function.
97
97
  *
@@ -1,11 +1,11 @@
1
- import { Context } from "effect";
1
+ import { ServiceMap } from "effect";
2
2
 
3
3
  //#region src/inspection.ts
4
4
  /**
5
5
  * Inspector service tag - optional service for machine introspection
6
6
  * Uses `any` types to allow variance flexibility when providing the service
7
7
  */
8
- const Inspector = Context.GenericTag("@effect/machine/Inspector");
8
+ const Inspector = ServiceMap.Service("@effect/machine/Inspector");
9
9
  /**
10
10
  * Create an inspector from a callback function.
11
11
  *