effect-machine 0.13.0 → 0.15.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.
Files changed (49) hide show
  1. package/README.md +19 -11
  2. package/dist/actor.d.ts +18 -6
  3. package/dist/actor.js +151 -61
  4. package/dist/cluster/entity-machine.d.ts +1 -1
  5. package/dist/cluster/entity-machine.js +2 -1
  6. package/dist/cluster/to-entity.d.ts +1 -1
  7. package/dist/errors.d.ts +9 -2
  8. package/dist/errors.js +8 -2
  9. package/dist/index.d.ts +4 -4
  10. package/dist/index.js +4 -4
  11. package/dist/internal/runtime.d.ts +21 -2
  12. package/dist/internal/runtime.js +64 -56
  13. package/dist/internal/transition.d.ts +9 -9
  14. package/dist/internal/transition.js +3 -5
  15. package/dist/machine.d.ts +129 -135
  16. package/dist/machine.js +97 -112
  17. package/dist/schema.d.ts +17 -1
  18. package/dist/schema.js +10 -0
  19. package/dist/slot.d.ts +112 -86
  20. package/dist/slot.js +92 -59
  21. package/dist/testing.d.ts +16 -16
  22. package/dist/testing.js +3 -3
  23. package/package.json +3 -3
  24. package/v3/dist/actor.d.ts +25 -12
  25. package/v3/dist/actor.js +174 -89
  26. package/v3/dist/cluster/entity-machine.d.ts +1 -1
  27. package/v3/dist/cluster/entity-machine.js +1 -0
  28. package/v3/dist/cluster/to-entity.d.ts +1 -1
  29. package/v3/dist/errors.d.ts +12 -3
  30. package/v3/dist/errors.js +10 -4
  31. package/v3/dist/index.d.ts +6 -6
  32. package/v3/dist/index.js +2 -2
  33. package/v3/dist/inspection.d.ts +3 -22
  34. package/v3/dist/inspection.js +1 -15
  35. package/v3/dist/internal/brands.d.ts +4 -8
  36. package/v3/dist/internal/inspection.js +1 -1
  37. package/v3/dist/internal/runtime.d.ts +27 -8
  38. package/v3/dist/internal/runtime.js +91 -61
  39. package/v3/dist/internal/transition.d.ts +10 -10
  40. package/v3/dist/internal/transition.js +8 -10
  41. package/v3/dist/internal/utils.js +5 -1
  42. package/v3/dist/machine.d.ts +160 -120
  43. package/v3/dist/machine.js +118 -115
  44. package/v3/dist/schema.d.ts +25 -11
  45. package/v3/dist/schema.js +18 -5
  46. package/v3/dist/slot.d.ts +112 -86
  47. package/v3/dist/slot.js +92 -59
  48. package/v3/dist/testing.d.ts +16 -16
  49. package/v3/dist/testing.js +7 -7
@@ -1,9 +1,9 @@
1
1
  import { INTERNAL_INIT_EVENT } from "./utils.js";
2
- import { processEventCore, runSpawnEffects, shouldPostpone } from "./transition.js";
3
2
  import { NoReplyError } from "../errors.js";
3
+ import { processEventCore, runSpawnEffects, shouldPostpone } from "./transition.js";
4
4
  import { ActorExit } from "../supervision.js";
5
5
  import { ActorSystem } from "../actor.js";
6
- import { Cause, Deferred, Effect, Exit, Fiber, Option, Queue, Ref, Schema, Scope, SubscriptionRef } from "effect";
6
+ import { Cause, Deferred, Effect, Exit, Fiber, Queue, Ref, Schema, Scope, SubscriptionRef } from "effect";
7
7
  //#region src/internal/runtime.ts
8
8
  /**
9
9
  * Shared runtime kernel for machine event processing.
@@ -72,7 +72,6 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
72
72
  })
73
73
  };
74
74
  const stateScopeRef = { current: yield* Scope.make() };
75
- const backgroundFibers = [];
76
75
  const initEvent = { _tag: INTERNAL_INIT_EVENT };
77
76
  const ctx = {
78
77
  actorId,
@@ -81,60 +80,72 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
81
80
  self,
82
81
  system
83
82
  };
84
- const { effects: effectSlots } = machine._slots;
85
- for (const bg of machine.backgroundEffects) {
86
- const fiber = yield* bg.handler({
87
- actorId,
88
- state: machine.initial,
89
- event: initEvent,
90
- self,
91
- effects: effectSlots,
92
- system
93
- }).pipe(Effect.provideService(machine.Context, ctx), Effect.forkIn(actorScope));
94
- backgroundFibers.push(fiber);
95
- }
96
- if (lifecycle?.onInitialSpawnEffects !== void 0) yield* lifecycle.onInitialSpawnEffects(machine.initial);
83
+ const slots = machine._slots;
97
84
  const loopFiberRef = { current: void 0 };
98
- const initialSpawnDefectSignal = (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => loopFiberRef.current !== void 0 ? Fiber.interrupt(loopFiberRef.current) : Effect.void)), Effect.asVoid);
99
- yield* runSpawnEffects(machine, machine.initial, initEvent, self, stateScopeRef.current, system, actorId, hooks?.onError, initialSpawnDefectSignal).pipe(Effect.catchCause((cause) => {
100
- return Effect.gen(function* () {
85
+ /** Set the exit deferred exactly once. */
86
+ const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
87
+ const startDeferred = yield* Deferred.make();
88
+ const startedRef = yield* Ref.make(false);
89
+ const start = Effect.gen(function* () {
90
+ if (yield* Ref.getAndSet(startedRef, true)) {
91
+ yield* Deferred.await(startDeferred);
92
+ return;
93
+ }
94
+ const backgroundFibers = [];
95
+ for (const bg of machine.backgroundEffects) {
96
+ const fiber = yield* bg.handler({
97
+ actorId,
98
+ state: machine.initial,
99
+ event: initEvent,
100
+ self,
101
+ slots,
102
+ system
103
+ }).pipe(Effect.provideService(machine.Context, ctx), Effect.forkIn(actorScope));
104
+ backgroundFibers.push(fiber);
105
+ }
106
+ if (lifecycle?.onInitialSpawnEffects !== void 0) yield* lifecycle.onInitialSpawnEffects(machine.initial);
107
+ const initialSpawnDefectSignal = (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => loopFiberRef.current !== void 0 ? Fiber.interrupt(loopFiberRef.current) : Effect.void)), Effect.asVoid);
108
+ yield* runSpawnEffects(machine, machine.initial, initEvent, self, stateScopeRef.current, system, actorId, hooks?.onError, initialSpawnDefectSignal).pipe(Effect.catchCause((cause) => {
109
+ return Effect.gen(function* () {
110
+ yield* Ref.set(stoppedRef, true);
111
+ yield* Scope.close(stateScopeRef.current, Exit.void);
112
+ yield* Scope.close(actorScope, Exit.void);
113
+ yield* Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn"));
114
+ return yield* Effect.failCause(cause);
115
+ });
116
+ }));
117
+ if (machine.finalStates.has(machine.initial._tag)) {
118
+ if (lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(machine.initial);
101
119
  yield* Ref.set(stoppedRef, true);
102
120
  yield* Scope.close(stateScopeRef.current, Exit.void);
103
121
  yield* Scope.close(actorScope, Exit.void);
104
- yield* Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "initial-spawn"));
105
- return yield* Effect.failCause(cause);
106
- });
107
- }));
108
- /** Set the exit deferred exactly once. */
109
- const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
110
- if (machine.finalStates.has(machine.initial._tag)) {
111
- if (lifecycle?.onFinal !== void 0) yield* lifecycle.onFinal(machine.initial);
112
- yield* Ref.set(stoppedRef, true);
113
- yield* Scope.close(stateScopeRef.current, Exit.void);
114
- yield* Scope.close(actorScope, Exit.void);
115
- yield* setExit(ActorExit.Final(machine.initial));
116
- return makeHandle(stateRef, stoppedRef, eventQueue, exitDeferred, actorScope);
117
- }
118
- const augmentedHooks = {
119
- ...hooks,
120
- onSpawnDefect: (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => loopFiberRef.current !== void 0 ? Fiber.interrupt(loopFiberRef.current) : Effect.void)), Effect.asVoid)
121
- };
122
- const loopFiber = yield* Effect.forkDetach(runtimeEventLoop(machine, stateRef, eventQueue, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, augmentedHooks, deferredReplyRef, lifecycle, config.wrapProcess, fork));
123
- loopFiberRef.current = loopFiber;
124
- if (backgroundFibers.length > 0) yield* Effect.raceAll(backgroundFibers.map((fiber) => Fiber.await(fiber).pipe(Effect.flatMap((exit) => {
125
- if (exit._tag === "Failure" && !Cause.hasInterruptsOnly(exit.cause)) return setExit(ActorExit.Defect(exit.cause, "background")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Fiber.interrupt(loopFiber)));
126
- return Effect.never;
127
- })))).pipe(Effect.forkIn(actorScope));
128
- yield* Effect.forkDetach(Effect.gen(function* () {
129
- const loopExit = yield* Fiber.await(loopFiber);
130
- if (loopExit._tag === "Success") yield* Scope.close(actorScope, Exit.void);
131
- else yield* Scope.close(actorScope, loopExit);
132
- }));
122
+ yield* setExit(ActorExit.Final(machine.initial));
123
+ yield* Deferred.succeed(startDeferred, void 0);
124
+ return;
125
+ }
126
+ const augmentedHooks = {
127
+ ...hooks,
128
+ onSpawnDefect: (cause) => Deferred.succeed(exitDeferred, ActorExit.Defect(cause, "spawn")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Effect.suspend(() => loopFiberRef.current !== void 0 ? Fiber.interrupt(loopFiberRef.current) : Effect.void)), Effect.asVoid)
129
+ };
130
+ const loopFiber = yield* Effect.forkDetach(runtimeEventLoop(machine, stateRef, eventQueue, stoppedRef, self, stateScopeRef, actorId, system, exitDeferred, augmentedHooks, deferredReplyRef, lifecycle, config.wrapProcess, fork));
131
+ loopFiberRef.current = loopFiber;
132
+ if (backgroundFibers.length > 0) yield* Effect.raceAll(backgroundFibers.map((fiber) => Fiber.await(fiber).pipe(Effect.flatMap((exit) => {
133
+ if (exit._tag === "Failure" && !Cause.hasInterruptsOnly(exit.cause)) return setExit(ActorExit.Defect(exit.cause, "background")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Fiber.interrupt(loopFiber)));
134
+ return Effect.never;
135
+ })))).pipe(Effect.forkIn(actorScope));
136
+ yield* Effect.forkDetach(Effect.gen(function* () {
137
+ const loopExit = yield* Fiber.await(loopFiber);
138
+ if (loopExit._tag === "Success") yield* Scope.close(actorScope, Exit.void);
139
+ else yield* Scope.close(actorScope, loopExit);
140
+ }));
141
+ yield* Deferred.succeed(startDeferred, void 0);
142
+ }).pipe(Effect.catchCause((cause) => Deferred.failCause(startDeferred, cause).pipe(Effect.andThen(Effect.failCause(cause)))));
133
143
  const stop = Effect.gen(function* () {
134
144
  if (yield* Ref.get(stoppedRef)) return;
135
145
  if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
136
146
  yield* Ref.set(stoppedRef, true);
137
- yield* Fiber.interrupt(loopFiber);
147
+ const loopFiber = loopFiberRef.current;
148
+ if (loopFiber !== void 0) yield* Fiber.interrupt(loopFiber);
138
149
  yield* Scope.close(stateScopeRef.current, Exit.void);
139
150
  yield* Scope.close(actorScope, Exit.void);
140
151
  yield* setExit(ActorExit.Stopped);
@@ -142,7 +153,8 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
142
153
  if (config.skipFinalizer !== true) yield* Effect.addFinalizer(() => stop);
143
154
  return {
144
155
  ...makeHandle(stateRef, stoppedRef, eventQueue, exitDeferred, actorScope),
145
- stop
156
+ stop,
157
+ start
146
158
  };
147
159
  });
148
160
  /**
@@ -184,6 +196,7 @@ const makeHandle = (stateRef, stoppedRef, eventQueue, exitDeferred, actorScope)
184
196
  stateRef,
185
197
  isStopped: Ref.get(stoppedRef),
186
198
  stop: Effect.void,
199
+ start: Effect.void,
187
200
  _queue: eventQueue,
188
201
  _stoppedRef: stoppedRef,
189
202
  exitDeferred,
@@ -278,12 +291,7 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
278
291
  yield* Ref.set(stoppedRef, true);
279
292
  if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
280
293
  settlePostponed(postponed, actorId, forkEffect);
281
- const remaining = [];
282
- let next = yield* Queue.poll(eventQueue);
283
- while (Option.isSome(next)) {
284
- remaining.push(next.value);
285
- next = yield* Queue.poll(eventQueue);
286
- }
294
+ const remaining = yield* Queue.clear(eventQueue);
287
295
  for (const entry of remaining) if (entry._tag === "sendWait") forkEffect(Deferred.succeed(entry.done, void 0));
288
296
  else if (entry._tag === "ask") forkEffect(Deferred.fail(entry.reply, new NoReplyError({
289
297
  actorId,
@@ -1,4 +1,4 @@
1
- import { EffectsDef, GuardsDef, MachineContext } from "../slot.js";
1
+ import { MachineContext, SlotsDef } from "../slot.js";
2
2
  import { Machine, MachineRef, SpawnEffect, Transition } from "../machine.js";
3
3
  import { ActorSystem } from "../actor.js";
4
4
  import { Cause, Effect, Scope } from "effect";
@@ -29,7 +29,7 @@ declare const runTransitionHandler: <S extends {
29
29
  readonly _tag: string;
30
30
  }, E extends {
31
31
  readonly _tag: string;
32
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, transition: Transition<S, E, GD, EFD, R>, state: S, event: E, self: MachineRef<E>, system: ActorSystem, actorId: string) => Effect.Effect<{
32
+ }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, transition: Transition<S, E, SD, R>, state: S, event: E, self: MachineRef<E>, system: ActorSystem, actorId: string) => Effect.Effect<{
33
33
  newState: S;
34
34
  hasReply: boolean;
35
35
  deferReply: boolean;
@@ -50,7 +50,7 @@ declare const executeTransition: <S extends {
50
50
  readonly _tag: string;
51
51
  }, E extends {
52
52
  readonly _tag: string;
53
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, currentState: S, event: E, self: MachineRef<E>, system: ActorSystem, actorId: string) => Effect.Effect<{
53
+ }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, currentState: S, event: E, self: MachineRef<E>, system: ActorSystem, actorId: string) => Effect.Effect<{
54
54
  newState: S;
55
55
  transitioned: boolean;
56
56
  reenter: boolean;
@@ -111,7 +111,7 @@ declare const shouldPostpone: <S extends {
111
111
  readonly _tag: string;
112
112
  }, E extends {
113
113
  readonly _tag: string;
114
- }, R>(machine: Machine<S, E, R, any, any, any, any>, stateTag: string, eventTag: string) => boolean;
114
+ }, R>(machine: Machine<S, E, R, any, any, any>, stateTag: string, eventTag: string) => boolean;
115
115
  /**
116
116
  * Process a single event through the machine.
117
117
  *
@@ -128,7 +128,7 @@ declare const processEventCore: <S extends {
128
128
  readonly _tag: string;
129
129
  }, E extends {
130
130
  readonly _tag: string;
131
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, currentState: S, event: E, self: MachineRef<E>, stateScopeRef: {
131
+ }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, currentState: S, event: E, self: MachineRef<E>, stateScopeRef: {
132
132
  current: Scope.Closeable;
133
133
  }, system: ActorSystem, actorId: string, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
134
134
  newState: S;
@@ -150,7 +150,7 @@ declare const runSpawnEffects: <S extends {
150
150
  readonly _tag: string;
151
151
  }, E extends {
152
152
  readonly _tag: string;
153
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, state: S, event: E, self: MachineRef<E>, stateScope: Scope.Closeable, system: ActorSystem, actorId: string, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined, onSpawnDefect?: ((cause: Cause.Cause<unknown>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never, Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
153
+ }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, state: S, event: E, self: MachineRef<E>, stateScope: Scope.Closeable, system: ActorSystem, actorId: string, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined, onSpawnDefect?: ((cause: Cause.Cause<unknown>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never, Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
154
154
  /**
155
155
  * Resolve which transition should fire for a given state and event.
156
156
  * Uses indexed O(1) lookup. First matching transition wins.
@@ -159,7 +159,7 @@ declare const resolveTransition: <S extends {
159
159
  readonly _tag: string;
160
160
  }, E extends {
161
161
  readonly _tag: string;
162
- }, R>(machine: Machine<S, E, R, any, any, any, any>, currentState: S, event: E) => (typeof machine.transitions)[number] | undefined;
162
+ }, R>(machine: Machine<S, E, R, any, any, any>, currentState: S, event: E) => (typeof machine.transitions)[number] | undefined;
163
163
  /**
164
164
  * Invalidate cached index for a machine (call after mutation).
165
165
  */
@@ -174,7 +174,7 @@ declare const findTransitions: <S extends {
174
174
  readonly _tag: string;
175
175
  }, E extends {
176
176
  readonly _tag: string;
177
- }, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(machine: Machine<S, E, R, any, any, GD, EFD>, stateTag: string, eventTag: string) => ReadonlyArray<Transition<S, E, GD, EFD, R>>;
177
+ }, R, SD extends SlotsDef = Record<string, never>>(machine: Machine<S, E, R, any, any, SD>, stateTag: string, eventTag: string) => ReadonlyArray<Transition<S, E, SD, R>>;
178
178
  /**
179
179
  * Find all spawn effects for a state.
180
180
  * Returns empty array if no matches.
@@ -185,6 +185,6 @@ declare const findSpawnEffects: <S extends {
185
185
  readonly _tag: string;
186
186
  }, E extends {
187
187
  readonly _tag: string;
188
- }, R, GD extends GuardsDef = Record<string, never>, EFD extends EffectsDef = Record<string, never>>(machine: Machine<S, E, R, any, any, GD, EFD>, stateTag: string) => ReadonlyArray<SpawnEffect<S, E, EFD, R>>;
188
+ }, R, SD extends SlotsDef = Record<string, never>>(machine: Machine<S, E, R, any, any, SD>, stateTag: string) => ReadonlyArray<SpawnEffect<S, E, SD, R>>;
189
189
  //#endregion
190
190
  export { ProcessEventError, ProcessEventHooks, ProcessEventResult, TransitionExecutionResult, executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler, shouldPostpone };
@@ -29,12 +29,10 @@ const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(fu
29
29
  self,
30
30
  system
31
31
  };
32
- const { guards, effects } = machine._slots;
33
32
  const handlerCtx = {
34
33
  state,
35
34
  event,
36
- guards,
37
- effects
35
+ slots: machine._slots
38
36
  };
39
37
  const raw = transition.handler(handlerCtx);
40
38
  const resolved = isEffect(raw) ? yield* raw.pipe(Effect.provideService(machine.Context, ctx)) : raw;
@@ -166,7 +164,7 @@ const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (m
166
164
  self,
167
165
  system
168
166
  };
169
- const { effects: effectSlots } = machine._slots;
167
+ const slots = machine._slots;
170
168
  const reportError = onError;
171
169
  const defectSignal = onSpawnDefect;
172
170
  for (const spawnEffect of spawnEffects) {
@@ -175,7 +173,7 @@ const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (m
175
173
  state,
176
174
  event,
177
175
  self,
178
- effects: effectSlots,
176
+ slots,
179
177
  system
180
178
  }).pipe(Effect.provideService(machine.Context, ctx), Effect.catchCause((cause) => {
181
179
  if (Cause.hasInterruptsOnly(cause)) return Effect.interrupt;