From 9a3510f23683a884907b5f9b1b20cd161483f059 Mon Sep 17 00:00:00 2001 From: Cristian Date: Wed, 2 Sep 2026 10:47:08 +0000 Subject: [PATCH] fix(actor-system): release spawn gate before start --- .changeset/release-nested-spawn-gate.md | 5 +++++ src/actor.ts | 17 ++++++++--------- test/actor.test.ts | 24 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 .changeset/release-nested-spawn-gate.md diff --git a/.changeset/release-nested-spawn-gate.md b/.changeset/release-nested-spawn-gate.md new file mode 100644 index 0000000..a7d7543 --- /dev/null +++ b/.changeset/release-nested-spawn-gate.md @@ -0,0 +1,5 @@ +--- +"effect-machine": patch +--- + +Allow nested actors to start before their parent spawn returns. diff --git a/src/actor.ts b/src/actor.ts index 2d03a53..c64b246 100644 --- a/src/actor.ts +++ b/src/actor.ts @@ -1262,10 +1262,6 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () { actorRef = actor as unknown as ActorRef; // Register before start — actor is in the map before lifecycle hooks fire yield* registerActor(id, actor); - // Auto-start: system.spawn returns a running actor - yield* actor.start.pipe( - Effect.catchCause((cause) => actor.stop.pipe(Effect.andThen(Effect.failCause(cause)))), - ); return actor; }); @@ -1282,11 +1278,14 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () { options?: SystemSpawnOptions, ): Effect.Effect, DuplicateActorError, R> => { const id = actorSystemId(idOrKey); - return withSpawnGate(spawnRegular(id, machine, options)) as Effect.Effect< - ActorRef, - DuplicateActorError, - R - >; + return withSpawnGate(spawnRegular(id, machine, options)).pipe( + Effect.flatMap((actor) => + actor.start.pipe( + Effect.catchCause((cause) => actor.stop.pipe(Effect.andThen(Effect.failCause(cause)))), + Effect.as(actor), + ), + ), + ) as Effect.Effect, DuplicateActorError, R>; }; function get( diff --git a/test/actor.test.ts b/test/actor.test.ts index e1ace1e..03118b8 100644 --- a/test/actor.test.ts +++ b/test/actor.test.ts @@ -97,6 +97,30 @@ describe("ActorSystem", () => { }).pipe(Effect.provide(ActorSystemDefault)), ); + it.scopedLive("registers a nested child before parent spawn returns", () => + Effect.gen(function* () { + const ChildState = State({ Active: {} }); + const ChildEvent = Event({ Stop: {} }); + const childMachine = Machine.make({ + state: ChildState, + event: ChildEvent, + initial: ChildState.Active, + }); + const parentMachine = Machine.make({ + state: TestState, + event: TestEvent, + initial: TestState.Idle, + }).spawn(TestState.Idle, ({ self }) => + self.spawn("nested-child", childMachine).pipe(Effect.asVoid, Effect.orDie), + ); + const system = yield* ActorSystemService; + + yield* system.spawn("nested-parent", parentMachine); + + expect((yield* system.get("nested-child"))._tag).toBe("Some"); + }).pipe(Effect.provide(ActorSystemDefault)), + ); + it.scopedLive("stops actors properly", () => Effect.gen(function* () { const machine = Machine.make({