effect-machine 0.1.0 → 0.2.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/src/actor.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * - Actor creation and event loop
8
8
  */
9
9
  import {
10
- Clock,
10
+ Cause,
11
11
  Context,
12
12
  Effect,
13
13
  Exit,
@@ -16,22 +16,28 @@ import {
16
16
  MutableHashMap,
17
17
  Option,
18
18
  Queue,
19
+ Ref,
19
20
  Scope,
21
+ Stream,
20
22
  SubscriptionRef,
21
23
  } from "effect";
22
- import type { Stream } from "effect";
23
24
 
24
25
  import type { Machine, MachineRef } from "./machine.js";
25
- import type { InspectionEvent, Inspector } from "./inspection.js";
26
+ import type { Inspector } from "./inspection.js";
26
27
  import { Inspector as InspectorTag } from "./inspection.js";
27
28
  import { processEventCore, runSpawnEffects, resolveTransition } from "./internal/transition.js";
28
- import type { ProcessEventHooks } from "./internal/transition.js";
29
+ import type { ProcessEventError, ProcessEventHooks } from "./internal/transition.js";
30
+ import { emitWithTimestamp } from "./internal/inspection.js";
29
31
 
30
32
  // Re-export for external use (cluster, persistence)
31
33
  export { resolveTransition, runSpawnEffects, processEventCore } from "./internal/transition.js";
32
- export type { ProcessEventHooks, ProcessEventResult } from "./internal/transition.js";
34
+ export type {
35
+ ProcessEventError,
36
+ ProcessEventHooks,
37
+ ProcessEventResult,
38
+ } from "./internal/transition.js";
33
39
  import type { GuardsDef, EffectsDef } from "./slot.js";
34
- import { DuplicateActorError } from "./errors.js";
40
+ import { DuplicateActorError, UnprovidedSlotsError } from "./errors.js";
35
41
  import { INTERNAL_INIT_EVENT } from "./internal/utils.js";
36
42
  import type {
37
43
  ActorMetadata,
@@ -111,6 +117,24 @@ export interface ActorRef<State extends { readonly _tag: string }, Event> {
111
117
  */
112
118
  readonly changes: Stream.Stream<State>;
113
119
 
120
+ /**
121
+ * Wait for a state that matches predicate (includes current snapshot)
122
+ */
123
+ readonly waitFor: (predicate: (state: State) => boolean) => Effect.Effect<State>;
124
+
125
+ /**
126
+ * Wait for a final state (includes current snapshot)
127
+ */
128
+ readonly awaitFinal: Effect.Effect<State>;
129
+
130
+ /**
131
+ * Send event and wait for predicate or final state
132
+ */
133
+ readonly sendAndWait: (
134
+ event: Event,
135
+ predicate?: (state: State) => boolean,
136
+ ) => Effect.Effect<State>;
137
+
114
138
  /**
115
139
  * Subscribe to state changes (sync callback)
116
140
  * Returns unsubscribe function
@@ -162,15 +186,15 @@ export interface ActorSystem {
162
186
  id: string,
163
187
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Schema fields need wide acceptance
164
188
  machine: Machine<S, E, R, any, any, GD, EFD>,
165
- ): Effect.Effect<ActorRef<S, E>, DuplicateActorError, R | Scope.Scope>;
189
+ ): Effect.Effect<ActorRef<S, E>, DuplicateActorError | UnprovidedSlotsError, R | Scope.Scope>;
166
190
 
167
191
  // Persistent machine overload
168
192
  <S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
169
193
  id: string,
170
194
  machine: PersistentMachine<S, E, R>,
171
195
  ): Effect.Effect<
172
- PersistentActorRef<S, E>,
173
- PersistenceError | VersionConflictError | DuplicateActorError,
196
+ PersistentActorRef<S, E, R>,
197
+ PersistenceError | VersionConflictError | DuplicateActorError | UnprovidedSlotsError,
174
198
  R | Scope.Scope | PersistenceAdapterTag
175
199
  >;
176
200
  };
@@ -193,8 +217,8 @@ export interface ActorSystem {
193
217
  id: string,
194
218
  machine: PersistentMachine<S, E, R>,
195
219
  ) => Effect.Effect<
196
- Option.Option<PersistentActorRef<S, E>>,
197
- PersistenceError | DuplicateActorError,
220
+ Option.Option<PersistentActorRef<S, E, R>>,
221
+ PersistenceError | DuplicateActorError | UnprovidedSlotsError,
198
222
  R | Scope.Scope | PersistenceAdapterTag
199
223
  >;
200
224
 
@@ -243,7 +267,7 @@ export interface ActorSystem {
243
267
  >(
244
268
  ids: ReadonlyArray<string>,
245
269
  machine: PersistentMachine<S, E, R>,
246
- ) => Effect.Effect<RestoreResult<S, E>, never, R | Scope.Scope | PersistenceAdapterTag>;
270
+ ) => Effect.Effect<RestoreResult<S, E, R>, never, R | Scope.Scope | PersistenceAdapterTag>;
247
271
 
248
272
  /**
249
273
  * Restore all persisted actors for a machine type.
@@ -265,7 +289,7 @@ export interface ActorSystem {
265
289
  machine: PersistentMachine<S, E, R>,
266
290
  options?: { filter?: (meta: ActorMetadata) => boolean },
267
291
  ) => Effect.Effect<
268
- RestoreResult<S, E>,
292
+ RestoreResult<S, E, R>,
269
293
  PersistenceError,
270
294
  R | Scope.Scope | PersistenceAdapterTag
271
295
  >;
@@ -288,7 +312,11 @@ export type Listeners<S> = Set<(state: S) => void>;
288
312
  */
289
313
  export const notifyListeners = <S>(listeners: Listeners<S>, state: S): void => {
290
314
  for (const listener of listeners) {
291
- listener(state);
315
+ try {
316
+ listener(state);
317
+ } catch {
318
+ // Ignore listener failures to avoid crashing the actor loop
319
+ }
292
320
  }
293
321
  };
294
322
 
@@ -307,50 +335,84 @@ export const buildActorRefCore = <
307
335
  machine: Machine<S, E, R, any, any, GD, EFD>,
308
336
  stateRef: SubscriptionRef.SubscriptionRef<S>,
309
337
  eventQueue: Queue.Queue<E>,
338
+ stoppedRef: Ref.Ref<boolean>,
310
339
  listeners: Listeners<S>,
311
340
  stop: Effect.Effect<void>,
312
- ): ActorRef<S, E> =>
313
- ({
341
+ ): ActorRef<S, E> => {
342
+ const send = Effect.fn("effect-machine.actor.send")(function* (event: E) {
343
+ const stopped = yield* Ref.get(stoppedRef);
344
+ if (stopped) {
345
+ return;
346
+ }
347
+ yield* Queue.offer(eventQueue, event);
348
+ });
349
+
350
+ const snapshot = SubscriptionRef.get(stateRef).pipe(
351
+ Effect.withSpan("effect-machine.actor.snapshot"),
352
+ );
353
+
354
+ const matches = Effect.fn("effect-machine.actor.matches")(function* (tag: S["_tag"]) {
355
+ const state = yield* SubscriptionRef.get(stateRef);
356
+ return state._tag === tag;
357
+ });
358
+
359
+ const can = Effect.fn("effect-machine.actor.can")(function* (event: E) {
360
+ const state = yield* SubscriptionRef.get(stateRef);
361
+ return resolveTransition(machine, state, event) !== undefined;
362
+ });
363
+
364
+ const waitFor = Effect.fn("effect-machine.actor.waitFor")(function* (
365
+ predicate: (state: S) => boolean,
366
+ ) {
367
+ const current = yield* SubscriptionRef.get(stateRef);
368
+ if (predicate(current)) {
369
+ return current;
370
+ }
371
+ const next = yield* stateRef.changes.pipe(Stream.filter(predicate), Stream.runHead);
372
+ return Option.getOrElse(next, () => current);
373
+ });
374
+
375
+ const awaitFinal = waitFor((state) => machine.finalStates.has(state._tag)).pipe(
376
+ Effect.withSpan("effect-machine.actor.awaitFinal"),
377
+ );
378
+
379
+ const sendAndWait = Effect.fn("effect-machine.actor.sendAndWait")(function* (
380
+ event: E,
381
+ predicate?: (state: S) => boolean,
382
+ ) {
383
+ yield* send(event);
384
+ if (predicate !== undefined) {
385
+ return yield* waitFor(predicate);
386
+ }
387
+ return yield* awaitFinal;
388
+ });
389
+
390
+ return {
314
391
  id,
315
- send: (event) => Queue.offer(eventQueue, event),
392
+ send,
316
393
  state: stateRef,
317
394
  stop,
318
- snapshot: SubscriptionRef.get(stateRef),
395
+ snapshot,
319
396
  snapshotSync: () => Effect.runSync(SubscriptionRef.get(stateRef)),
320
- matches: (tag) => Effect.map(SubscriptionRef.get(stateRef), (s) => s._tag === tag),
397
+ matches,
321
398
  matchesSync: (tag) => Effect.runSync(SubscriptionRef.get(stateRef))._tag === tag,
322
- can: (event) =>
323
- Effect.map(
324
- SubscriptionRef.get(stateRef),
325
- (s) => resolveTransition(machine, s, event) !== undefined,
326
- ),
399
+ can,
327
400
  canSync: (event) => {
328
401
  const state = Effect.runSync(SubscriptionRef.get(stateRef));
329
402
  return resolveTransition(machine, state, event) !== undefined;
330
403
  },
331
404
  changes: stateRef.changes,
405
+ waitFor,
406
+ awaitFinal,
407
+ sendAndWait,
332
408
  subscribe: (fn) => {
333
409
  listeners.add(fn);
334
410
  return () => {
335
411
  listeners.delete(fn);
336
412
  };
337
413
  },
338
- });
339
-
340
- // ============================================================================
341
- // Inspection Helpers
342
- // ============================================================================
343
-
344
- /** Emit an inspection event with timestamp from Clock */
345
- const emitWithTimestamp = <S, E>(
346
- inspector: Inspector<S, E> | undefined,
347
- makeEvent: (timestamp: number) => InspectionEvent<S, E>,
348
- ): Effect.Effect<void> =>
349
- inspector === undefined
350
- ? Effect.void
351
- : Effect.flatMap(Clock.currentTimeMillis, (timestamp) =>
352
- Effect.sync(() => inspector.onInspect(makeEvent(timestamp))),
353
- );
414
+ };
415
+ };
354
416
 
355
417
  // ============================================================================
356
418
  // Actor Creation and Event Loop
@@ -359,143 +421,142 @@ const emitWithTimestamp = <S, E>(
359
421
  /**
360
422
  * Create and start an actor for a machine
361
423
  */
362
- export const createActor = <
424
+ export const createActor = Effect.fn("effect-machine.actor.spawn")(function* <
363
425
  S extends { readonly _tag: string },
364
426
  E extends { readonly _tag: string },
365
427
  R,
366
428
  GD extends GuardsDef,
367
429
  EFD extends EffectsDef,
368
- >(
369
- id: string,
370
- machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>,
371
- ): Effect.Effect<ActorRef<S, E>, never, R> =>
372
- Effect.withSpan("effect-machine.actor.spawn", {
373
- attributes: { "effect_machine.actor.id": id },
374
- })(
375
- Effect.gen(function* () {
376
- // Get optional inspector from context
377
- const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(InspectorTag)) as
378
- | Inspector<S, E>
379
- | undefined;
380
-
381
- // Create self reference for sending events
382
- const eventQueue = yield* Queue.unbounded<E>();
383
- const self: MachineRef<E> = {
384
- send: (event) => Queue.offer(eventQueue, event),
385
- };
386
-
387
- // Annotate span with initial state
388
- yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", machine.initial._tag);
430
+ >(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>) {
431
+ yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
389
432
 
390
- // Emit spawn event
391
- yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
392
- type: "@machine.spawn",
393
- actorId: id,
394
- initialState: machine.initial,
395
- timestamp,
396
- }));
397
-
398
- // Initialize state
399
- const stateRef = yield* SubscriptionRef.make(machine.initial);
400
- const listeners: Listeners<S> = new Set();
401
-
402
- // Fork background effects (run for entire machine lifetime)
403
- const backgroundFibers: Fiber.Fiber<void, never>[] = [];
404
- const initEvent = { _tag: INTERNAL_INIT_EVENT } as E;
405
- const { effects: effectSlots } = machine._createSlotAccessors({
406
- state: machine.initial,
407
- event: initEvent,
408
- self,
409
- });
433
+ const missing = machine._missingSlots();
434
+ if (missing.length > 0) {
435
+ return yield* new UnprovidedSlotsError({ slots: missing });
436
+ }
410
437
 
411
- for (const bg of machine.backgroundEffects) {
412
- const fiber = yield* Effect.fork(
413
- bg.handler({ state: machine.initial, event: initEvent, self, effects: effectSlots }),
414
- );
415
- backgroundFibers.push(fiber);
438
+ // Get optional inspector from context
439
+ const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(InspectorTag)) as
440
+ | Inspector<S, E>
441
+ | undefined;
442
+
443
+ // Create self reference for sending events
444
+ const eventQueue = yield* Queue.unbounded<E>();
445
+ const stoppedRef = yield* Ref.make(false);
446
+ const self: MachineRef<E> = {
447
+ send: Effect.fn("effect-machine.actor.self.send")(function* (event: E) {
448
+ const stopped = yield* Ref.get(stoppedRef);
449
+ if (stopped) {
450
+ return;
416
451
  }
452
+ yield* Queue.offer(eventQueue, event);
453
+ }),
454
+ };
417
455
 
418
- // Create state scope for initial state's spawn effects
419
- const stateScopeRef: { current: Scope.CloseableScope } = {
420
- current: yield* Scope.make(),
421
- };
456
+ // Annotate span with initial state
457
+ yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", machine.initial._tag);
458
+
459
+ // Emit spawn event
460
+ yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
461
+ type: "@machine.spawn",
462
+ actorId: id,
463
+ initialState: machine.initial,
464
+ timestamp,
465
+ }));
466
+
467
+ // Initialize state
468
+ const stateRef = yield* SubscriptionRef.make(machine.initial);
469
+ const listeners: Listeners<S> = new Set();
470
+
471
+ // Fork background effects (run for entire machine lifetime)
472
+ const backgroundFibers: Fiber.Fiber<void, never>[] = [];
473
+ const initEvent = { _tag: INTERNAL_INIT_EVENT } as E;
474
+ const ctx = { state: machine.initial, event: initEvent, self };
475
+ const { effects: effectSlots } = machine._slots;
476
+
477
+ for (const bg of machine.backgroundEffects) {
478
+ const fiber = yield* Effect.fork(
479
+ bg
480
+ .handler({ state: machine.initial, event: initEvent, self, effects: effectSlots })
481
+ .pipe(Effect.provideService(machine.Context, ctx)),
482
+ );
483
+ backgroundFibers.push(fiber);
484
+ }
422
485
 
423
- // Run initial spawn effects
424
- yield* runSpawnEffectsWithInspection(
425
- machine,
426
- machine.initial,
427
- initEvent,
428
- self,
429
- stateScopeRef.current,
430
- id,
431
- inspectorValue,
432
- );
433
-
434
- // Check if initial state (after always) is final
435
- if (machine.finalStates.has(machine.initial._tag)) {
436
- // Close state scope and interrupt background effects
437
- yield* Scope.close(stateScopeRef.current, Exit.void);
438
- yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
439
- yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
440
- type: "@machine.stop",
441
- actorId: id,
442
- finalState: machine.initial,
443
- timestamp,
444
- }));
445
- return buildActorRefCore(
446
- id,
447
- machine,
448
- stateRef,
449
- eventQueue,
450
- listeners,
451
- Queue.shutdown(eventQueue).pipe(Effect.asVoid),
452
- );
453
- }
486
+ // Create state scope for initial state's spawn effects
487
+ const stateScopeRef: { current: Scope.CloseableScope } = {
488
+ current: yield* Scope.make(),
489
+ };
454
490
 
455
- // Start the event loop
456
- const loopFiber = yield* Effect.fork(
457
- eventLoop(
458
- machine,
459
- stateRef,
460
- eventQueue,
461
- self,
462
- listeners,
463
- backgroundFibers,
464
- stateScopeRef,
465
- id,
466
- inspectorValue,
467
- ),
468
- );
491
+ // Run initial spawn effects
492
+ yield* runSpawnEffectsWithInspection(
493
+ machine,
494
+ machine.initial,
495
+ initEvent,
496
+ self,
497
+ stateScopeRef.current,
498
+ id,
499
+ inspectorValue,
500
+ );
469
501
 
470
- return buildActorRefCore(
471
- id,
472
- machine,
473
- stateRef,
474
- eventQueue,
475
- listeners,
476
- Effect.gen(function* () {
477
- const finalState = yield* SubscriptionRef.get(stateRef);
478
- yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
479
- type: "@machine.stop",
480
- actorId: id,
481
- finalState,
482
- timestamp,
483
- }));
484
- yield* Queue.shutdown(eventQueue);
485
- yield* Fiber.interrupt(loopFiber);
486
- // Close state scope (interrupts spawn fibers)
487
- yield* Scope.close(stateScopeRef.current, Exit.void);
488
- // Interrupt background effects (in parallel)
489
- yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
490
- }).pipe(Effect.asVoid),
491
- );
492
- }),
502
+ // Check if initial state (after always) is final
503
+ if (machine.finalStates.has(machine.initial._tag)) {
504
+ // Close state scope and interrupt background effects
505
+ yield* Scope.close(stateScopeRef.current, Exit.void);
506
+ yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
507
+ yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
508
+ type: "@machine.stop",
509
+ actorId: id,
510
+ finalState: machine.initial,
511
+ timestamp,
512
+ }));
513
+ yield* Ref.set(stoppedRef, true);
514
+ const stop = Ref.set(stoppedRef, true).pipe(
515
+ Effect.withSpan("effect-machine.actor.stop"),
516
+ Effect.asVoid,
517
+ );
518
+ return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, stop);
519
+ }
520
+
521
+ // Start the event loop
522
+ const loopFiber = yield* Effect.fork(
523
+ eventLoop(
524
+ machine,
525
+ stateRef,
526
+ eventQueue,
527
+ stoppedRef,
528
+ self,
529
+ listeners,
530
+ backgroundFibers,
531
+ stateScopeRef,
532
+ id,
533
+ inspectorValue,
534
+ ),
493
535
  );
494
536
 
537
+ const stop = Effect.gen(function* () {
538
+ const finalState = yield* SubscriptionRef.get(stateRef);
539
+ yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
540
+ type: "@machine.stop",
541
+ actorId: id,
542
+ finalState,
543
+ timestamp,
544
+ }));
545
+ yield* Ref.set(stoppedRef, true);
546
+ yield* Fiber.interrupt(loopFiber);
547
+ // Close state scope (interrupts spawn fibers)
548
+ yield* Scope.close(stateScopeRef.current, Exit.void);
549
+ // Interrupt background effects (in parallel)
550
+ yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
551
+ }).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid);
552
+
553
+ return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, stop);
554
+ });
555
+
495
556
  /**
496
557
  * Main event loop for the actor
497
558
  */
498
- const eventLoop = <
559
+ const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* <
499
560
  S extends { readonly _tag: string },
500
561
  E extends { readonly _tag: string },
501
562
  R,
@@ -505,55 +566,56 @@ const eventLoop = <
505
566
  machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>,
506
567
  stateRef: SubscriptionRef.SubscriptionRef<S>,
507
568
  eventQueue: Queue.Queue<E>,
569
+ stoppedRef: Ref.Ref<boolean>,
508
570
  self: MachineRef<E>,
509
571
  listeners: Listeners<S>,
510
572
  backgroundFibers: Fiber.Fiber<void, never>[],
511
573
  stateScopeRef: { current: Scope.CloseableScope },
512
574
  actorId: string,
513
575
  inspector?: Inspector<S, E>,
514
- ): Effect.Effect<void, never, R> =>
515
- Effect.gen(function* () {
516
- while (true) {
517
- // Block waiting for next event - will fail with QueueShutdown when queue is shut down
518
- const event = yield* Queue.take(eventQueue);
519
-
520
- const currentState = yield* SubscriptionRef.get(stateRef);
521
-
522
- // Process event in a span
523
- const shouldStop = yield* Effect.withSpan("effect-machine.event.process", {
524
- attributes: {
525
- "effect_machine.actor.id": actorId,
526
- "effect_machine.state.current": currentState._tag,
527
- "effect_machine.event.type": event._tag,
528
- },
529
- })(
530
- processEvent(
531
- machine,
532
- currentState,
533
- event,
534
- stateRef,
535
- self,
536
- listeners,
537
- stateScopeRef,
538
- actorId,
539
- inspector,
540
- ),
541
- );
542
-
543
- if (shouldStop) {
544
- // Close state scope and interrupt background effects when reaching final state
545
- yield* Scope.close(stateScopeRef.current, Exit.void);
546
- yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
547
- return;
548
- }
576
+ ) {
577
+ while (true) {
578
+ // Block waiting for next event - will fail with QueueShutdown when queue is shut down
579
+ const event = yield* Queue.take(eventQueue);
580
+
581
+ const currentState = yield* SubscriptionRef.get(stateRef);
582
+
583
+ // Process event in a span
584
+ const shouldStop = yield* Effect.withSpan("effect-machine.event.process", {
585
+ attributes: {
586
+ "effect_machine.actor.id": actorId,
587
+ "effect_machine.state.current": currentState._tag,
588
+ "effect_machine.event.type": event._tag,
589
+ },
590
+ })(
591
+ processEvent(
592
+ machine,
593
+ currentState,
594
+ event,
595
+ stateRef,
596
+ self,
597
+ listeners,
598
+ stateScopeRef,
599
+ actorId,
600
+ inspector,
601
+ ),
602
+ );
603
+
604
+ if (shouldStop) {
605
+ // Close state scope and interrupt background effects when reaching final state
606
+ yield* Ref.set(stoppedRef, true);
607
+ yield* Scope.close(stateScopeRef.current, Exit.void);
608
+ yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
609
+ return;
549
610
  }
550
- });
611
+ }
612
+ });
551
613
 
552
614
  /**
553
615
  * Process a single event, returning true if the actor should stop.
554
616
  * Wraps processEventCore with actor-specific concerns (inspection, listeners, state ref).
555
617
  */
556
- const processEvent = <
618
+ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* <
557
619
  S extends { readonly _tag: string },
558
620
  E extends { readonly _tag: string },
559
621
  R,
@@ -569,89 +631,91 @@ const processEvent = <
569
631
  stateScopeRef: { current: Scope.CloseableScope },
570
632
  actorId: string,
571
633
  inspector?: Inspector<S, E>,
572
- ): Effect.Effect<boolean, never, R> =>
573
- Effect.gen(function* () {
574
- // Emit event received
575
- yield* emitWithTimestamp(inspector, (timestamp) => ({
576
- type: "@machine.event",
577
- actorId,
578
- state: currentState,
579
- event,
580
- timestamp,
581
- }));
582
-
583
- // Build inspection hooks for processEventCore
584
- const hooks: ProcessEventHooks<S, E> | undefined =
585
- inspector === undefined
586
- ? undefined
587
- : {
588
- onSpawnEffect: (state) =>
589
- emitWithTimestamp(inspector, (timestamp) => ({
590
- type: "@machine.effect",
591
- actorId,
592
- effectType: "spawn",
593
- state,
594
- timestamp,
595
- })),
596
- onTransition: (from, to, ev) =>
597
- emitWithTimestamp(inspector, (timestamp) => ({
598
- type: "@machine.transition",
599
- actorId,
600
- fromState: from,
601
- toState: to,
602
- event: ev,
603
- timestamp,
604
- })),
605
- };
606
-
607
- // Process event using shared core
608
- const result = yield* processEventCore(
609
- machine,
610
- currentState,
611
- event,
612
- self,
613
- stateScopeRef,
614
- hooks,
615
- );
616
-
617
- if (!result.transitioned) {
618
- yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", false);
619
- return false;
620
- }
634
+ ) {
635
+ // Emit event received
636
+ yield* emitWithTimestamp(inspector, (timestamp) => ({
637
+ type: "@machine.event",
638
+ actorId,
639
+ state: currentState,
640
+ event,
641
+ timestamp,
642
+ }));
643
+
644
+ // Build inspection hooks for processEventCore
645
+ const hooks: ProcessEventHooks<S, E> | undefined =
646
+ inspector === undefined
647
+ ? undefined
648
+ : {
649
+ onSpawnEffect: (state) =>
650
+ emitWithTimestamp(inspector, (timestamp) => ({
651
+ type: "@machine.effect",
652
+ actorId,
653
+ effectType: "spawn",
654
+ state,
655
+ timestamp,
656
+ })),
657
+ onTransition: (from, to, ev) =>
658
+ emitWithTimestamp(inspector, (timestamp) => ({
659
+ type: "@machine.transition",
660
+ actorId,
661
+ fromState: from,
662
+ toState: to,
663
+ event: ev,
664
+ timestamp,
665
+ })),
666
+ onError: (info) =>
667
+ emitWithTimestamp(inspector, (timestamp) => ({
668
+ type: "@machine.error",
669
+ actorId,
670
+ phase: info.phase,
671
+ state: info.state,
672
+ event: info.event,
673
+ error: Cause.pretty(info.cause),
674
+ timestamp,
675
+ })),
676
+ };
677
+
678
+ // Process event using shared core
679
+ const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, hooks);
680
+
681
+ if (!result.transitioned) {
682
+ yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", false);
683
+ return false;
684
+ }
621
685
 
622
- yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
686
+ yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
623
687
 
624
- // Update state ref and notify listeners
625
- yield* SubscriptionRef.set(stateRef, result.newState);
626
- notifyListeners(listeners, result.newState);
688
+ // Update state ref and notify listeners
689
+ yield* SubscriptionRef.set(stateRef, result.newState);
690
+ notifyListeners(listeners, result.newState);
627
691
 
628
- if (result.lifecycleRan) {
629
- yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
630
- yield* Effect.annotateCurrentSpan("effect_machine.state.to", result.newState._tag);
692
+ if (result.lifecycleRan) {
693
+ yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
694
+ yield* Effect.annotateCurrentSpan("effect_machine.state.to", result.newState._tag);
631
695
 
632
- // Transition inspection event emitted via hooks in processEventCore
696
+ // Transition inspection event emitted via hooks in processEventCore
633
697
 
634
- // Check if new state is final
635
- if (result.isFinal) {
636
- yield* emitWithTimestamp(inspector, (timestamp) => ({
637
- type: "@machine.stop",
638
- actorId,
639
- finalState: result.newState,
640
- timestamp,
641
- }));
642
- return true;
643
- }
698
+ // Check if new state is final
699
+ if (result.isFinal) {
700
+ yield* emitWithTimestamp(inspector, (timestamp) => ({
701
+ type: "@machine.stop",
702
+ actorId,
703
+ finalState: result.newState,
704
+ timestamp,
705
+ }));
706
+ return true;
644
707
  }
708
+ }
645
709
 
646
- return false;
647
- });
710
+ return false;
711
+ });
648
712
 
649
713
  /**
650
714
  * Run spawn effects with actor-specific inspection and tracing.
651
715
  * Wraps the core runSpawnEffects with inspection events and spans.
652
716
  * @internal
653
717
  */
654
- const runSpawnEffectsWithInspection = <
718
+ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffects")(function* <
655
719
  S extends { readonly _tag: string },
656
720
  E extends { readonly _tag: string },
657
721
  R,
@@ -665,20 +729,33 @@ const runSpawnEffectsWithInspection = <
665
729
  stateScope: Scope.CloseableScope,
666
730
  actorId: string,
667
731
  inspector?: Inspector<S, E>,
668
- ): Effect.Effect<void, never, R> =>
669
- Effect.gen(function* () {
670
- // Emit inspection event before running effects
671
- yield* emitWithTimestamp(inspector, (timestamp) => ({
672
- type: "@machine.effect",
673
- actorId,
674
- effectType: "spawn",
675
- state,
676
- timestamp,
677
- }));
732
+ ) {
733
+ // Emit inspection event before running effects
734
+ yield* emitWithTimestamp(inspector, (timestamp) => ({
735
+ type: "@machine.effect",
736
+ actorId,
737
+ effectType: "spawn",
738
+ state,
739
+ timestamp,
740
+ }));
741
+
742
+ // Use shared core
743
+ const onError =
744
+ inspector === undefined
745
+ ? undefined
746
+ : (info: ProcessEventError<S, E>) =>
747
+ emitWithTimestamp(inspector, (timestamp) => ({
748
+ type: "@machine.error",
749
+ actorId,
750
+ phase: info.phase,
751
+ state: info.state,
752
+ event: info.event,
753
+ error: Cause.pretty(info.cause),
754
+ timestamp,
755
+ }));
678
756
 
679
- // Use shared core
680
- yield* runSpawnEffects(machine, state, event, self, stateScope);
681
- });
757
+ yield* runSpawnEffects(machine, state, event, self, stateScope, onError);
758
+ });
682
759
 
683
760
  // ============================================================================
684
761
  // ActorSystem Implementation
@@ -687,85 +764,102 @@ const runSpawnEffectsWithInspection = <
687
764
  /**
688
765
  * Internal implementation
689
766
  */
690
- const make = Effect.sync(() => {
767
+ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
691
768
  // MutableHashMap for O(1) spawn/stop/get operations
692
769
  const actors = MutableHashMap.empty<string, ActorRef<AnyState, unknown>>();
770
+ const spawnGate = yield* Effect.makeSemaphore(1);
771
+ const withSpawnGate = spawnGate.withPermits(1);
693
772
 
694
773
  /** Check for duplicate ID, register actor, add cleanup finalizer */
695
- const registerActor = <T extends { stop: Effect.Effect<void> }>(
696
- id: string,
697
- actor: T,
698
- ): Effect.Effect<T, DuplicateActorError, Scope.Scope> =>
699
- Effect.gen(function* () {
700
- // Check if actor already exists
701
- if (MutableHashMap.has(actors, id)) {
702
- return yield* new DuplicateActorError({ actorId: id });
703
- }
774
+ const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* <
775
+ T extends { stop: Effect.Effect<void> },
776
+ >(id: string, actor: T) {
777
+ // Check if actor already exists
778
+ if (MutableHashMap.has(actors, id)) {
779
+ // Stop the newly created actor to avoid leaks
780
+ yield* actor.stop;
781
+ return yield* new DuplicateActorError({ actorId: id });
782
+ }
704
783
 
705
- // Register it - O(1)
706
- MutableHashMap.set(actors, id, actor as unknown as ActorRef<AnyState, unknown>);
784
+ // Register it - O(1)
785
+ MutableHashMap.set(actors, id, actor as unknown as ActorRef<AnyState, unknown>);
707
786
 
708
- // Register cleanup on scope finalization
709
- yield* Effect.addFinalizer(() =>
710
- Effect.gen(function* () {
711
- yield* actor.stop;
712
- MutableHashMap.remove(actors, id);
713
- }),
714
- );
787
+ // Register cleanup on scope finalization
788
+ yield* Effect.addFinalizer(
789
+ Effect.fn("effect-machine.actorSystem.register.finalizer")(function* () {
790
+ yield* actor.stop;
791
+ MutableHashMap.remove(actors, id);
792
+ }),
793
+ );
715
794
 
716
- return actor;
717
- });
795
+ return actor;
796
+ });
718
797
 
719
- const spawnRegular = <
798
+ const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* <
720
799
  S extends { readonly _tag: string },
721
800
  E extends { readonly _tag: string },
722
801
  R,
723
802
  GD extends GuardsDef = Record<string, never>,
724
803
  EFD extends EffectsDef = Record<string, never>,
725
- >(
726
- id: string,
727
- machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>,
728
- ): Effect.Effect<ActorRef<S, E>, DuplicateActorError, R | Scope.Scope> =>
729
- Effect.gen(function* () {
730
- // Create and register the actor
731
- const actor = yield* createActor(id, machine);
732
- return yield* registerActor(id, actor);
733
- });
734
-
735
- const spawnPersistent = <
804
+ >(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>) {
805
+ if (MutableHashMap.has(actors, id)) {
806
+ return yield* new DuplicateActorError({ actorId: id });
807
+ }
808
+ // Create and register the actor
809
+ const actor = yield* createActor(id, machine);
810
+ return yield* registerActor(id, actor);
811
+ });
812
+
813
+ const spawnPersistent = Effect.fn("effect-machine.actorSystem.spawnPersistent")(function* <
814
+ S extends { readonly _tag: string },
815
+ E extends { readonly _tag: string },
816
+ R,
817
+ >(id: string, persistentMachine: PersistentMachine<S, E, R>) {
818
+ if (MutableHashMap.has(actors, id)) {
819
+ return yield* new DuplicateActorError({ actorId: id });
820
+ }
821
+ const adapter = yield* PersistenceAdapterTag;
822
+
823
+ // Try to load existing snapshot
824
+ const maybeSnapshot = yield* adapter.loadSnapshot(
825
+ id,
826
+ persistentMachine.persistence.stateSchema,
827
+ );
828
+
829
+ // Load events after snapshot (or all events if no snapshot)
830
+ const events = yield* adapter.loadEvents(
831
+ id,
832
+ persistentMachine.persistence.eventSchema,
833
+ Option.isSome(maybeSnapshot) ? maybeSnapshot.value.version : undefined,
834
+ );
835
+
836
+ // Create and register the persistent actor
837
+ const actor = yield* createPersistentActor(id, persistentMachine, maybeSnapshot, events);
838
+ return yield* registerActor(id, actor);
839
+ });
840
+
841
+ const spawnImpl = Effect.fn("effect-machine.actorSystem.spawn")(function* <
736
842
  S extends { readonly _tag: string },
737
843
  E extends { readonly _tag: string },
738
844
  R,
845
+ GD extends GuardsDef = Record<string, never>,
846
+ EFD extends EffectsDef = Record<string, never>,
739
847
  >(
740
848
  id: string,
741
- persistentMachine: PersistentMachine<S, E, R>,
742
- ): Effect.Effect<
743
- PersistentActorRef<S, E>,
744
- PersistenceError | VersionConflictError | DuplicateActorError,
745
- R | Scope.Scope | PersistenceAdapterTag
746
- > =>
747
- Effect.gen(function* () {
748
- const adapter = yield* PersistenceAdapterTag;
749
-
750
- // Try to load existing snapshot
751
- const maybeSnapshot = yield* adapter.loadSnapshot(
752
- id,
753
- persistentMachine.persistence.stateSchema,
754
- );
755
-
756
- // Load events after snapshot (if any)
757
- const events = Option.isSome(maybeSnapshot)
758
- ? yield* adapter.loadEvents(
759
- id,
760
- persistentMachine.persistence.eventSchema,
761
- maybeSnapshot.value.version,
762
- )
763
- : [];
764
-
765
- // Create and register the persistent actor
766
- const actor = yield* createPersistentActor(id, persistentMachine, maybeSnapshot, events);
767
- return yield* registerActor(id, actor);
768
- });
849
+ machine:
850
+ | Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>
851
+ | PersistentMachine<S, E, R>,
852
+ ) {
853
+ if (isPersistentMachine(machine)) {
854
+ // TypeScript can't narrow union with invariant generic params
855
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
856
+ return yield* spawnPersistent(id, machine as PersistentMachine<S, E, R>);
857
+ }
858
+ return yield* spawnRegular(
859
+ id,
860
+ machine as Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>,
861
+ );
862
+ });
769
863
 
770
864
  // Type-safe overloaded spawn implementation
771
865
  function spawn<
@@ -777,13 +871,13 @@ const make = Effect.sync(() => {
777
871
  >(
778
872
  id: string,
779
873
  machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>,
780
- ): Effect.Effect<ActorRef<S, E>, DuplicateActorError, R | Scope.Scope>;
874
+ ): Effect.Effect<ActorRef<S, E>, DuplicateActorError | UnprovidedSlotsError, R | Scope.Scope>;
781
875
  function spawn<S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
782
876
  id: string,
783
877
  machine: PersistentMachine<S, E, R>,
784
878
  ): Effect.Effect<
785
- PersistentActorRef<S, E>,
786
- PersistenceError | VersionConflictError | DuplicateActorError,
879
+ PersistentActorRef<S, E, R>,
880
+ PersistenceError | VersionConflictError | DuplicateActorError | UnprovidedSlotsError,
787
881
  R | Scope.Scope | PersistenceAdapterTag
788
882
  >;
789
883
  function spawn<
@@ -798,140 +892,135 @@ const make = Effect.sync(() => {
798
892
  | Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>
799
893
  | PersistentMachine<S, E, R>,
800
894
  ):
801
- | Effect.Effect<ActorRef<S, E>, DuplicateActorError, R | Scope.Scope>
895
+ | Effect.Effect<ActorRef<S, E>, DuplicateActorError | UnprovidedSlotsError, R | Scope.Scope>
802
896
  | Effect.Effect<
803
- PersistentActorRef<S, E>,
804
- PersistenceError | VersionConflictError | DuplicateActorError,
897
+ PersistentActorRef<S, E, R>,
898
+ PersistenceError | VersionConflictError | DuplicateActorError | UnprovidedSlotsError,
805
899
  R | Scope.Scope | PersistenceAdapterTag
806
900
  > {
807
- if (isPersistentMachine(machine)) {
808
- // TypeScript can't narrow union with invariant generic params
809
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
810
- return spawnPersistent(id, machine as PersistentMachine<S, E, R>);
811
- }
812
- return spawnRegular(
813
- id,
814
- machine as Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>,
815
- );
901
+ return withSpawnGate(spawnImpl(id, machine)) as
902
+ | Effect.Effect<ActorRef<S, E>, DuplicateActorError | UnprovidedSlotsError, R | Scope.Scope>
903
+ | Effect.Effect<
904
+ PersistentActorRef<S, E, R>,
905
+ PersistenceError | VersionConflictError | DuplicateActorError | UnprovidedSlotsError,
906
+ R | Scope.Scope | PersistenceAdapterTag
907
+ >;
816
908
  }
817
909
 
910
+ const restoreImpl = Effect.fn("effect-machine.actorSystem.restore")(function* <
911
+ S extends { readonly _tag: string },
912
+ E extends { readonly _tag: string },
913
+ R,
914
+ >(id: string, persistentMachine: PersistentMachine<S, E, R>) {
915
+ // Try to restore from persistence
916
+ const maybeActor = yield* restorePersistentActor(id, persistentMachine);
917
+
918
+ if (Option.isSome(maybeActor)) {
919
+ yield* registerActor(id, maybeActor.value);
920
+ }
921
+
922
+ return maybeActor;
923
+ });
818
924
  const restore = <S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
819
925
  id: string,
820
926
  persistentMachine: PersistentMachine<S, E, R>,
821
- ): Effect.Effect<
822
- Option.Option<PersistentActorRef<S, E>>,
823
- PersistenceError | DuplicateActorError,
824
- R | Scope.Scope | PersistenceAdapterTag
825
- > =>
826
- Effect.gen(function* () {
827
- // Try to restore from persistence
828
- const maybeActor = yield* restorePersistentActor(id, persistentMachine);
927
+ ) => withSpawnGate(restoreImpl(id, persistentMachine));
829
928
 
830
- if (Option.isSome(maybeActor)) {
831
- yield* registerActor(id, maybeActor.value);
832
- }
833
-
834
- return maybeActor;
835
- });
929
+ const get = Effect.fn("effect-machine.actorSystem.get")(function* (id: string) {
930
+ return yield* Effect.sync(() => MutableHashMap.get(actors, id));
931
+ });
836
932
 
837
- const get = (id: string): Effect.Effect<Option.Option<ActorRef<AnyState, unknown>>> =>
838
- Effect.sync(() => MutableHashMap.get(actors, id));
933
+ const stop = Effect.fn("effect-machine.actorSystem.stop")(function* (id: string) {
934
+ const maybeActor = MutableHashMap.get(actors, id);
935
+ if (Option.isNone(maybeActor)) {
936
+ return false;
937
+ }
839
938
 
840
- const stop = (id: string): Effect.Effect<boolean> =>
841
- Effect.gen(function* () {
842
- const maybeActor = MutableHashMap.get(actors, id);
843
- if (Option.isNone(maybeActor)) {
844
- return false;
845
- }
939
+ yield* maybeActor.value.stop;
940
+ MutableHashMap.remove(actors, id);
941
+ return true;
942
+ });
846
943
 
847
- yield* maybeActor.value.stop;
848
- MutableHashMap.remove(actors, id);
849
- return true;
850
- });
944
+ const listPersisted = Effect.fn("effect-machine.actorSystem.listPersisted")(function* () {
945
+ const adapter = yield* PersistenceAdapterTag;
946
+ if (adapter.listActors === undefined) {
947
+ return [];
948
+ }
949
+ return yield* adapter.listActors();
950
+ });
851
951
 
852
- const listPersisted = (): Effect.Effect<
853
- ReadonlyArray<ActorMetadata>,
854
- PersistenceError,
855
- PersistenceAdapterTag
856
- > =>
857
- Effect.gen(function* () {
858
- const adapter = yield* PersistenceAdapterTag;
859
- if (adapter.listActors === undefined) {
860
- return [];
952
+ const restoreMany = Effect.fn("effect-machine.actorSystem.restoreMany")(function* <
953
+ S extends { readonly _tag: string },
954
+ E extends { readonly _tag: string },
955
+ R,
956
+ >(ids: ReadonlyArray<string>, persistentMachine: PersistentMachine<S, E, R>) {
957
+ const restored: PersistentActorRef<S, E, R>[] = [];
958
+ const failed: {
959
+ id: string;
960
+ error: PersistenceError | DuplicateActorError | UnprovidedSlotsError;
961
+ }[] = [];
962
+
963
+ for (const id of ids) {
964
+ // Skip if already running
965
+ if (MutableHashMap.has(actors, id)) {
966
+ continue;
861
967
  }
862
- return yield* adapter.listActors();
863
- });
864
968
 
865
- const restoreMany = <S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
866
- ids: ReadonlyArray<string>,
867
- persistentMachine: PersistentMachine<S, E, R>,
868
- ): Effect.Effect<RestoreResult<S, E>, never, R | Scope.Scope | PersistenceAdapterTag> =>
869
- Effect.gen(function* () {
870
- const restored: PersistentActorRef<S, E>[] = [];
871
- const failed: { id: string; error: PersistenceError | DuplicateActorError }[] = [];
872
-
873
- for (const id of ids) {
874
- // Skip if already running
875
- if (MutableHashMap.has(actors, id)) {
876
- continue;
877
- }
878
-
879
- const result = yield* Effect.either(restore(id, persistentMachine));
880
- if (result._tag === "Left") {
881
- failed.push({ id, error: result.left });
882
- } else if (Option.isSome(result.right)) {
883
- restored.push(result.right.value);
884
- } else {
885
- // No persisted state for this ID
886
- failed.push({
887
- id,
888
- error: new PersistenceErrorClass({
889
- operation: "restore",
890
- actorId: id,
891
- message: "No persisted state found",
892
- }),
893
- });
894
- }
969
+ const result = yield* Effect.either(restore(id, persistentMachine));
970
+ if (result._tag === "Left") {
971
+ failed.push({ id, error: result.left });
972
+ } else if (Option.isSome(result.right)) {
973
+ restored.push(result.right.value);
974
+ } else {
975
+ // No persisted state for this ID
976
+ failed.push({
977
+ id,
978
+ error: new PersistenceErrorClass({
979
+ operation: "restore",
980
+ actorId: id,
981
+ message: "No persisted state found",
982
+ }),
983
+ });
895
984
  }
985
+ }
896
986
 
897
- return { restored, failed };
898
- });
987
+ return { restored, failed };
988
+ });
899
989
 
900
- const restoreAll = <S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
990
+ const restoreAll = Effect.fn("effect-machine.actorSystem.restoreAll")(function* <
991
+ S extends { readonly _tag: string },
992
+ E extends { readonly _tag: string },
993
+ R,
994
+ >(
901
995
  persistentMachine: PersistentMachine<S, E, R>,
902
996
  options?: { filter?: (meta: ActorMetadata) => boolean },
903
- ): Effect.Effect<
904
- RestoreResult<S, E>,
905
- PersistenceError,
906
- R | Scope.Scope | PersistenceAdapterTag
907
- > =>
908
- Effect.gen(function* () {
909
- const adapter = yield* PersistenceAdapterTag;
910
- if (adapter.listActors === undefined) {
911
- return { restored: [], failed: [] };
912
- }
997
+ ) {
998
+ const adapter = yield* PersistenceAdapterTag;
999
+ if (adapter.listActors === undefined) {
1000
+ return { restored: [], failed: [] };
1001
+ }
913
1002
 
914
- // Require explicit machineType to prevent cross-machine restores
915
- const machineType = persistentMachine.persistence.machineType;
916
- if (machineType === undefined) {
917
- return yield* new PersistenceErrorClass({
918
- operation: "restoreAll",
919
- actorId: "*",
920
- message: "restoreAll requires explicit machineType in persistence config",
921
- });
922
- }
1003
+ // Require explicit machineType to prevent cross-machine restores
1004
+ const machineType = persistentMachine.persistence.machineType;
1005
+ if (machineType === undefined) {
1006
+ return yield* new PersistenceErrorClass({
1007
+ operation: "restoreAll",
1008
+ actorId: "*",
1009
+ message: "restoreAll requires explicit machineType in persistence config",
1010
+ });
1011
+ }
923
1012
 
924
- const allMetadata = yield* adapter.listActors();
1013
+ const allMetadata = yield* adapter.listActors();
925
1014
 
926
- // Filter by machineType and optional user filter
927
- let filtered = allMetadata.filter((meta) => meta.machineType === machineType);
928
- if (options?.filter !== undefined) {
929
- filtered = filtered.filter(options.filter);
930
- }
1015
+ // Filter by machineType and optional user filter
1016
+ let filtered = allMetadata.filter((meta) => meta.machineType === machineType);
1017
+ if (options?.filter !== undefined) {
1018
+ filtered = filtered.filter(options.filter);
1019
+ }
931
1020
 
932
- const ids = filtered.map((meta) => meta.id);
933
- return yield* restoreMany(ids, persistentMachine);
934
- });
1021
+ const ids = filtered.map((meta) => meta.id);
1022
+ return yield* restoreMany(ids, persistentMachine);
1023
+ });
935
1024
 
936
1025
  return ActorSystem.of({ spawn, restore, get, stop, listPersisted, restoreMany, restoreAll });
937
1026
  });
@@ -939,4 +1028,4 @@ const make = Effect.sync(() => {
939
1028
  /**
940
1029
  * Default ActorSystem layer
941
1030
  */
942
- export const Default = Layer.effect(ActorSystem, make);
1031
+ export const Default = Layer.effect(ActorSystem, make());