effect-machine 0.2.4 → 0.3.1

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/machine.ts CHANGED
@@ -34,7 +34,7 @@
34
34
  * )
35
35
  * .on(MyState.Running, MyEvent.Stop, () => MyState.Idle)
36
36
  * .final(MyState.Idle)
37
- * .provide({
37
+ * .build({
38
38
  * canStart: ({ threshold }) => Effect.succeed(threshold > 0),
39
39
  * notify: ({ message }) => Effect.log(message),
40
40
  * })
@@ -42,10 +42,8 @@
42
42
  *
43
43
  * @module
44
44
  */
45
- import type { Schema, Schedule, Scope, Context } from "effect";
46
- import { Cause, Effect, Exit } from "effect";
47
- import type { Pipeable } from "effect/Pipeable";
48
- import { pipeArguments } from "effect/Pipeable";
45
+ import type { Schema, Schedule, Context } from "effect";
46
+ import { Cause, Effect, Exit, Option, Scope } from "effect";
49
47
 
50
48
  import type { TransitionResult } from "./internal/utils.js";
51
49
  import { getTag } from "./internal/utils.js";
@@ -54,7 +52,6 @@ import type { MachineStateSchema, MachineEventSchema, VariantsUnion } from "./sc
54
52
  import type { PersistentMachine, WithPersistenceConfig } from "./persistence/persistent-machine.js";
55
53
  import { persist as persistImpl } from "./persistence/persistent-machine.js";
56
54
  import { SlotProvisionError, ProvisionValidationError } from "./errors.js";
57
- import type { UnprovidedSlotsError } from "./errors.js";
58
55
  import { invalidateIndex } from "./internal/transition.js";
59
56
  import type {
60
57
  GuardsSchema,
@@ -198,7 +195,7 @@ type HasEffectKeys<EFD extends EffectsDef> = [keyof EFD] extends [never]
198
195
  /** Context type passed to guard/effect handlers */
199
196
  export type SlotContext<State, Event> = MachineContext<State, Event, MachineRef<Event>>;
200
197
 
201
- /** Combined handlers for provide() - guards and effects only */
198
+ /** Combined handlers for build() - guards and effects only */
202
199
  export type ProvideHandlers<
203
200
  State,
204
201
  Event,
@@ -210,6 +207,43 @@ export type ProvideHandlers<
210
207
  ? SlotEffectHandlers<EFD, SlotContext<State, Event>, R>
211
208
  : object);
212
209
 
210
+ /** Whether the machine has any guard or effect slots */
211
+ type HasSlots<GD extends GuardsDef, EFD extends EffectsDef> =
212
+ HasGuardKeys<GD> extends true ? true : HasEffectKeys<EFD>;
213
+
214
+ // ============================================================================
215
+ // BuiltMachine
216
+ // ============================================================================
217
+
218
+ /**
219
+ * A finalized machine ready for spawning.
220
+ *
221
+ * Created by calling `.build()` on a `Machine`. This is the only type
222
+ * accepted by `Machine.spawn` and `ActorSystem.spawn` (regular overload).
223
+ * Testing utilities (`simulate`, `createTestHarness`, etc.) still accept `Machine`.
224
+ */
225
+ export class BuiltMachine<State, Event, R = never> {
226
+ /** @internal */
227
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
228
+ readonly _inner: Machine<State, Event, R, any, any, any, any>;
229
+
230
+ /** @internal */
231
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
232
+ constructor(machine: Machine<State, Event, R, any, any, any, any>) {
233
+ this._inner = machine;
234
+ }
235
+
236
+ get initial(): State {
237
+ return this._inner.initial;
238
+ }
239
+
240
+ persist(
241
+ config: PersistOptions,
242
+ ): PersistentMachine<State & { readonly _tag: string }, Event & { readonly _tag: string }, R> {
243
+ return this._inner.persist(config);
244
+ }
245
+ }
246
+
213
247
  // ============================================================================
214
248
  // Machine class
215
249
  // ============================================================================
@@ -234,7 +268,7 @@ export class Machine<
234
268
  _ED extends Record<string, Schema.Struct.Fields> = Record<string, Schema.Struct.Fields>,
235
269
  GD extends GuardsDef = Record<string, never>,
236
270
  EFD extends EffectsDef = Record<string, never>,
237
- > implements Pipeable {
271
+ > {
238
272
  readonly initial: State;
239
273
  /** @internal */ readonly _transitions: Array<Transition<State, Event, GD, EFD, R>>;
240
274
  /** @internal */ readonly _spawnEffects: Array<SpawnEffect<State, Event, EFD, R>>;
@@ -340,13 +374,9 @@ export class Machine<
340
374
  this._slots = { guards: guardSlots, effects: effectSlots };
341
375
  }
342
376
 
343
- pipe() {
344
- // eslint-disable-next-line prefer-rest-params
345
- return pipeArguments(this, arguments);
346
- }
347
-
348
377
  // ---- on ----
349
378
 
379
+ /** Register transition for a single state */
350
380
  on<
351
381
  NS extends VariantsUnion<_SD> & BrandedState,
352
382
  NE extends VariantsUnion<_ED> & BrandedEvent,
@@ -355,8 +385,31 @@ export class Machine<
355
385
  state: TaggedOrConstructor<NS>,
356
386
  event: TaggedOrConstructor<NE>,
357
387
  handler: TransitionHandler<NS, NE, RS, GD, EFD, never>,
358
- ): Machine<State, Event, R, _SD, _ED, GD, EFD> {
359
- return this.addTransition(state, event, handler, false);
388
+ ): Machine<State, Event, R, _SD, _ED, GD, EFD>;
389
+ /** Register transition for multiple states (handler receives union of state types) */
390
+ on<
391
+ NS extends ReadonlyArray<TaggedOrConstructor<VariantsUnion<_SD> & BrandedState>>,
392
+ NE extends VariantsUnion<_ED> & BrandedEvent,
393
+ RS extends VariantsUnion<_SD> & BrandedState,
394
+ >(
395
+ states: NS,
396
+ event: TaggedOrConstructor<NE>,
397
+ handler: TransitionHandler<
398
+ NS[number] extends TaggedOrConstructor<infer S> ? S : never,
399
+ NE,
400
+ RS,
401
+ GD,
402
+ EFD,
403
+ never
404
+ >,
405
+ ): Machine<State, Event, R, _SD, _ED, GD, EFD>;
406
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
407
+ on(stateOrStates: any, event: any, handler: any): Machine<State, Event, R, _SD, _ED, GD, EFD> {
408
+ const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
409
+ for (const s of states) {
410
+ this.addTransition(s, event, handler, false);
411
+ }
412
+ return this;
360
413
  }
361
414
 
362
415
  // ---- reenter ----
@@ -365,6 +418,7 @@ export class Machine<
365
418
  * Like `on()`, but forces onEnter/spawn to run even when transitioning to the same state tag.
366
419
  * Use this to restart timers, re-run spawned effects, or reset state-scoped effects.
367
420
  */
421
+ /** Single state */
368
422
  reenter<
369
423
  NS extends VariantsUnion<_SD> & BrandedState,
370
424
  NE extends VariantsUnion<_ED> & BrandedEvent,
@@ -373,8 +427,59 @@ export class Machine<
373
427
  state: TaggedOrConstructor<NS>,
374
428
  event: TaggedOrConstructor<NE>,
375
429
  handler: TransitionHandler<NS, NE, RS, GD, EFD, never>,
430
+ ): Machine<State, Event, R, _SD, _ED, GD, EFD>;
431
+ /** Multiple states */
432
+ reenter<
433
+ NS extends ReadonlyArray<TaggedOrConstructor<VariantsUnion<_SD> & BrandedState>>,
434
+ NE extends VariantsUnion<_ED> & BrandedEvent,
435
+ RS extends VariantsUnion<_SD> & BrandedState,
436
+ >(
437
+ states: NS,
438
+ event: TaggedOrConstructor<NE>,
439
+ handler: TransitionHandler<
440
+ NS[number] extends TaggedOrConstructor<infer S> ? S : never,
441
+ NE,
442
+ RS,
443
+ GD,
444
+ EFD,
445
+ never
446
+ >,
447
+ ): Machine<State, Event, R, _SD, _ED, GD, EFD>;
448
+ /* eslint-disable @typescript-eslint/no-explicit-any */
449
+ reenter(
450
+ stateOrStates: any,
451
+ event: any,
452
+ handler: any,
453
+ ): Machine<State, Event, R, _SD, _ED, GD, EFD> {
454
+ /* eslint-enable @typescript-eslint/no-explicit-any */
455
+ const states = Array.isArray(stateOrStates) ? stateOrStates : [stateOrStates];
456
+ for (const s of states) {
457
+ this.addTransition(s, event, handler, true);
458
+ }
459
+ return this;
460
+ }
461
+
462
+ // ---- onAny ----
463
+
464
+ /**
465
+ * Register a wildcard transition that fires from any state when no specific transition matches.
466
+ * Specific `.on()` transitions always take priority over `.onAny()`.
467
+ */
468
+ onAny<NE extends VariantsUnion<_ED> & BrandedEvent, RS extends VariantsUnion<_SD> & BrandedState>(
469
+ event: TaggedOrConstructor<NE>,
470
+ handler: TransitionHandler<VariantsUnion<_SD> & BrandedState, NE, RS, GD, EFD, never>,
376
471
  ): Machine<State, Event, R, _SD, _ED, GD, EFD> {
377
- return this.addTransition(state, event, handler, true);
472
+ const eventTag = getTag(event);
473
+ const transition: Transition<State, Event, GD, EFD, R> = {
474
+ stateTag: "*",
475
+ eventTag,
476
+ handler: handler as unknown as Transition<State, Event, GD, EFD, R>["handler"],
477
+ reenter: false,
478
+ };
479
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
480
+ (this._transitions as any[]).push(transition);
481
+ invalidateIndex(this);
482
+ return this;
378
483
  }
379
484
 
380
485
  /** @internal */
@@ -415,7 +520,7 @@ export class Machine<
415
520
  *
416
521
  * machine
417
522
  * .spawn(State.Loading, ({ effects, state }) => effects.fetchData({ url: state.url }))
418
- * .provide({
523
+ * .build({
419
524
  * fetchData: ({ url }, { self }) =>
420
525
  * Effect.gen(function* () {
421
526
  * yield* Effect.addFinalizer(() => Effect.log("Leaving Loading"));
@@ -506,7 +611,7 @@ export class Machine<
506
611
  *
507
612
  * machine
508
613
  * .background(({ effects }) => effects.heartbeat())
509
- * .provide({
614
+ * .build({
510
615
  * heartbeat: (_, { self }) =>
511
616
  * Effect.forever(
512
617
  * Effect.sleep("30 seconds").pipe(Effect.andThen(self.send(Event.Ping)))
@@ -534,87 +639,97 @@ export class Machine<
534
639
  return this;
535
640
  }
536
641
 
537
- // ---- provide ----
642
+ // ---- build ----
538
643
 
539
644
  /**
540
- * Provide implementations for guard and effect slots.
541
- * Creates a new machine instance (the original can be reused with different providers).
645
+ * Finalize the machine. Returns a `BuiltMachine` — the only type accepted by `Machine.spawn`.
646
+ *
647
+ * - Machines with slots: pass implementations as the first argument.
648
+ * - Machines without slots: call with no arguments.
542
649
  */
543
- provide<R2>(
544
- handlers: ProvideHandlers<State, Event, GD, EFD, R2>,
545
- ): Machine<State, Event, R | NormalizeR<R2>, _SD, _ED, GD, EFD> {
546
- // Collect all required slot names in a single pass
547
- const requiredSlots = new Set<string>();
548
- if (this._guardsSchema !== undefined) {
549
- for (const name of Object.keys(this._guardsSchema.definitions)) {
550
- requiredSlots.add(name);
650
+ build<R2 = never>(
651
+ ...args: HasSlots<GD, EFD> extends true
652
+ ? [handlers: ProvideHandlers<State, Event, GD, EFD, R2>]
653
+ : [handlers?: ProvideHandlers<State, Event, GD, EFD, R2>]
654
+ ): BuiltMachine<State, Event, R | NormalizeR<R2>> {
655
+ const handlers = args[0];
656
+ if (handlers !== undefined) {
657
+ // Collect all required slot names in a single pass
658
+ const requiredSlots = new Set<string>();
659
+ if (this._guardsSchema !== undefined) {
660
+ for (const name of Object.keys(this._guardsSchema.definitions)) {
661
+ requiredSlots.add(name);
662
+ }
551
663
  }
552
- }
553
- if (this._effectsSchema !== undefined) {
554
- for (const name of Object.keys(this._effectsSchema.definitions)) {
555
- requiredSlots.add(name);
664
+ if (this._effectsSchema !== undefined) {
665
+ for (const name of Object.keys(this._effectsSchema.definitions)) {
666
+ requiredSlots.add(name);
667
+ }
556
668
  }
557
- }
558
669
 
559
- // Single-pass validation: collect all missing and extra handlers
560
- const providedSlots = new Set(Object.keys(handlers));
561
- const missing: string[] = [];
562
- const extra: string[] = [];
670
+ // Single-pass validation: collect all missing and extra handlers
671
+ const providedSlots = new Set(Object.keys(handlers));
672
+ const missing: string[] = [];
673
+ const extra: string[] = [];
563
674
 
564
- for (const name of requiredSlots) {
565
- if (!providedSlots.has(name)) {
566
- missing.push(name);
675
+ for (const name of requiredSlots) {
676
+ if (!providedSlots.has(name)) {
677
+ missing.push(name);
678
+ }
567
679
  }
568
- }
569
- for (const name of providedSlots) {
570
- if (!requiredSlots.has(name)) {
571
- extra.push(name);
680
+ for (const name of providedSlots) {
681
+ if (!requiredSlots.has(name)) {
682
+ extra.push(name);
683
+ }
572
684
  }
573
- }
574
-
575
- // Report all validation errors at once
576
- if (missing.length > 0 || extra.length > 0) {
577
- throw new ProvisionValidationError({ missing, extra });
578
- }
579
-
580
- // Create new machine to preserve original for reuse with different providers
581
- const result = new Machine<State, Event, R | R2, _SD, _ED, GD, EFD>(
582
- this.initial,
583
- this.stateSchema as Schema.Schema<State, unknown, never>,
584
- this.eventSchema as Schema.Schema<Event, unknown, never>,
585
- this._guardsSchema,
586
- this._effectsSchema,
587
- );
588
685
 
589
- // Copy arrays/sets to avoid mutation bleed
590
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
591
- (result as any)._transitions = [...this._transitions];
592
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
593
- (result as any)._finalStates = new Set(this._finalStates);
594
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
595
- (result as any)._spawnEffects = [...this._spawnEffects];
596
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
597
- (result as any)._backgroundEffects = [...this._backgroundEffects];
686
+ // Report all validation errors at once
687
+ if (missing.length > 0 || extra.length > 0) {
688
+ throw new ProvisionValidationError({ missing, extra });
689
+ }
598
690
 
599
- // Register handlers from provided object
600
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
601
- const anyHandlers = handlers as Record<string, any>;
602
- if (this._guardsSchema !== undefined) {
603
- for (const name of Object.keys(this._guardsSchema.definitions)) {
604
- result._guardHandlers.set(name, anyHandlers[name]);
691
+ // Create new machine to preserve original for reuse with different providers
692
+ const result = new Machine<State, Event, R | R2, _SD, _ED, GD, EFD>(
693
+ this.initial,
694
+ this.stateSchema as Schema.Schema<State, unknown, never>,
695
+ this.eventSchema as Schema.Schema<Event, unknown, never>,
696
+ this._guardsSchema,
697
+ this._effectsSchema,
698
+ );
699
+
700
+ // Copy arrays/sets to avoid mutation bleed
701
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
702
+ (result as any)._transitions = [...this._transitions];
703
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
704
+ (result as any)._finalStates = new Set(this._finalStates);
705
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
706
+ (result as any)._spawnEffects = [...this._spawnEffects];
707
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
708
+ (result as any)._backgroundEffects = [...this._backgroundEffects];
709
+
710
+ // Register handlers from provided object
711
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
712
+ const anyHandlers = handlers as Record<string, any>;
713
+ if (this._guardsSchema !== undefined) {
714
+ for (const name of Object.keys(this._guardsSchema.definitions)) {
715
+ result._guardHandlers.set(name, anyHandlers[name]);
716
+ }
605
717
  }
606
- }
607
- if (this._effectsSchema !== undefined) {
608
- for (const name of Object.keys(this._effectsSchema.definitions)) {
609
- result._effectHandlers.set(name, anyHandlers[name]);
718
+ if (this._effectsSchema !== undefined) {
719
+ for (const name of Object.keys(this._effectsSchema.definitions)) {
720
+ result._effectHandlers.set(name, anyHandlers[name]);
721
+ }
610
722
  }
611
- }
612
723
 
613
- return result as unknown as Machine<State, Event, R | NormalizeR<R2>, _SD, _ED, GD, EFD>;
724
+ return new BuiltMachine(result as unknown as Machine<State, Event, R | NormalizeR<R2>>);
725
+ }
726
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
727
+ return new BuiltMachine(this as any);
614
728
  }
615
729
 
616
- // ---- persist ----
730
+ // ---- persist (on Machine, for unbuilt usage in testing) ----
617
731
 
732
+ /** @internal Persist from raw Machine — prefer BuiltMachine.persist() */
618
733
  persist(
619
734
  config: PersistOptions,
620
735
  ): PersistentMachine<State & { readonly _tag: string }, Event & { readonly _tag: string }, R> {
@@ -627,25 +742,6 @@ export class Machine<
627
742
  >;
628
743
  }
629
744
 
630
- /**
631
- * Missing slot handlers (guards + effects).
632
- * @internal Used by actor creation to fail fast.
633
- */
634
- _missingSlots(): string[] {
635
- const missing: string[] = [];
636
- if (this._guardsSchema !== undefined) {
637
- for (const name of Object.keys(this._guardsSchema.definitions)) {
638
- if (!this._guardHandlers.has(name)) missing.push(name);
639
- }
640
- }
641
- if (this._effectsSchema !== undefined) {
642
- for (const name of Object.keys(this._effectsSchema.definitions)) {
643
- if (!this._effectHandlers.has(name)) missing.push(name);
644
- }
645
- }
646
- return missing;
647
- }
648
-
649
745
  // ---- Static factory ----
650
746
 
651
747
  static make<
@@ -681,69 +777,56 @@ import { createActor } from "./actor.js";
681
777
 
682
778
  /**
683
779
  * Spawn an actor directly without ActorSystem ceremony.
780
+ * Accepts only `BuiltMachine` (call `.build()` first).
781
+ *
782
+ * **Single actor, no registry.** Caller manages lifetime via `actor.stop`.
783
+ * If a `Scope` exists in context, cleanup attaches automatically on scope close.
684
784
  *
685
- * Use this for simple single-actor cases. For registry, persistence, or
686
- * multi-actor coordination, use ActorSystemService instead.
785
+ * For registry, lookup by ID, persistence, or multi-actor coordination,
786
+ * use `ActorSystemService` / `system.spawn` instead.
687
787
  *
688
788
  * @example
689
789
  * ```ts
690
- * const program = Effect.gen(function* () {
691
- * const actor = yield* Machine.spawn(machine);
692
- * yield* actor.send(Event.Start);
693
- * yield* Effect.yieldNow();
694
- * return yield* actor.snapshot;
695
- * });
790
+ * // Fire-and-forget caller manages lifetime
791
+ * const actor = yield* Machine.spawn(machine.build());
792
+ * yield* actor.send(Event.Start);
793
+ * yield* actor.awaitFinal;
794
+ * yield* actor.stop;
696
795
  *
697
- * Effect.runPromise(Effect.scoped(program));
796
+ * // Scope-aware — auto-cleans up on scope close
797
+ * yield* Effect.scoped(Effect.gen(function* () {
798
+ * const actor = yield* Machine.spawn(machine.build());
799
+ * yield* actor.send(Event.Start);
800
+ * // actor.stop called automatically when scope closes
801
+ * }));
698
802
  * ```
699
803
  */
700
804
  const spawnImpl = Effect.fn("effect-machine.spawn")(function* <
701
805
  S extends { readonly _tag: string },
702
806
  E extends { readonly _tag: string },
703
807
  R,
704
- GD extends GuardsDef,
705
- EFD extends EffectsDef,
706
- >(
707
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Schema fields need wide acceptance
708
- machine: Machine<S, E, R, any, any, GD, EFD>,
709
- id?: string,
710
- ) {
808
+ >(built: BuiltMachine<S, E, R>, id?: string) {
711
809
  const actorId = id ?? `actor-${Math.random().toString(36).slice(2)}`;
712
- const actor = yield* createActor(actorId, machine);
810
+ const actor = yield* createActor(actorId, built._inner);
713
811
 
714
- // Register cleanup on scope finalization
715
- yield* Effect.addFinalizer(
716
- Effect.fn("effect-machine.spawn.finalizer")(function* () {
717
- yield* actor.stop;
718
- }),
719
- );
812
+ // If a scope exists in context, attach cleanup automatically
813
+ const maybeScope = yield* Effect.serviceOption(Scope.Scope);
814
+ if (Option.isSome(maybeScope)) {
815
+ yield* Scope.addFinalizer(maybeScope.value, actor.stop);
816
+ }
720
817
 
721
818
  return actor;
722
819
  });
723
820
 
724
821
  export const spawn: {
725
- <
726
- S extends { readonly _tag: string },
727
- E extends { readonly _tag: string },
728
- R,
729
- GD extends GuardsDef,
730
- EFD extends EffectsDef,
731
- >(
732
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Schema fields need wide acceptance
733
- machine: Machine<S, E, R, any, any, GD, EFD>,
734
- ): Effect.Effect<ActorRef<S, E>, UnprovidedSlotsError, R | Scope.Scope>;
735
-
736
- <
737
- S extends { readonly _tag: string },
738
- E extends { readonly _tag: string },
739
- R,
740
- GD extends GuardsDef,
741
- EFD extends EffectsDef,
742
- >(
743
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Schema fields need wide acceptance
744
- machine: Machine<S, E, R, any, any, GD, EFD>,
822
+ <S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
823
+ machine: BuiltMachine<S, E, R>,
824
+ ): Effect.Effect<ActorRef<S, E>, never, R>;
825
+
826
+ <S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
827
+ machine: BuiltMachine<S, E, R>,
745
828
  id: string,
746
- ): Effect.Effect<ActorRef<S, E>, UnprovidedSlotsError, R | Scope.Scope>;
829
+ ): Effect.Effect<ActorRef<S, E>, never, R>;
747
830
  } = spawnImpl;
748
831
 
749
832
  // Transition lookup (introspection)
@@ -2,7 +2,7 @@ import { Context, Schema } from "effect";
2
2
  import type { Effect, Option } from "effect";
3
3
 
4
4
  import type { PersistentActorRef } from "./persistent-actor.js";
5
- import type { DuplicateActorError, UnprovidedSlotsError } from "../errors.js";
5
+ import type { DuplicateActorError } from "../errors.js";
6
6
 
7
7
  /**
8
8
  * Metadata for a persisted actor.
@@ -37,7 +37,7 @@ export interface RestoreResult<
37
37
  */
38
38
  export interface RestoreFailure {
39
39
  readonly id: string;
40
- readonly error: PersistenceError | DuplicateActorError | UnprovidedSlotsError;
40
+ readonly error: PersistenceError | DuplicateActorError;
41
41
  }
42
42
 
43
43
  /**
@@ -28,7 +28,6 @@ import {
28
28
  } from "../internal/transition.js";
29
29
  import type { ProcessEventError } from "../internal/transition.js";
30
30
  import type { GuardsDef, EffectsDef } from "../slot.js";
31
- import { UnprovidedSlotsError } from "../errors.js";
32
31
  import { INTERNAL_INIT_EVENT } from "../internal/utils.js";
33
32
  import { emitWithTimestamp } from "../internal/inspection.js";
34
33
 
@@ -252,11 +251,6 @@ export const createPersistentActor = Effect.fn("effect-machine.persistentActor.s
252
251
  EFD
253
252
  >;
254
253
 
255
- const missing = typedMachine._missingSlots();
256
- if (missing.length > 0) {
257
- return yield* new UnprovidedSlotsError({ slots: missing });
258
- }
259
-
260
254
  // Get optional inspector from context
261
255
  const inspector = Option.getOrUndefined(yield* Effect.serviceOption(InspectorTag)) as
262
256
  | Inspector<S, E>
@@ -332,7 +326,7 @@ export const createPersistentActor = Effect.fn("effect-machine.persistentActor.s
332
326
 
333
327
  const snapshotEnabledRef = yield* Ref.make(true);
334
328
  const persistenceQueue = yield* Queue.unbounded<Effect.Effect<void, never>>();
335
- const persistenceFiber = yield* Effect.fork(persistenceWorker(persistenceQueue));
329
+ const persistenceFiber = yield* Effect.forkDaemon(persistenceWorker(persistenceQueue));
336
330
 
337
331
  // Save initial metadata
338
332
  yield* Queue.offer(
@@ -342,7 +336,7 @@ export const createPersistentActor = Effect.fn("effect-machine.persistentActor.s
342
336
 
343
337
  // Snapshot scheduler
344
338
  const snapshotQueue = yield* Queue.unbounded<{ state: S; version: number }>();
345
- const snapshotFiber = yield* Effect.fork(
339
+ const snapshotFiber = yield* Effect.forkDaemon(
346
340
  snapshotWorker(id, persistence, adapter, snapshotQueue, snapshotEnabledRef),
347
341
  );
348
342
 
@@ -353,7 +347,7 @@ export const createPersistentActor = Effect.fn("effect-machine.persistentActor.s
353
347
  const { effects: effectSlots } = typedMachine._slots;
354
348
 
355
349
  for (const bg of typedMachine.backgroundEffects) {
356
- const fiber = yield* Effect.fork(
350
+ const fiber = yield* Effect.forkDaemon(
357
351
  bg
358
352
  .handler({ state: resolvedInitial, event: initEvent, self, effects: effectSlots })
359
353
  .pipe(Effect.provideService(typedMachine.Context, initCtx)),
@@ -408,7 +402,7 @@ export const createPersistentActor = Effect.fn("effect-machine.persistentActor.s
408
402
  }
409
403
 
410
404
  // Start the persistent event loop
411
- const loopFiber = yield* Effect.fork(
405
+ const loopFiber = yield* Effect.forkDaemon(
412
406
  persistentEventLoop(
413
407
  id,
414
408
  persistentMachine,