effect-machine 0.15.2 → 0.16.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/dist/actor.d.ts CHANGED
@@ -5,7 +5,7 @@ import { ActorExit, Supervision } from "./supervision.js";
5
5
  import { ProcessEventError, ProcessEventHooks, ProcessEventResult, processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
6
6
  import { Lifecycle, Machine } from "./machine.js";
7
7
  import { RuntimeQueuedEvent } from "./internal/runtime.js";
8
- import { Deferred, Effect, Layer, Option, PubSub, Queue, Ref, Scope, ServiceMap, Stream, SubscriptionRef } from "effect";
8
+ import { Context, Deferred, Effect, Layer, Option, PubSub, Queue, Ref, Scope, Stream, SubscriptionRef } from "effect";
9
9
 
10
10
  //#region src/actor.d.ts
11
11
  /** Discriminated mailbox request — alias for RuntimeQueuedEvent */
@@ -199,7 +199,17 @@ interface ActorSystem {
199
199
  /**
200
200
  * ActorSystem service tag
201
201
  */
202
- declare const ActorSystem: ServiceMap.Service<ActorSystem, ActorSystem>;
202
+ declare const ActorSystem: Context.Service<ActorSystem, ActorSystem>;
203
+ /**
204
+ * Explicit scope for actor lifecycle management.
205
+ *
206
+ * When present in context, actors attach cleanup finalizers to this scope.
207
+ * This replaces ambient `Scope.Scope` detection which caused bugs where
208
+ * unrelated scopes would tear down actors unexpectedly.
209
+ *
210
+ * Provide via `Machine.scoped` or `Effect.provideService(ActorScope, scope)`.
211
+ */
212
+ declare const ActorScope: Context.Service<Scope.Scope, Scope.Scope>;
203
213
  /** Listener set for sync subscriptions */
204
214
  type Listeners<S> = Set<(state: S) => void>;
205
215
  /**
@@ -240,4 +250,4 @@ declare const makeSystem: () => Effect.Effect<ActorSystem, never, Scope.Scope>;
240
250
  */
241
251
  declare const Default: Layer.Layer<ActorSystem, never, never>;
242
252
  //#endregion
243
- export { ActorRef, ActorRefSync, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, TransitionInfo, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
253
+ export { ActorRef, ActorRefSync, ActorScope, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, TransitionInfo, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
package/dist/actor.js CHANGED
@@ -4,7 +4,7 @@ import { emitWithTimestamp } from "./internal/inspection.js";
4
4
  import { Inspector } from "./inspection.js";
5
5
  import { materializeMachine } from "./machine.js";
6
6
  import { createRuntime } from "./internal/runtime.js";
7
- import { Cause, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Semaphore, ServiceMap, Stream, SubscriptionRef } from "effect";
7
+ import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Semaphore, Stream, SubscriptionRef } from "effect";
8
8
  //#region src/actor.ts
9
9
  /**
10
10
  * Actor system: spawning, lifecycle, and event processing.
@@ -17,7 +17,17 @@ import { Cause, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, Pu
17
17
  /**
18
18
  * ActorSystem service tag
19
19
  */
20
- const ActorSystem = ServiceMap.Service("@effect/machine/ActorSystem");
20
+ const ActorSystem = Context.Service("@effect/machine/ActorSystem");
21
+ /**
22
+ * Explicit scope for actor lifecycle management.
23
+ *
24
+ * When present in context, actors attach cleanup finalizers to this scope.
25
+ * This replaces ambient `Scope.Scope` detection which caused bugs where
26
+ * unrelated scopes would tear down actors unexpectedly.
27
+ *
28
+ * Provide via `Machine.scoped` or `Effect.provideService(ActorScope, scope)`.
29
+ */
30
+ const ActorScope = Context.Service("@effect/machine/ActorScope");
21
31
  /**
22
32
  * Notify all listeners of state change.
23
33
  */
@@ -40,11 +50,19 @@ const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, lis
40
50
  });
41
51
  const call = Effect.fn("effect-machine.actor.call")(function* (event) {
42
52
  if (yield* Ref.get(stoppedRef)) {
53
+ yield* Effect.logWarning("effect-machine.actor.call.stopped").pipe(Effect.annotateLogs({
54
+ actorId: id,
55
+ eventTag: event._tag
56
+ }));
43
57
  const currentState = yield* SubscriptionRef.get(stateRef);
44
58
  return {
45
59
  newState: currentState,
46
60
  previousState: currentState,
47
61
  transitioned: false,
62
+ hasReply: false,
63
+ deferReply: false,
64
+ reply: void 0,
65
+ postponed: false,
48
66
  lifecycleRan: false,
49
67
  isFinal: machine.finalStates.has(currentState._tag)
50
68
  };
@@ -61,6 +79,10 @@ const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, lis
61
79
  newState: currentState,
62
80
  previousState: currentState,
63
81
  transitioned: false,
82
+ hasReply: false,
83
+ deferReply: false,
84
+ reply: void 0,
85
+ postponed: false,
64
86
  lifecycleRan: false,
65
87
  isFinal: machine.finalStates.has(currentState._tag)
66
88
  })))));
@@ -475,7 +497,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
475
497
  id,
476
498
  actor: actorRef
477
499
  });
478
- const maybeScope = yield* Effect.serviceOption(Scope.Scope);
500
+ const maybeScope = yield* Effect.serviceOption(ActorScope);
479
501
  if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.gen(function* () {
480
502
  if (MutableHashMap.has(actorsMap, id)) {
481
503
  yield* emitSystemEvent({
@@ -558,4 +580,4 @@ const makeSystem = make;
558
580
  */
559
581
  const Default = Layer.effect(ActorSystem, make());
560
582
  //#endregion
561
- export { ActorSystem, Default, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
583
+ export { ActorScope, ActorSystem, Default, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
@@ -1,5 +1,5 @@
1
1
  import { PersistenceError, VersionConflictError } from "../errors.js";
2
- import { Effect, Option, Schedule, ServiceMap } from "effect";
2
+ import { Context, Effect, Option, Schedule } from "effect";
3
3
 
4
4
  //#region src/cluster/persistence.d.ts
5
5
  /** Namespaced key preventing cross-type collisions (e.g. Order/123 vs User/123). */
@@ -44,6 +44,6 @@ interface PersistenceAdapter {
44
44
  readonly loadEvents: (key: PersistenceKey, afterVersion?: number) => Effect.Effect<ReadonlyArray<PersistedEvent<unknown>>, PersistenceError>;
45
45
  }
46
46
  /** Service tag for PersistenceAdapter — resolve from context for shared infra. */
47
- declare const PersistenceAdapter: ServiceMap.Service<PersistenceAdapter, PersistenceAdapter>;
47
+ declare const PersistenceAdapter: Context.Service<PersistenceAdapter, PersistenceAdapter>;
48
48
  //#endregion
49
49
  export { EntityPersistenceConfig, PersistedEvent, PersistenceAdapter, PersistenceKey, Snapshot };
@@ -1,4 +1,4 @@
1
- import { ServiceMap } from "effect";
1
+ import { Context } from "effect";
2
2
  //#region src/cluster/persistence.ts
3
3
  /**
4
4
  * Entity persistence types and adapter interface.
@@ -13,6 +13,6 @@ import { ServiceMap } from "effect";
13
13
  * @module
14
14
  */
15
15
  /** Service tag for PersistenceAdapter — resolve from context for shared infra. */
16
- const PersistenceAdapter = ServiceMap.Service("@effect-machine/cluster/PersistenceAdapter");
16
+ const PersistenceAdapter = Context.Service("@effect-machine/cluster/PersistenceAdapter");
17
17
  //#endregion
18
18
  export { PersistenceAdapter };
package/dist/errors.d.ts CHANGED
@@ -2,67 +2,67 @@ import { Schema } from "effect";
2
2
  import * as _$effect_Cause0 from "effect/Cause";
3
3
 
4
4
  //#region src/errors.d.ts
5
- declare const DuplicateActorError_base: Schema.ErrorClass<DuplicateActorError, Schema.TaggedStruct<"DuplicateActorError", {
5
+ declare const DuplicateActorError_base: Schema.Class<DuplicateActorError, Schema.TaggedStruct<"DuplicateActorError", {
6
6
  readonly actorId: Schema.String;
7
7
  }>, _$effect_Cause0.YieldableError>;
8
8
  /** Attempted to spawn/restore actor with ID already in use */
9
9
  declare class DuplicateActorError extends DuplicateActorError_base {}
10
- declare const MissingSchemaError_base: Schema.ErrorClass<MissingSchemaError, Schema.TaggedStruct<"MissingSchemaError", {
10
+ declare const MissingSchemaError_base: Schema.Class<MissingSchemaError, Schema.TaggedStruct<"MissingSchemaError", {
11
11
  readonly operation: Schema.String;
12
12
  }>, _$effect_Cause0.YieldableError>;
13
13
  /** Operation requires schemas attached to machine */
14
14
  declare class MissingSchemaError extends MissingSchemaError_base {}
15
- declare const InvalidSchemaError_base: Schema.ErrorClass<InvalidSchemaError, Schema.TaggedStruct<"InvalidSchemaError", {
15
+ declare const InvalidSchemaError_base: Schema.Class<InvalidSchemaError, Schema.TaggedStruct<"InvalidSchemaError", {
16
16
  readonly message: Schema.String;
17
17
  }>, _$effect_Cause0.YieldableError>;
18
18
  /** State/Event schema has no variants */
19
19
  declare class InvalidSchemaError extends InvalidSchemaError_base {}
20
- declare const MissingMatchHandlerError_base: Schema.ErrorClass<MissingMatchHandlerError, Schema.TaggedStruct<"MissingMatchHandlerError", {
20
+ declare const MissingMatchHandlerError_base: Schema.Class<MissingMatchHandlerError, Schema.TaggedStruct<"MissingMatchHandlerError", {
21
21
  readonly tag: Schema.String;
22
22
  }>, _$effect_Cause0.YieldableError>;
23
23
  /** $match called with missing handler for tag */
24
24
  declare class MissingMatchHandlerError extends MissingMatchHandlerError_base {}
25
- declare const SlotProvisionError_base: Schema.ErrorClass<SlotProvisionError, Schema.TaggedStruct<"SlotProvisionError", {
25
+ declare const SlotProvisionError_base: Schema.Class<SlotProvisionError, Schema.TaggedStruct<"SlotProvisionError", {
26
26
  readonly slotName: Schema.String;
27
27
  readonly slotType: Schema.Literal<"slot">;
28
28
  }>, _$effect_Cause0.YieldableError>;
29
29
  /** Slot handler not found at runtime (internal error) */
30
30
  declare class SlotProvisionError extends SlotProvisionError_base {}
31
- declare const ProvisionValidationError_base: Schema.ErrorClass<ProvisionValidationError, Schema.TaggedStruct<"ProvisionValidationError", {
31
+ declare const ProvisionValidationError_base: Schema.Class<ProvisionValidationError, Schema.TaggedStruct<"ProvisionValidationError", {
32
32
  readonly missing: Schema.$Array<Schema.String>;
33
33
  readonly extra: Schema.$Array<Schema.String>;
34
34
  }>, _$effect_Cause0.YieldableError>;
35
35
  /** Slot provision validation failed — missing or extra handlers */
36
36
  declare class ProvisionValidationError extends ProvisionValidationError_base {}
37
- declare const AssertionError_base: Schema.ErrorClass<AssertionError, Schema.TaggedStruct<"AssertionError", {
37
+ declare const AssertionError_base: Schema.Class<AssertionError, Schema.TaggedStruct<"AssertionError", {
38
38
  readonly message: Schema.String;
39
39
  }>, _$effect_Cause0.YieldableError>;
40
40
  /** Assertion failed in testing utilities */
41
41
  declare class AssertionError extends AssertionError_base {}
42
- declare const ActorStoppedError_base: Schema.ErrorClass<ActorStoppedError, Schema.TaggedStruct<"ActorStoppedError", {
42
+ declare const ActorStoppedError_base: Schema.Class<ActorStoppedError, Schema.TaggedStruct<"ActorStoppedError", {
43
43
  readonly actorId: Schema.String;
44
44
  }>, _$effect_Cause0.YieldableError>;
45
45
  /** Actor was stopped while a call/ask was pending */
46
46
  declare class ActorStoppedError extends ActorStoppedError_base {}
47
- declare const NoReplyError_base: Schema.ErrorClass<NoReplyError, Schema.TaggedStruct<"NoReplyError", {
47
+ declare const NoReplyError_base: Schema.Class<NoReplyError, Schema.TaggedStruct<"NoReplyError", {
48
48
  readonly actorId: Schema.String;
49
49
  readonly eventTag: Schema.String;
50
50
  }>, _$effect_Cause0.YieldableError>;
51
51
  /** ask() was used but the transition handler did not call reply */
52
52
  declare class NoReplyError extends NoReplyError_base {}
53
- declare const PersistenceError_base: Schema.ErrorClass<PersistenceError, Schema.TaggedStruct<"PersistenceError", {
53
+ declare const PersistenceError_base: Schema.Class<PersistenceError, Schema.TaggedStruct<"PersistenceError", {
54
54
  readonly message: Schema.String;
55
55
  }>, _$effect_Cause0.YieldableError>;
56
56
  /** Persistence adapter operation failed */
57
57
  declare class PersistenceError extends PersistenceError_base {}
58
- declare const SlotCodecError_base: Schema.ErrorClass<SlotCodecError, Schema.TaggedStruct<"SlotCodecError", {
58
+ declare const SlotCodecError_base: Schema.Class<SlotCodecError, Schema.TaggedStruct<"SlotCodecError", {
59
59
  readonly slotName: Schema.String;
60
60
  readonly phase: Schema.Literals<readonly ["input", "output"]>;
61
61
  readonly message: Schema.String;
62
62
  }>, _$effect_Cause0.YieldableError>;
63
63
  /** Slot input/output schema validation failed */
64
64
  declare class SlotCodecError extends SlotCodecError_base {}
65
- declare const VersionConflictError_base: Schema.ErrorClass<VersionConflictError, Schema.TaggedStruct<"VersionConflictError", {
65
+ declare const VersionConflictError_base: Schema.Class<VersionConflictError, Schema.TaggedStruct<"VersionConflictError", {
66
66
  readonly expected: Schema.Number;
67
67
  readonly actual: Schema.Number;
68
68
  }>, _$effect_Cause0.YieldableError>;
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ import { HasSlotKeys, MachineContext, ProvideSlots, Slot, SlotCall, SlotCalls, S
5
5
  import { ActorExit, CellPhase, DefectPhase, Supervision } from "./supervision.js";
6
6
  import { ProcessEventResult } from "./internal/transition.js";
7
7
  import { BackgroundEffect, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, SpawnEffect, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, machine_d_exports } from "./machine.js";
8
- import { ActorRef, ActorRefSync, ActorSystem, Default, SystemEvent, SystemEventListener, TransitionInfo } from "./actor.js";
8
+ import { ActorRef, ActorRefSync, ActorScope, ActorSystem, Default, SystemEvent, SystemEventListener, TransitionInfo } from "./actor.js";
9
9
  import { SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
10
10
  import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
11
- export { ActorExit, type ActorRef, type ActorRefSync, ActorStoppedError, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type CellPhase, type DefectPhase, type DeferReplyResult, DuplicateActorError, type Durability, type DurabilityCommit, type EffectEvent, type ErrorEvent, Event, type EventReceivedEvent, type HandlerContext, type HasSlotKeys, type InspectionEvent, type Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, type Lifecycle, machine_d_exports as Machine, type MachineContext, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, type ProcessEventResult, type ProvideSlots, ProvisionValidationError, type Recovery, type RecoveryContext, type ReplyFields, type ReplyResult, type SimulationResult, Slot, type SlotCall, type SlotCalls, SlotCodecError, type SlotFnDef, type SlotHandler, type SlotInvocation, SlotProvisionError, type SlotRequest, type SlotResult, type SlotsDef, type SlotsSchema, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, Supervision, type SystemEvent, type SystemEventListener, type TaskEvent, type TaskOptions, type TestHarness, type TestHarnessOptions, type TimeoutConfig, type TracingInspectorOptions, type Transition, type TransitionEvent, type TransitionInfo, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
11
+ export { ActorExit, type ActorRef, type ActorRefSync, ActorScope, ActorStoppedError, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type CellPhase, type DefectPhase, type DeferReplyResult, DuplicateActorError, type Durability, type DurabilityCommit, type EffectEvent, type ErrorEvent, Event, type EventReceivedEvent, type HandlerContext, type HasSlotKeys, type InspectionEvent, type Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, type Lifecycle, machine_d_exports as Machine, type MachineContext, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, type ProcessEventResult, type ProvideSlots, ProvisionValidationError, type Recovery, type RecoveryContext, type ReplyFields, type ReplyResult, type SimulationResult, Slot, type SlotCall, type SlotCalls, SlotCodecError, type SlotFnDef, type SlotHandler, type SlotInvocation, SlotProvisionError, type SlotRequest, type SlotResult, type SlotsDef, type SlotsSchema, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, Supervision, type SystemEvent, type SystemEventListener, type TaskEvent, type TaskOptions, type TestHarness, type TestHarnessOptions, type TimeoutConfig, type TracingInspectorOptions, type Transition, type TransitionEvent, type TransitionInfo, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { Inspector, collectingInspector, combineInspectors, consoleInspector, ma
3
3
  import { Slot } from "./slot.js";
4
4
  import { machine_exports } from "./machine.js";
5
5
  import { ActorExit, Supervision } from "./supervision.js";
6
- import { ActorSystem, Default } from "./actor.js";
6
+ import { ActorScope, ActorSystem, Default } from "./actor.js";
7
7
  import { Event, State } from "./schema.js";
8
8
  import { assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
9
- export { ActorExit, ActorStoppedError, Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, Slot, SlotCodecError, SlotProvisionError, State, Supervision, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
9
+ export { ActorExit, ActorScope, ActorStoppedError, Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, Slot, SlotCodecError, SlotProvisionError, State, Supervision, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
@@ -1,4 +1,4 @@
1
- import { Effect, Schema, ServiceMap } from "effect";
1
+ import { Context, Effect, Schema } from "effect";
2
2
 
3
3
  //#region src/inspection.d.ts
4
4
  /**
@@ -101,7 +101,7 @@ interface Inspector<S, E> {
101
101
  * Inspector service tag - optional service for machine introspection
102
102
  * Uses `any` types to allow variance flexibility when providing the service
103
103
  */
104
- declare const Inspector: ServiceMap.Service<Inspector<any, any>, Inspector<any, any>>;
104
+ declare const Inspector: Context.Service<Inspector<any, any>, Inspector<any, any>>;
105
105
  /**
106
106
  * Create an inspector from a callback function.
107
107
  *
@@ -1,10 +1,10 @@
1
- import { Effect, Option, ServiceMap } from "effect";
1
+ import { Context, Effect, Option } from "effect";
2
2
  //#region src/inspection.ts
3
3
  /**
4
4
  * Inspector service tag - optional service for machine introspection
5
5
  * Uses `any` types to allow variance flexibility when providing the service
6
6
  */
7
- const Inspector = ServiceMap.Service("@effect/machine/Inspector");
7
+ const Inspector = Context.Service("@effect/machine/Inspector");
8
8
  /**
9
9
  * Create an inspector from a callback function.
10
10
  *
@@ -40,7 +40,7 @@ import { Cause, Deferred, Effect, Exit, Fiber, Queue, Ref, Schema, Scope, Subscr
40
40
  */
41
41
  const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (machine, system, config) {
42
42
  const { actorId, hooks, lifecycle } = config;
43
- const services = yield* Effect.services();
43
+ const services = yield* Effect.context();
44
44
  const fork = Effect.runForkWith(services);
45
45
  const stateRef = config.cellResources?.stateRef ?? (yield* SubscriptionRef.make(machine.initial));
46
46
  const stoppedRef = config.cellResources?.stoppedRef ?? (yield* Ref.make(false));
package/dist/machine.d.ts CHANGED
@@ -6,11 +6,11 @@ import { MachineContext, ProvideSlots, SlotCalls, SlotsDef, SlotsSchema } from "
6
6
  import { Supervision } from "./supervision.js";
7
7
  import { findTransitions } from "./internal/transition.js";
8
8
  import { ActorRef, ActorSystem } from "./actor.js";
9
- import { Cause, Duration, Effect, Option, Schema, Scope, ServiceMap } from "effect";
9
+ import { Cause, Context, Duration, Effect, Option, Schema, Scope } from "effect";
10
10
 
11
11
  //#region src/machine.d.ts
12
12
  declare namespace machine_d_exports {
13
- export { BackgroundEffect, DeferReplyResult, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, ReplyResult, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, make, materializeMachine, replay, reply, spawn };
13
+ export { BackgroundEffect, DeferReplyResult, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, ReplyResult, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, make, materializeMachine, replay, reply, scoped, spawn };
14
14
  }
15
15
  /**
16
16
  * Self reference for sending events back to the machine
@@ -196,7 +196,7 @@ declare class Machine<State, Event, R = never, _SD extends Record<string, Schema
196
196
  * Context tag for accessing machine state/event/self in slot handlers.
197
197
  * Uses shared module-level tag for all machines.
198
198
  */
199
- readonly Context: ServiceMap.Service<MachineContext<State, Event, MachineRef<Event>>, MachineContext<State, Event, MachineRef<Event>>>;
199
+ readonly Context: Context.Service<MachineContext<State, Event, MachineRef<Event>>, MachineContext<State, Event, MachineRef<Event>>>;
200
200
  get transitions(): ReadonlyArray<Transition<State, Event, SD, R>>;
201
201
  get spawnEffects(): ReadonlyArray<SpawnEffect<State, Event, SD, R>>;
202
202
  get backgroundEffects(): ReadonlyArray<BackgroundEffect<State, Event, SD, R>>;
@@ -362,6 +362,26 @@ declare const spawn: <S extends {
362
362
  supervision?: Supervision.Policy;
363
363
  lifecycle?: Lifecycle<S, E>;
364
364
  }) => Effect.Effect<ActorRef<S, E>, never, R>;
365
+ /**
366
+ * Wrap an effect to provide an `ActorScope` from the current `Scope`.
367
+ *
368
+ * Actors spawned inside will attach cleanup finalizers to this scope,
369
+ * so they are automatically stopped when the scope closes.
370
+ *
371
+ * @example
372
+ * ```ts
373
+ * yield* Effect.scoped(
374
+ * Machine.scoped(
375
+ * Effect.gen(function* () {
376
+ * const actor = yield* Machine.spawn(machine);
377
+ * yield* actor.start;
378
+ * // actor auto-stopped when scope closes
379
+ * }),
380
+ * ),
381
+ * );
382
+ * ```
383
+ */
384
+ declare const scoped: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R | Scope.Scope>;
365
385
  declare const replay: {
366
386
  <S extends {
367
387
  readonly _tag: string;
@@ -375,4 +395,4 @@ declare const replay: {
375
395
  declare const reply: <State, Reply>(state: State, reply: Reply) => ReplyResult<State, Reply>;
376
396
  declare const deferReply: <State>(state: State) => DeferReplyResult<State>;
377
397
  //#endregion
378
- export { BackgroundEffect, type DeferReplyResult, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, type ReplyResult, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, machine_d_exports, make, materializeMachine, replay, reply, spawn };
398
+ export { BackgroundEffect, type DeferReplyResult, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, type ReplyResult, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, machine_d_exports, make, materializeMachine, replay, reply, scoped, spawn };
package/dist/machine.js CHANGED
@@ -5,7 +5,7 @@ import { findTransitions, invalidateIndex, resolveTransition, runTransitionHandl
5
5
  import { emitWithTimestamp } from "./internal/inspection.js";
6
6
  import { Inspector } from "./inspection.js";
7
7
  import { MachineContextTag } from "./slot.js";
8
- import { createActor } from "./actor.js";
8
+ import { ActorScope, createActor } from "./actor.js";
9
9
  import { Cause, Effect, Exit, Option, Random, Schema, Scope } from "effect";
10
10
  //#region src/machine.ts
11
11
  var machine_exports = /* @__PURE__ */ __exportAll({
@@ -16,6 +16,7 @@ var machine_exports = /* @__PURE__ */ __exportAll({
16
16
  materializeMachine: () => materializeMachine,
17
17
  replay: () => replay,
18
18
  reply: () => reply,
19
+ scoped: () => scoped,
19
20
  spawn: () => spawn
20
21
  });
21
22
  const emitTaskInspection = (input) => Effect.flatMap(Effect.serviceOption(Inspector), (inspector) => Option.isNone(inspector) ? Effect.void : emitWithTimestamp(inspector.value, (timestamp) => ({
@@ -75,24 +76,6 @@ const materializeMachine = (machine, handlers) => {
75
76
  * - `SD`: Slot definitions
76
77
  */
77
78
  var Machine = class Machine {
78
- initial;
79
- /** @internal */ _transitions;
80
- /** @internal */ _spawnEffects;
81
- /** @internal */ _backgroundEffects;
82
- /** @internal */ _finalStates;
83
- /** @internal */ _postponeRules;
84
- /** @internal */ _slotsSchema;
85
- /** @internal */ _slotHandlers;
86
- /** @internal */ _slots;
87
- /** @internal */ _slotValidation;
88
- stateSchema;
89
- eventSchema;
90
- /** @internal */ _replySchemas;
91
- /**
92
- * Context tag for accessing machine state/event/self in slot handlers.
93
- * Uses shared module-level tag for all machines.
94
- */
95
- Context = MachineContextTag;
96
79
  get transitions() {
97
80
  return this._transitions;
98
81
  }
@@ -116,6 +99,7 @@ var Machine = class Machine {
116
99
  }
117
100
  /** @internal */
118
101
  constructor(initial, stateSchema, eventSchema, slotsSchema, slotValidation = true) {
102
+ this.Context = MachineContextTag;
119
103
  this.initial = initial;
120
104
  this._transitions = [];
121
105
  this._spawnEffects = [];
@@ -411,10 +395,30 @@ const spawn = Effect.fn("effect-machine.spawn")(function* (machine, idOrOptions)
411
395
  supervision: opts?.supervision,
412
396
  lifecycle: opts?.lifecycle
413
397
  });
414
- const maybeScope = yield* Effect.serviceOption(Scope.Scope);
398
+ const maybeScope = yield* Effect.serviceOption(ActorScope);
415
399
  if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, actor.stop);
416
400
  return actor;
417
401
  });
402
+ /**
403
+ * Wrap an effect to provide an `ActorScope` from the current `Scope`.
404
+ *
405
+ * Actors spawned inside will attach cleanup finalizers to this scope,
406
+ * so they are automatically stopped when the scope closes.
407
+ *
408
+ * @example
409
+ * ```ts
410
+ * yield* Effect.scoped(
411
+ * Machine.scoped(
412
+ * Effect.gen(function* () {
413
+ * const actor = yield* Machine.spawn(machine);
414
+ * yield* actor.start;
415
+ * // actor auto-stopped when scope closes
416
+ * }),
417
+ * ),
418
+ * );
419
+ * ```
420
+ */
421
+ const scoped = (effect) => Effect.flatMap(Effect.service(Scope.Scope), (scope) => Effect.provideService(effect, ActorScope, scope));
418
422
  const replay = Effect.fn("effect-machine.replay")(function* (input, events, options) {
419
423
  const machine = materializeMachine(input, options?.slots);
420
424
  let state = options?.from ?? machine.initial;
@@ -462,4 +466,4 @@ const replay = Effect.fn("effect-machine.replay")(function* (input, events, opti
462
466
  const reply = makeReply;
463
467
  const deferReply = makeDeferReply;
464
468
  //#endregion
465
- export { Machine, deferReply, findTransitions, machine_exports, make, materializeMachine, replay, reply, spawn };
469
+ export { Machine, deferReply, findTransitions, machine_exports, make, materializeMachine, replay, reply, scoped, spawn };
package/dist/slot.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ActorSystem } from "./actor.js";
2
- import { Effect, Schema, ServiceMap } from "effect";
2
+ import { Context, Effect, Schema } from "effect";
3
3
 
4
4
  //#region src/slot.d.ts
5
5
  /** Schema fields definition (like Schema.Struct.Fields) */
@@ -132,7 +132,7 @@ interface MachineContext<State, Event, Self> {
132
132
  * Single module-level tag instead of per-machine allocation.
133
133
  * @internal
134
134
  */
135
- declare const MachineContextTag: ServiceMap.Service<MachineContext<any, any, any>, MachineContext<any, any, any>>;
135
+ declare const MachineContextTag: Context.Service<MachineContext<any, any, any>, MachineContext<any, any, any>>;
136
136
  /**
137
137
  * Define a set of slots with parameter and return schemas.
138
138
  *
package/dist/slot.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Schema, ServiceMap } from "effect";
1
+ import { Context, Schema } from "effect";
2
2
  //#region src/slot.ts
3
3
  /**
4
4
  * Slot module — unified, schema-based parameterized slots.
@@ -62,7 +62,7 @@ const fn = (fields, returnSchema) => {
62
62
  * Single module-level tag instead of per-machine allocation.
63
63
  * @internal
64
64
  */
65
- const MachineContextTag = ServiceMap.Service("@effect-machine/Context");
65
+ const MachineContextTag = Context.Service("@effect-machine/Context");
66
66
  /**
67
67
  * Define a set of slots with parameter and return schemas.
68
68
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-machine",
3
- "version": "0.15.2",
3
+ "version": "0.16.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cevr/effect-machine.git"
@@ -40,40 +40,44 @@
40
40
  "access": "public"
41
41
  },
42
42
  "scripts": {
43
- "typecheck": "tsc --noEmit",
43
+ "typecheck": "tsgo --noEmit",
44
44
  "typecheck:v3": "tsc --noEmit -p v3/tsconfig.json",
45
- "lint": "oxlint",
45
+ "lint": "concurrently -n ox,effect -c yellow,blue \"oxlint\" \"bun run lint:effect\"",
46
+ "lint:ox": "oxlint",
47
+ "lint:effect": "effect-language-service diagnostics --project tsconfig.json --lspconfig \"$(cat .effect-lsp.json)\"",
46
48
  "lint:fix": "oxlint --fix",
47
49
  "fmt": "oxfmt",
48
50
  "fmt:check": "oxfmt --check",
49
- "test": "bun test",
50
- "test:v3": "bun test --tsconfig-override=v3/tsconfig.json v3/test/",
51
+ "test": "bun test --tsconfig-override=tsconfig.json",
52
+ "test:v3": "cd v3 && bun test --tsconfig-override=tsconfig.json ./test/",
51
53
  "test:watch": "bun test --watch",
52
- "gate": "concurrently -n type,lint,fmt,test,build -c blue,yellow,magenta,green,cyan \"bun run typecheck\" \"bun run lint:fix\" \"bun run fmt\" \"bun run test\" \"bun run build\"",
53
- "prepare": "lefthook install || true && effect-language-service patch",
54
+ "test:all": "bun run test && bun run test:v3",
55
+ "gate": "concurrently -n type,lint,fmt,test,build -c blue,yellow,magenta,green,cyan \"bun run typecheck\" \"bun run lint:fix\" \"bun run fmt\" \"bun run test:all\" \"bun run build\"",
56
+ "prepare": "lefthook install",
54
57
  "version": "changeset version",
55
- "build": "tsdown && tsdown --config v3/tsdown.config.ts",
58
+ "build": "concurrently -n v4,v3 -c cyan,magenta \"tsdown\" \"tsdown --config v3/tsdown.config.ts\"",
56
59
  "release": "bun run build && changeset publish"
57
60
  },
58
61
  "dependencies": {
59
- "effect": "4.0.0-beta.43"
62
+ "effect": "4.0.0-beta.47"
60
63
  },
61
64
  "devDependencies": {
62
65
  "@changesets/changelog-github": "^0.6.0",
63
66
  "@changesets/cli": "^2.30.0",
64
- "@effect/language-service": "^0.84.2",
65
- "@types/bun": "1.3.11",
67
+ "@effect/language-service": "^0.85.1",
68
+ "@types/bun": "1.3.12",
69
+ "@typescript/native-preview": "^7.0.0-dev.20260412.1",
66
70
  "concurrently": "^9.2.1",
67
71
  "effect-bun-test": "0.3.0",
68
72
  "effect-v3": "npm:effect@^3.21.0",
69
- "lefthook": "^2.1.4",
70
- "oxfmt": "^0.42.0",
71
- "oxlint": "^1.57.0",
73
+ "lefthook": "^2.1.5",
74
+ "oxfmt": "^0.44.0",
75
+ "oxlint": "^1.59.0",
72
76
  "tsdown": "^0.21.7",
73
- "typescript": "^5.9.3"
77
+ "typescript": "^6.0.2"
74
78
  },
75
79
  "peerDependencies": {
76
- "effect": ">=3.19.16"
80
+ "effect": ">=4.0.0-beta.47"
77
81
  },
78
82
  "peerDependenciesMeta": {
79
83
  "effect": {
@@ -81,6 +85,6 @@
81
85
  }
82
86
  },
83
87
  "overrides": {
84
- "effect": "4.0.0-beta.43"
88
+ "effect": "4.0.0-beta.47"
85
89
  }
86
90
  }
@@ -197,6 +197,16 @@ interface ActorSystem {
197
197
  * ActorSystem service tag
198
198
  */
199
199
  declare const ActorSystem: Context.Tag<ActorSystem, ActorSystem>;
200
+ /**
201
+ * Explicit scope for actor lifecycle management.
202
+ *
203
+ * When present in context, actors attach cleanup finalizers to this scope.
204
+ * This replaces ambient `Scope.Scope` detection which caused bugs where
205
+ * unrelated scopes would tear down actors unexpectedly.
206
+ *
207
+ * Provide via `Machine.scoped` or `Effect.provideService(ActorScope, scope)`.
208
+ */
209
+ declare const ActorScope: Context.Tag<Scope.Scope, Scope.Scope>;
200
210
  /** Listener set for sync subscriptions */
201
211
  type Listeners<S> = Set<(state: S) => void>;
202
212
  /**
@@ -237,4 +247,4 @@ declare const makeSystem: () => Effect.Effect<ActorSystem, never, Scope.Scope>;
237
247
  */
238
248
  declare const Default: Layer.Layer<ActorSystem, never, never>;
239
249
  //#endregion
240
- export { ActorRef, ActorRefSync, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, TransitionInfo, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
250
+ export { ActorRef, ActorRefSync, ActorScope, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, TransitionInfo, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
package/v3/dist/actor.js CHANGED
@@ -19,6 +19,16 @@ import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, O
19
19
  */
20
20
  const ActorSystem = Context.GenericTag("@effect/machine/ActorSystem");
21
21
  /**
22
+ * Explicit scope for actor lifecycle management.
23
+ *
24
+ * When present in context, actors attach cleanup finalizers to this scope.
25
+ * This replaces ambient `Scope.Scope` detection which caused bugs where
26
+ * unrelated scopes would tear down actors unexpectedly.
27
+ *
28
+ * Provide via `Machine.scoped` or `Effect.provideService(ActorScope, scope)`.
29
+ */
30
+ const ActorScope = Context.GenericTag("@effect/machine/ActorScope");
31
+ /**
22
32
  * Notify all listeners of state change.
23
33
  */
24
34
  const notifyListeners = (listeners, state) => {
@@ -40,11 +50,19 @@ const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, lis
40
50
  });
41
51
  const call = Effect.fn("effect-machine.actor.call")(function* (event) {
42
52
  if (yield* Ref.get(stoppedRef)) {
53
+ yield* Effect.logWarning("effect-machine.actor.call.stopped").pipe(Effect.annotateLogs({
54
+ actorId: id,
55
+ eventTag: event._tag
56
+ }));
43
57
  const currentState = yield* SubscriptionRef.get(stateRef);
44
58
  return {
45
59
  newState: currentState,
46
60
  previousState: currentState,
47
61
  transitioned: false,
62
+ hasReply: false,
63
+ deferReply: false,
64
+ reply: void 0,
65
+ postponed: false,
48
66
  lifecycleRan: false,
49
67
  isFinal: machine.finalStates.has(currentState._tag)
50
68
  };
@@ -61,6 +79,10 @@ const buildActorRefCore = (id, machine, stateRef, eventQueueRef, stoppedRef, lis
61
79
  newState: currentState,
62
80
  previousState: currentState,
63
81
  transitioned: false,
82
+ hasReply: false,
83
+ deferReply: false,
84
+ reply: void 0,
85
+ postponed: false,
64
86
  lifecycleRan: false,
65
87
  isFinal: machine.finalStates.has(currentState._tag)
66
88
  })))));
@@ -469,7 +491,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
469
491
  id,
470
492
  actor: actorRef
471
493
  });
472
- const maybeScope = yield* Effect.serviceOption(Scope.Scope);
494
+ const maybeScope = yield* Effect.serviceOption(ActorScope);
473
495
  if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.gen(function* () {
474
496
  if (MutableHashMap.has(actorsMap, id)) {
475
497
  yield* emitSystemEvent({
@@ -552,4 +574,4 @@ const makeSystem = make;
552
574
  */
553
575
  const Default = Layer.scoped(ActorSystem, make());
554
576
  //#endregion
555
- export { ActorSystem, Default, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
577
+ export { ActorScope, ActorSystem, Default, buildActorRefCore, createActor, makeSystem, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };
@@ -5,7 +5,7 @@ import { HasSlotKeys, MachineContext, ProvideSlots, Slot, SlotCall, SlotCalls, S
5
5
  import { ActorExit, CellPhase, DefectPhase, Supervision } from "./supervision.js";
6
6
  import { ProcessEventResult } from "./internal/transition.js";
7
7
  import { BackgroundEffect, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, SpawnEffect, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, machine_d_exports } from "./machine.js";
8
- import { ActorRef, ActorRefSync, ActorSystem, Default, SystemEvent, SystemEventListener, TransitionInfo } from "./actor.js";
8
+ import { ActorRef, ActorRefSync, ActorScope, ActorSystem, Default, SystemEvent, SystemEventListener, TransitionInfo } from "./actor.js";
9
9
  import { SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
10
10
  import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
11
- export { ActorExit, type ActorRef, type ActorRefSync, ActorStoppedError, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type CellPhase, type DefectPhase, type DeferReplyResult, DuplicateActorError, type Durability, type DurabilityCommit, type EffectEvent, type ErrorEvent, Event, type EventReceivedEvent, type HandlerContext, type HasSlotKeys, type InspectionEvent, type Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, type Lifecycle, machine_d_exports as Machine, type MachineContext, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, type ProcessEventResult, type ProvideSlots, ProvisionValidationError, type Recovery, type RecoveryContext, type ReplyFields, type ReplyResult, type SimulationResult, Slot, type SlotCall, type SlotCalls, SlotCodecError, type SlotFnDef, type SlotHandler, type SlotInvocation, SlotProvisionError, type SlotRequest, type SlotResult, type SlotsDef, type SlotsSchema, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, Supervision, type SystemEvent, type SystemEventListener, type TaskEvent, type TaskOptions, type TestHarness, type TestHarnessOptions, type TimeoutConfig, type TracingInspectorOptions, type Transition, type TransitionEvent, type TransitionInfo, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
11
+ export { ActorExit, type ActorRef, type ActorRefSync, ActorScope, ActorStoppedError, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type CellPhase, type DefectPhase, type DeferReplyResult, DuplicateActorError, type Durability, type DurabilityCommit, type EffectEvent, type ErrorEvent, Event, type EventReceivedEvent, type HandlerContext, type HasSlotKeys, type InspectionEvent, type Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, type Lifecycle, machine_d_exports as Machine, type MachineContext, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, type ProcessEventResult, type ProvideSlots, ProvisionValidationError, type Recovery, type RecoveryContext, type ReplyFields, type ReplyResult, type SimulationResult, Slot, type SlotCall, type SlotCalls, SlotCodecError, type SlotFnDef, type SlotHandler, type SlotInvocation, SlotProvisionError, type SlotRequest, type SlotResult, type SlotsDef, type SlotsSchema, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, Supervision, type SystemEvent, type SystemEventListener, type TaskEvent, type TaskOptions, type TestHarness, type TestHarnessOptions, type TimeoutConfig, type TracingInspectorOptions, type Transition, type TransitionEvent, type TransitionInfo, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
package/v3/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { Inspector, collectingInspector, combineInspectors, consoleInspector, ma
3
3
  import { Slot } from "./slot.js";
4
4
  import { machine_exports } from "./machine.js";
5
5
  import { ActorExit, Supervision } from "./supervision.js";
6
- import { ActorSystem, Default } from "./actor.js";
6
+ import { ActorScope, ActorSystem, Default } from "./actor.js";
7
7
  import { Event, State } from "./schema.js";
8
8
  import { assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
9
- export { ActorExit, ActorStoppedError, Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, Slot, SlotCodecError, SlotProvisionError, State, Supervision, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
9
+ export { ActorExit, ActorScope, ActorStoppedError, Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, Slot, SlotCodecError, SlotProvisionError, State, Supervision, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
@@ -10,7 +10,7 @@ import { Cause, Context, Duration, Effect, Option, Schema, Scope } from "effect"
10
10
 
11
11
  //#region src/machine.d.ts
12
12
  declare namespace machine_d_exports {
13
- export { BackgroundEffect, DeferReplyResult, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, ReplyResult, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, make, materializeMachine, replay, reply, spawn };
13
+ export { BackgroundEffect, DeferReplyResult, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, ReplyResult, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, make, materializeMachine, replay, reply, scoped, spawn };
14
14
  }
15
15
  /**
16
16
  * Self reference for sending events back to the machine
@@ -362,6 +362,26 @@ declare const spawn: <S extends {
362
362
  supervision?: Supervision.Policy;
363
363
  lifecycle?: Lifecycle<S, E>;
364
364
  }) => Effect.Effect<ActorRef<S, E>, never, R>;
365
+ /**
366
+ * Wrap an effect to provide an `ActorScope` from the current `Scope`.
367
+ *
368
+ * Actors spawned inside will attach cleanup finalizers to this scope,
369
+ * so they are automatically stopped when the scope closes.
370
+ *
371
+ * @example
372
+ * ```ts
373
+ * yield* Effect.scoped(
374
+ * Machine.scoped(
375
+ * Effect.gen(function* () {
376
+ * const actor = yield* Machine.spawn(machine);
377
+ * yield* actor.start;
378
+ * // actor auto-stopped when scope closes
379
+ * }),
380
+ * ),
381
+ * );
382
+ * ```
383
+ */
384
+ declare const scoped: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R | Scope.Scope>;
365
385
  declare const replay: {
366
386
  <S extends {
367
387
  readonly _tag: string;
@@ -375,4 +395,4 @@ declare const replay: {
375
395
  declare const reply: <State, Reply>(state: State, reply: Reply) => ReplyResult<State, Reply>;
376
396
  declare const deferReply: <State>(state: State) => DeferReplyResult<State>;
377
397
  //#endregion
378
- export { BackgroundEffect, type DeferReplyResult, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, type ReplyResult, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, machine_d_exports, make, materializeMachine, replay, reply, spawn };
398
+ export { BackgroundEffect, type DeferReplyResult, Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, type ReplyResult, SpawnEffect, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, TransitionHandler, deferReply, findTransitions, machine_d_exports, make, materializeMachine, replay, reply, scoped, spawn };
@@ -5,7 +5,7 @@ import { findTransitions, invalidateIndex, resolveTransition, runTransitionHandl
5
5
  import { emitWithTimestamp } from "./internal/inspection.js";
6
6
  import { Inspector } from "./inspection.js";
7
7
  import { MachineContextTag } from "./slot.js";
8
- import { createActor } from "./actor.js";
8
+ import { ActorScope, createActor } from "./actor.js";
9
9
  import { Cause, Effect, Exit, Option, Random, Schema, Scope } from "effect";
10
10
  //#region src/machine.ts
11
11
  var machine_exports = /* @__PURE__ */ __exportAll({
@@ -16,6 +16,7 @@ var machine_exports = /* @__PURE__ */ __exportAll({
16
16
  materializeMachine: () => materializeMachine,
17
17
  replay: () => replay,
18
18
  reply: () => reply,
19
+ scoped: () => scoped,
19
20
  spawn: () => spawn
20
21
  });
21
22
  const emitTaskInspection = (input) => Effect.flatMap(Effect.serviceOption(Inspector), (inspector) => Option.isNone(inspector) ? Effect.void : emitWithTimestamp(inspector.value, (timestamp) => ({
@@ -411,10 +412,30 @@ const spawn = Effect.fn("effect-machine.spawn")(function* (machine, idOrOptions)
411
412
  supervision: opts?.supervision,
412
413
  lifecycle: opts?.lifecycle
413
414
  });
414
- const maybeScope = yield* Effect.serviceOption(Scope.Scope);
415
+ const maybeScope = yield* Effect.serviceOption(ActorScope);
415
416
  if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, actor.stop);
416
417
  return actor;
417
418
  });
419
+ /**
420
+ * Wrap an effect to provide an `ActorScope` from the current `Scope`.
421
+ *
422
+ * Actors spawned inside will attach cleanup finalizers to this scope,
423
+ * so they are automatically stopped when the scope closes.
424
+ *
425
+ * @example
426
+ * ```ts
427
+ * yield* Effect.scoped(
428
+ * Machine.scoped(
429
+ * Effect.gen(function* () {
430
+ * const actor = yield* Machine.spawn(machine);
431
+ * yield* actor.start;
432
+ * // actor auto-stopped when scope closes
433
+ * }),
434
+ * ),
435
+ * );
436
+ * ```
437
+ */
438
+ const scoped = (effect) => Effect.flatMap(Scope.Scope, (scope) => Effect.provideService(effect, ActorScope, scope));
418
439
  const replay = Effect.fn("effect-machine.replay")(function* (input, events, options) {
419
440
  const machine = materializeMachine(input, options?.slots);
420
441
  let state = options?.from ?? machine.initial;
@@ -462,4 +483,4 @@ const replay = Effect.fn("effect-machine.replay")(function* (input, events, opti
462
483
  const reply = makeReply;
463
484
  const deferReply = makeDeferReply;
464
485
  //#endregion
465
- export { Machine, deferReply, findTransitions, machine_exports, make, materializeMachine, replay, reply, spawn };
486
+ export { Machine, deferReply, findTransitions, machine_exports, make, materializeMachine, replay, reply, scoped, spawn };