effect-machine 0.4.0 → 0.6.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 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
@@ -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.
@@ -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,10 +6,18 @@ 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, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Runtime, Scope, 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
23
  const ActorSystem = Context.GenericTag("@effect/machine/ActorSystem");
@@ -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);
@@ -89,7 +97,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
89
97
  listeners.delete(fn);
90
98
  };
91
99
  },
92
- system
100
+ system,
101
+ children: childrenMap
93
102
  };
94
103
  };
95
104
  /**
@@ -109,12 +118,21 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
109
118
  const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
110
119
  const eventQueue = yield* Queue.unbounded();
111
120
  const stoppedRef = yield* Ref.make(false);
121
+ const childrenMap = /* @__PURE__ */ new Map();
112
122
  const self = {
113
123
  send: Effect.fn("effect-machine.actor.self.send")(function* (event) {
114
124
  if (yield* Ref.get(stoppedRef)) return;
115
125
  yield* Queue.offer(eventQueue, event);
116
126
  }),
117
- spawn: (childId, childMachine) => system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system))
127
+ spawn: (childId, childMachine) => Effect.gen(function* () {
128
+ const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
129
+ childrenMap.set(childId, child);
130
+ const maybeScope = yield* Effect.serviceOption(Scope.Scope);
131
+ if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
132
+ childrenMap.delete(childId);
133
+ }));
134
+ return child;
135
+ })
118
136
  };
119
137
  yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", machine.initial._tag);
120
138
  yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
@@ -157,7 +175,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
157
175
  }));
158
176
  yield* Ref.set(stoppedRef, true);
159
177
  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);
178
+ return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap);
161
179
  }
162
180
  const loopFiber = yield* Effect.forkDaemon(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue, system));
163
181
  return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
@@ -173,7 +191,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
173
191
  yield* Scope.close(stateScopeRef.current, Exit.void);
174
192
  yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
175
193
  if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
176
- }).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system);
194
+ }).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap);
177
195
  });
178
196
  /**
179
197
  * Main event loop for the actor
@@ -277,39 +295,58 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffec
277
295
  timestamp
278
296
  })));
279
297
  });
280
- /**
281
- * Internal implementation
282
- */
298
+ /** Notify all system event listeners (sync). */
299
+ const notifySystemListeners = (listeners, event) => {
300
+ for (const listener of listeners) try {
301
+ listener(event);
302
+ } catch {}
303
+ };
283
304
  const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
284
- const actors = MutableHashMap.empty();
305
+ const actorsMap = MutableHashMap.empty();
285
306
  const withSpawnGate = (yield* Effect.makeSemaphore(1)).withPermits(1);
307
+ const eventPubSub = yield* PubSub.unbounded();
308
+ const eventListeners = /* @__PURE__ */ new Set();
309
+ const emitSystemEvent = (event) => Effect.sync(() => notifySystemListeners(eventListeners, event)).pipe(Effect.andThen(PubSub.publish(eventPubSub, event)), Effect.catchAllCause(() => Effect.void), Effect.asVoid);
286
310
  yield* Effect.addFinalizer(() => {
287
311
  const stops = [];
288
- MutableHashMap.forEach(actors, (actor) => {
312
+ MutableHashMap.forEach(actorsMap, (actor) => {
289
313
  stops.push(actor.stop);
290
314
  });
291
- return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.asVoid);
315
+ return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.andThen(PubSub.shutdown(eventPubSub)), Effect.asVoid);
292
316
  });
293
317
  /** Check for duplicate ID, register actor, attach scope cleanup if available */
294
318
  const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* (id, actor) {
295
- if (MutableHashMap.has(actors, id)) {
319
+ if (MutableHashMap.has(actorsMap, id)) {
296
320
  yield* actor.stop;
297
321
  return yield* new DuplicateActorError({ actorId: id });
298
322
  }
299
- MutableHashMap.set(actors, id, actor);
323
+ const actorRef = actor;
324
+ MutableHashMap.set(actorsMap, id, actorRef);
325
+ yield* emitSystemEvent({
326
+ _tag: "ActorSpawned",
327
+ id,
328
+ actor: actorRef
329
+ });
300
330
  const maybeScope = yield* Effect.serviceOption(Scope.Scope);
301
331
  if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.gen(function* () {
332
+ if (MutableHashMap.has(actorsMap, id)) {
333
+ yield* emitSystemEvent({
334
+ _tag: "ActorStopped",
335
+ id,
336
+ actor: actorRef
337
+ });
338
+ MutableHashMap.remove(actorsMap, id);
339
+ }
302
340
  yield* actor.stop;
303
- MutableHashMap.remove(actors, id);
304
341
  }));
305
342
  return actor;
306
343
  });
307
344
  const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id, built) {
308
- if (MutableHashMap.has(actors, id)) return yield* new DuplicateActorError({ actorId: id });
345
+ if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
309
346
  return yield* registerActor(id, yield* createActor(id, built._inner));
310
347
  });
311
348
  const spawnPersistent = Effect.fn("effect-machine.actorSystem.spawnPersistent")(function* (id, persistentMachine) {
312
- if (MutableHashMap.has(actors, id)) return yield* new DuplicateActorError({ actorId: id });
349
+ if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
313
350
  const adapter = yield* PersistenceAdapterTag;
314
351
  const maybeSnapshot = yield* adapter.loadSnapshot(id, persistentMachine.persistence.stateSchema);
315
352
  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 +365,19 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
328
365
  });
329
366
  const restore = (id, persistentMachine) => withSpawnGate(restoreImpl(id, persistentMachine));
330
367
  const get = Effect.fn("effect-machine.actorSystem.get")(function* (id) {
331
- return yield* Effect.sync(() => MutableHashMap.get(actors, id));
368
+ return yield* Effect.sync(() => MutableHashMap.get(actorsMap, id));
332
369
  });
333
370
  const stop = Effect.fn("effect-machine.actorSystem.stop")(function* (id) {
334
- const maybeActor = MutableHashMap.get(actors, id);
371
+ const maybeActor = MutableHashMap.get(actorsMap, id);
335
372
  if (Option.isNone(maybeActor)) return false;
336
- yield* maybeActor.value.stop;
337
- MutableHashMap.remove(actors, id);
373
+ const actor = maybeActor.value;
374
+ MutableHashMap.remove(actorsMap, id);
375
+ yield* emitSystemEvent({
376
+ _tag: "ActorStopped",
377
+ id,
378
+ actor
379
+ });
380
+ yield* actor.stop;
338
381
  return true;
339
382
  });
340
383
  const listPersisted = Effect.fn("effect-machine.actorSystem.listPersisted")(function* () {
@@ -346,7 +389,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
346
389
  const restored = [];
347
390
  const failed = [];
348
391
  for (const id of ids) {
349
- if (MutableHashMap.has(actors, id)) continue;
392
+ if (MutableHashMap.has(actorsMap, id)) continue;
350
393
  const result = yield* Effect.either(restore(id, persistentMachine));
351
394
  if (result._tag === "Left") failed.push({
352
395
  id,
@@ -388,6 +431,20 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
388
431
  restore,
389
432
  get,
390
433
  stop,
434
+ events: Stream.fromPubSub(eventPubSub),
435
+ get actors() {
436
+ const snapshot = /* @__PURE__ */ new Map();
437
+ MutableHashMap.forEach(actorsMap, (actor, id) => {
438
+ snapshot.set(id, actor);
439
+ });
440
+ return snapshot;
441
+ },
442
+ subscribe: (fn) => {
443
+ eventListeners.add(fn);
444
+ return () => {
445
+ eventListeners.delete(fn);
446
+ };
447
+ },
391
448
  listPersisted,
392
449
  restoreMany,
393
450
  restoreAll
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,4 +1,4 @@
1
- import { Effect } from "effect";
1
+ import { Effect, Stream } from "effect";
2
2
 
3
3
  //#region src/internal/utils.ts
4
4
  /**
@@ -37,6 +37,11 @@ const stubSystem = {
37
37
  restore: () => Effect.die("restore not supported in stub system"),
38
38
  get: () => Effect.die("get not supported in stub system"),
39
39
  stop: () => Effect.die("stop not supported in stub system"),
40
+ events: Stream.empty,
41
+ get actors() {
42
+ return /* @__PURE__ */ new Map();
43
+ },
44
+ subscribe: () => () => {},
40
45
  listPersisted: () => Effect.die("listPersisted not supported in stub system"),
41
46
  restoreMany: () => Effect.die("restoreMany not supported in stub system"),
42
47
  restoreAll: () => Effect.die("restoreAll not supported in stub system")
@@ -31,7 +31,7 @@ const replayEvents = Effect.fn("effect-machine.persistentActor.replayEvents")(fu
31
31
  /**
32
32
  * Build PersistentActorRef with all methods
33
33
  */
34
- const buildPersistentActorRef = (id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, stop, adapter, system) => {
34
+ const buildPersistentActorRef = (id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, stop, adapter, system, childrenMap) => {
35
35
  const { machine, persistence } = persistentMachine;
36
36
  const typedMachine = machine;
37
37
  const persist = Effect.gen(function* () {
@@ -71,7 +71,7 @@ const buildPersistentActorRef = (id, persistentMachine, stateRef, versionRef, ev
71
71
  }
72
72
  });
73
73
  return {
74
- ...buildActorRefCore(id, typedMachine, stateRef, eventQueue, stoppedRef, listeners, stop, system),
74
+ ...buildActorRefCore(id, typedMachine, stateRef, eventQueue, stoppedRef, listeners, stop, system, childrenMap),
75
75
  persist,
76
76
  version,
77
77
  replayTo
@@ -92,12 +92,21 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
92
92
  const inspector = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
93
93
  const eventQueue = yield* Queue.unbounded();
94
94
  const stoppedRef = yield* Ref.make(false);
95
+ const childrenMap = /* @__PURE__ */ new Map();
95
96
  const self = {
96
97
  send: Effect.fn("effect-machine.persistentActor.self.send")(function* (event) {
97
98
  if (yield* Ref.get(stoppedRef)) return;
98
99
  yield* Queue.offer(eventQueue, event);
99
100
  }),
100
- spawn: (childId, childMachine) => system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system))
101
+ spawn: (childId, childMachine) => Effect.gen(function* () {
102
+ const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
103
+ childrenMap.set(childId, child);
104
+ const maybeScope = yield* Effect.serviceOption(Scope.Scope);
105
+ if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
106
+ childrenMap.delete(childId);
107
+ }));
108
+ return child;
109
+ })
101
110
  };
102
111
  let resolvedInitial;
103
112
  let initialVersion;
@@ -167,7 +176,7 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
167
176
  finalState: resolvedInitial,
168
177
  timestamp
169
178
  }));
170
- return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter, system);
179
+ return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter, system, childrenMap);
171
180
  }
172
181
  const loopFiber = yield* Effect.forkDaemon(persistentEventLoop(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, self, listeners, adapter, createdAt, stateScopeRef, backgroundFibers, snapshotQueue, snapshotEnabledRef, persistenceQueue, snapshotFiber, persistenceFiber, inspector, system));
173
182
  return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
@@ -184,7 +193,7 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
184
193
  yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
185
194
  yield* Fiber.interrupt(snapshotFiber);
186
195
  yield* Fiber.interrupt(persistenceFiber);
187
- }).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter, system);
196
+ }).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter, system, childrenMap);
188
197
  });
189
198
  /**
190
199
  * Main event loop for persistent actor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-machine",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cevr/effect-machine.git"