effect-machine 0.13.0 → 0.14.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 (48) hide show
  1. package/README.md +14 -9
  2. package/dist/actor.d.ts +9 -6
  3. package/dist/actor.js +97 -43
  4. package/dist/cluster/entity-machine.d.ts +1 -1
  5. package/dist/cluster/entity-machine.js +1 -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 +2 -2
  12. package/dist/internal/runtime.js +5 -10
  13. package/dist/internal/transition.d.ts +9 -9
  14. package/dist/internal/transition.js +3 -5
  15. package/dist/machine.d.ts +122 -135
  16. package/dist/machine.js +97 -112
  17. package/dist/schema.d.ts +14 -0
  18. package/dist/schema.js +9 -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 +19 -12
  25. package/v3/dist/actor.js +130 -75
  26. package/v3/dist/cluster/entity-machine.d.ts +1 -1
  27. package/v3/dist/cluster/to-entity.d.ts +1 -1
  28. package/v3/dist/errors.d.ts +12 -3
  29. package/v3/dist/errors.js +10 -4
  30. package/v3/dist/index.d.ts +6 -6
  31. package/v3/dist/index.js +2 -2
  32. package/v3/dist/inspection.d.ts +3 -22
  33. package/v3/dist/inspection.js +1 -15
  34. package/v3/dist/internal/brands.d.ts +4 -8
  35. package/v3/dist/internal/inspection.js +1 -1
  36. package/v3/dist/internal/runtime.d.ts +8 -8
  37. package/v3/dist/internal/runtime.js +45 -28
  38. package/v3/dist/internal/transition.d.ts +10 -10
  39. package/v3/dist/internal/transition.js +8 -10
  40. package/v3/dist/internal/utils.js +5 -1
  41. package/v3/dist/machine.d.ts +153 -120
  42. package/v3/dist/machine.js +118 -115
  43. package/v3/dist/schema.d.ts +25 -11
  44. package/v3/dist/schema.js +18 -5
  45. package/v3/dist/slot.d.ts +112 -86
  46. package/v3/dist/slot.js +92 -59
  47. package/v3/dist/testing.d.ts +16 -16
  48. package/v3/dist/testing.js +7 -7
package/README.md CHANGED
@@ -39,14 +39,14 @@ const CheckoutEvent = Event({
39
39
  Cancel: {},
40
40
  });
41
41
 
42
- const CheckoutEffects = Slot.Effects({
43
- chargeCard: { cartId: Schema.String, totalCents: Schema.Number },
42
+ const CheckoutSlots = Slot.define({
43
+ chargeCard: Slot.fn({ cartId: Schema.String, totalCents: Schema.Number }),
44
44
  });
45
45
 
46
46
  const checkoutMachine = Machine.make({
47
47
  state: CheckoutState,
48
48
  event: CheckoutEvent,
49
- effects: CheckoutEffects,
49
+ slots: CheckoutSlots,
50
50
  initial: CheckoutState.ReviewingCart({ cartId: "cart_123", totalCents: 4200 }),
51
51
  })
52
52
  .on(CheckoutState.ReviewingCart, CheckoutEvent.Submit, ({ state }) =>
@@ -61,8 +61,8 @@ const checkoutMachine = Machine.make({
61
61
  .onAny(CheckoutEvent.Cancel, ({ state }) =>
62
62
  CheckoutState.Failed.derive(state, { reason: "cancelled" }),
63
63
  )
64
- .spawn(CheckoutState.ChargingCard, ({ effects, state }) =>
65
- effects.chargeCard({ cartId: state.cartId, totalCents: state.totalCents }),
64
+ .spawn(CheckoutState.ChargingCard, ({ slots, state }) =>
65
+ slots.chargeCard({ cartId: state.cartId, totalCents: state.totalCents }),
66
66
  )
67
67
  .final(CheckoutState.Confirmed)
68
68
  .final(CheckoutState.Failed);
@@ -86,10 +86,11 @@ const actor =
86
86
  yield *
87
87
  Machine.spawn(checkoutMachine, {
88
88
  slots: {
89
- chargeCard: ({ cartId, totalCents }, { self }) =>
89
+ chargeCard: ({ cartId, totalCents }) =>
90
90
  Effect.gen(function* () {
91
+ const ctx = yield* checkoutMachine.Context;
91
92
  const result = yield* PaymentService.charge(cartId, totalCents);
92
- yield* self.send(
93
+ yield* ctx.self.send(
93
94
  result.ok
94
95
  ? CheckoutEvent.Charged({ receiptId: result.receiptId })
95
96
  : CheckoutEvent.Declined({ reason: result.error }),
@@ -114,8 +115,12 @@ The same machine can run with different slot implementations in tests, local app
114
115
  const program = Effect.gen(function* () {
115
116
  const actor = yield* Machine.spawn(checkoutMachine, {
116
117
  slots: {
117
- chargeCard: ({ cartId }, { self }) =>
118
- self.send(CheckoutEvent.Charged({ receiptId: `rcpt_${cartId}` })),
118
+ chargeCard: ({ cartId }) =>
119
+ checkoutMachine.Context.pipe(
120
+ Effect.flatMap((ctx) =>
121
+ ctx.self.send(CheckoutEvent.Charged({ receiptId: `rcpt_${cartId}` })),
122
+ ),
123
+ ),
119
124
  },
120
125
  });
121
126
 
package/dist/actor.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ExtractReply, ReplyTypeBrand } from "./internal/brands.js";
2
2
  import { ActorStoppedError, DuplicateActorError, NoReplyError } from "./errors.js";
3
- import { EffectsDef, GuardsDef } from "./slot.js";
3
+ import { ProvideSlots, SlotsDef } from "./slot.js";
4
4
  import { ActorExit, Supervision } from "./supervision.js";
5
5
  import { ProcessEventError, ProcessEventHooks, ProcessEventResult, processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
6
- import { Machine } from "./machine.js";
6
+ import { Machine, PersistConfig } from "./machine.js";
7
7
  import { RuntimeQueuedEvent } from "./internal/runtime.js";
8
8
  import { Deferred, Effect, Layer, Option, PubSub, Queue, Ref, Scope, ServiceMap, Stream, SubscriptionRef } from "effect";
9
9
 
@@ -158,8 +158,10 @@ interface ActorSystem {
158
158
  readonly _tag: string;
159
159
  }, E extends {
160
160
  readonly _tag: string;
161
- }, R>(id: string, machine: Machine<S, E, R, any, any, any, any>, options?: {
161
+ }, R, SD extends SlotsDef = Record<string, never>>(id: string, machine: Machine<S, E, R, any, any, SD>, options?: {
162
162
  readonly supervision?: Supervision.Policy;
163
+ readonly slots?: ProvideSlots<SD, any>;
164
+ readonly persist?: PersistConfig<S>;
163
165
  }) => Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>;
164
166
  /**
165
167
  * Get an existing actor by ID
@@ -202,7 +204,7 @@ declare const buildActorRefCore: <S extends {
202
204
  readonly _tag: string;
203
205
  }, E extends {
204
206
  readonly _tag: string;
205
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, any, any, GD, EFD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueueRef: Ref.Ref<Queue.Queue<QueuedEvent<E>>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>, pendingReplies: Set<Deferred.Deferred<unknown, unknown>>, transitionsPubSub: PubSub.PubSub<TransitionInfo<S, E>> | undefined, exitDeferred: Deferred.Deferred<ActorExit<S>, never>) => ActorRef<S, E>;
207
+ }, R, SD extends SlotsDef>(id: string, machine: Machine<S, E, R, any, any, SD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueueRef: Ref.Ref<Queue.Queue<QueuedEvent<E>>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>, pendingReplies: Set<Deferred.Deferred<unknown, unknown>>, transitionsPubSub: PubSub.PubSub<TransitionInfo<S, E>> | undefined, exitDeferred: Deferred.Deferred<ActorExit<S>, never>) => ActorRef<S, E>;
206
208
  /**
207
209
  * Create and start an actor for a machine.
208
210
  * Delegates to the shared runtime kernel with actor-specific lifecycle hooks.
@@ -211,9 +213,10 @@ declare const createActor: <S extends {
211
213
  readonly _tag: string;
212
214
  }, E extends {
213
215
  readonly _tag: string;
214
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, options?: {
216
+ }, R, SD extends SlotsDef>(id: string, machine: Machine<S, E, R, any, any, SD>, options?: {
215
217
  initialState?: S;
216
- supervision?: Supervision.Policy; /** @internal Called by system after each restart — emits ActorRestarted system event */
218
+ supervision?: Supervision.Policy;
219
+ persist?: PersistConfig<S>; /** @internal Called by system after each restart — emits ActorRestarted system event */
217
220
  onRestart?: (generation: number, exit: ActorExit<unknown>) => Effect.Effect<void>;
218
221
  } | undefined) => Effect.Effect<ActorRef<S, E>, never, never>;
219
222
  /** Fail all pending call/ask Deferreds with ActorStoppedError. Safe to call multiple times. */
package/dist/actor.js CHANGED
@@ -1,7 +1,8 @@
1
- import { Inspector } from "./inspection.js";
1
+ import { ActorStoppedError, DuplicateActorError } from "./errors.js";
2
2
  import { processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
3
3
  import { emitWithTimestamp } from "./internal/inspection.js";
4
- import { ActorStoppedError, DuplicateActorError } from "./errors.js";
4
+ import { Inspector } from "./inspection.js";
5
+ import { materializeMachine } from "./machine.js";
5
6
  import { createRuntime } from "./internal/runtime.js";
6
7
  import { Cause, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Schedule, Scope, Semaphore, ServiceMap, Stream, SubscriptionRef } from "effect";
7
8
  //#region src/actor.ts
@@ -190,22 +191,85 @@ const buildInspectionHooks = (actorId, inspector) => ({
190
191
  }))
191
192
  });
192
193
  /**
194
+ * Load persisted state and run onRestore hook if present.
195
+ * Returns the resolved initial state (loaded, restored, or fallback to machineInitial).
196
+ * @internal
197
+ */
198
+ const loadAndRestore = (persist, machineInitial) => Effect.gen(function* () {
199
+ const loaded = yield* persist.load();
200
+ if (Option.isNone(loaded)) return machineInitial;
201
+ if (persist.onRestore === void 0) return loaded.value;
202
+ const restored = yield* persist.onRestore(loaded.value, { initial: machineInitial });
203
+ return Option.getOrElse(restored, () => machineInitial);
204
+ });
205
+ /**
206
+ * Resolve actor system from context, creating an implicit one if none exists.
207
+ * @internal
208
+ */
209
+ const resolveActorSystem = Effect.fn("effect-machine.resolveActorSystem")(function* () {
210
+ const existingSystem = yield* Effect.serviceOption(ActorSystem);
211
+ if (Option.isSome(existingSystem)) return {
212
+ system: existingSystem.value,
213
+ implicitSystemScope: void 0
214
+ };
215
+ const scope = yield* Scope.make();
216
+ return {
217
+ system: yield* make().pipe(Effect.provideService(Scope.Scope, scope)),
218
+ implicitSystemScope: scope
219
+ };
220
+ });
221
+ /**
222
+ * Run the supervision loop for a supervised actor.
223
+ * Observes exit deferred, applies restart policy, resets cell resources on restart.
224
+ * @internal
225
+ */
226
+ const runSupervisionLoop = (params) => Effect.gen(function* () {
227
+ const step = yield* Schedule.toStepWithSleep(params.supervision.schedule);
228
+ let generation = 0;
229
+ while (true) {
230
+ const currentRuntime = params.runtimeRef.current;
231
+ if (currentRuntime === void 0) return;
232
+ const generationExit = yield* Deferred.await(currentRuntime.exitDeferred);
233
+ if (generationExit._tag !== "Defect") {
234
+ yield* Deferred.succeed(params.terminalExitDeferred, generationExit);
235
+ return;
236
+ }
237
+ if (params.supervision.shouldRestart !== void 0 && !params.supervision.shouldRestart(generationExit)) {
238
+ yield* Deferred.succeed(params.terminalExitDeferred, generationExit);
239
+ return;
240
+ }
241
+ if ((yield* step(generationExit).pipe(Effect.exit))._tag === "Failure") {
242
+ yield* Deferred.succeed(params.terminalExitDeferred, generationExit);
243
+ return;
244
+ }
245
+ const restartState = params.persist !== void 0 ? yield* loadAndRestore(params.persist, params.machine.initial) : params.machine.initial;
246
+ yield* settlePendingReplies(params.pendingReplies, params.id);
247
+ const freshQueue = yield* Queue.unbounded();
248
+ yield* Ref.set(params.eventQueueRef, freshQueue);
249
+ yield* SubscriptionRef.set(params.stateRef, restartState);
250
+ yield* Ref.set(params.stoppedRef, false);
251
+ params.childrenMap.clear();
252
+ const machineForRestart = restartState !== params.machine.initial ? Object.create(params.machine, { initial: {
253
+ value: restartState,
254
+ enumerable: true
255
+ } }) : params.machine;
256
+ const newRuntime = yield* params.spawnGeneration(machineForRestart);
257
+ params.runtimeRef.current = newRuntime;
258
+ generation++;
259
+ if (params.onRestart !== void 0) yield* params.onRestart(generation, generationExit);
260
+ notifyListeners(params.listeners, restartState);
261
+ }
262
+ });
263
+ /**
193
264
  * Create and start an actor for a machine.
194
265
  * Delegates to the shared runtime kernel with actor-specific lifecycle hooks.
195
266
  */
196
267
  const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine, options) {
197
- const initial = options?.initialState ?? machine.initial;
268
+ const persist = options?.persist;
269
+ const initial = options?.initialState ?? (persist !== void 0 ? yield* loadAndRestore(persist, machine.initial) : machine.initial);
198
270
  yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
199
271
  yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
200
- const existingSystem = yield* Effect.serviceOption(ActorSystem);
201
- let system;
202
- let implicitSystemScope;
203
- if (Option.isSome(existingSystem)) system = existingSystem.value;
204
- else {
205
- const scope = yield* Scope.make();
206
- system = yield* make().pipe(Effect.provideService(Scope.Scope, scope));
207
- implicitSystemScope = scope;
208
- }
272
+ const { system, implicitSystemScope } = yield* resolveActorSystem();
209
273
  const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
210
274
  const childrenMap = /* @__PURE__ */ new Map();
211
275
  const pendingReplies = /* @__PURE__ */ new Set();
@@ -242,6 +306,9 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
242
306
  })) : void 0,
243
307
  onStateChange: (result, _event) => Effect.gen(function* () {
244
308
  notifyListeners(listeners, result.newState);
309
+ if (persist !== void 0 && result.transitioned) {
310
+ if (persist.shouldSave === void 0 || persist.shouldSave(result.newState, result.previousState)) yield* persist.save(result.newState);
311
+ }
245
312
  yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
246
313
  if (result.lifecycleRan) {
247
314
  yield* Effect.annotateCurrentSpan("effect_machine.state.from", result.previousState._tag);
@@ -311,36 +378,21 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
311
378
  runtimeRef.current = runtime;
312
379
  const supervision = options?.supervision;
313
380
  let supervisorFiber;
314
- if (supervision !== void 0) supervisorFiber = yield* Effect.forkDetach(Effect.gen(function* () {
315
- const step = yield* Schedule.toStepWithSleep(supervision.schedule);
316
- let generation = 0;
317
- while (true) {
318
- const currentRuntime = runtimeRef.current;
319
- if (currentRuntime === void 0) return;
320
- const generationExit = yield* Deferred.await(currentRuntime.exitDeferred);
321
- if (generationExit._tag !== "Defect") {
322
- yield* Deferred.succeed(terminalExitDeferred, generationExit);
323
- return;
324
- }
325
- if (supervision.shouldRestart !== void 0 && !supervision.shouldRestart(generationExit)) {
326
- yield* Deferred.succeed(terminalExitDeferred, generationExit);
327
- return;
328
- }
329
- if ((yield* step(generationExit).pipe(Effect.exit))._tag === "Failure") {
330
- yield* Deferred.succeed(terminalExitDeferred, generationExit);
331
- return;
332
- }
333
- yield* settlePendingReplies(pendingReplies, id);
334
- const freshQueue = yield* Queue.unbounded();
335
- yield* Ref.set(eventQueueRef, freshQueue);
336
- yield* SubscriptionRef.set(stateRef, machine.initial);
337
- yield* Ref.set(stoppedRef, false);
338
- childrenMap.clear();
339
- runtimeRef.current = yield* spawnGeneration(machine);
340
- generation++;
341
- if (options?.onRestart !== void 0) yield* options.onRestart(generation, generationExit);
342
- notifyListeners(listeners, machine.initial);
343
- }
381
+ if (supervision !== void 0) supervisorFiber = yield* Effect.forkDetach(runSupervisionLoop({
382
+ supervision,
383
+ machine,
384
+ id,
385
+ runtimeRef,
386
+ terminalExitDeferred,
387
+ pendingReplies,
388
+ eventQueueRef,
389
+ stateRef,
390
+ stoppedRef,
391
+ childrenMap,
392
+ listeners,
393
+ spawnGeneration,
394
+ persist,
395
+ onRestart: options?.onRestart
344
396
  }));
345
397
  else yield* Effect.forkDetach(Deferred.await(runtime.exitDeferred).pipe(Effect.tap((exit) => Deferred.succeed(terminalExitDeferred, exit))));
346
398
  return buildActorRefCore(id, machine, stateRef, eventQueueRef, stoppedRef, listeners, Effect.gen(function* () {
@@ -406,9 +458,11 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
406
458
  });
407
459
  const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id, machine, spawnOptions) {
408
460
  if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
461
+ const materialized = spawnOptions?.slots !== void 0 ? materializeMachine(machine, spawnOptions.slots) : machine;
409
462
  let actorRef;
410
- const actor = yield* createActor(id, machine, {
463
+ const actor = yield* createActor(id, materialized, {
411
464
  supervision: spawnOptions?.supervision,
465
+ persist: spawnOptions?.persist,
412
466
  onRestart: spawnOptions?.supervision !== void 0 ? (generation, exit) => actorRef !== void 0 ? emitSystemEvent({
413
467
  _tag: "ActorRestarted",
414
468
  id,
@@ -65,7 +65,7 @@ declare const EntityMachine: {
65
65
  readonly _tag: string;
66
66
  }, E extends {
67
67
  readonly _tag: string;
68
- }, R, EntityType extends string, Rpcs extends Rpc.Any>(entity: Entity.Entity<EntityType, Rpcs>, machine: Machine<S, E, R, any, any, any, any>, options?: EntityMachineOptions<S, E>) => Layer.Layer<never, never, R>;
68
+ }, R, EntityType extends string, Rpcs extends Rpc.Any>(entity: Entity.Entity<EntityType, Rpcs>, machine: Machine<S, E, R, any, any, any>, options?: EntityMachineOptions<S, E>) => Layer.Layer<never, never, R>;
69
69
  };
70
70
  //#endregion
71
71
  export { EntityMachine, EntityMachineOptions };
@@ -1,6 +1,6 @@
1
+ import { replay } from "../machine.js";
1
2
  import { createRuntime } from "../internal/runtime.js";
2
3
  import { ActorSystem, makeSystem } from "../actor.js";
3
- import { replay } from "../machine.js";
4
4
  import { PersistenceAdapter } from "./persistence.js";
5
5
  import { Clock, Effect, Option, Queue, Ref, Stream, SubscriptionRef } from "effect";
6
6
  import { Entity } from "effect/unstable/cluster";
@@ -61,7 +61,7 @@ declare const toEntity: <S extends {
61
61
  readonly _tag: string;
62
62
  }, E extends {
63
63
  readonly _tag: string;
64
- }, R>(machine: Machine<S, E, R, any, any, any, any>, options: ToEntityOptions) => Entity.Entity<string, Rpc.Rpc<"Send", Schema.Struct<{
64
+ }, R>(machine: Machine<S, E, R, any, any, any>, options: ToEntityOptions) => Entity.Entity<string, Rpc.Rpc<"Send", Schema.Struct<{
65
65
  event: Schema.Schema<E>;
66
66
  }>, Schema.Schema<S>, Schema.Never, never, never> | Rpc.Rpc<"Ask", Schema.Struct<{
67
67
  event: Schema.Schema<E>;
package/dist/errors.d.ts CHANGED
@@ -24,7 +24,7 @@ declare const MissingMatchHandlerError_base: Schema.ErrorClass<MissingMatchHandl
24
24
  declare class MissingMatchHandlerError extends MissingMatchHandlerError_base {}
25
25
  declare const SlotProvisionError_base: Schema.ErrorClass<SlotProvisionError, Schema.TaggedStruct<"SlotProvisionError", {
26
26
  readonly slotName: Schema.String;
27
- readonly slotType: Schema.Literals<readonly ["guard", "effect"]>;
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 {}
@@ -55,6 +55,13 @@ declare const PersistenceError_base: Schema.ErrorClass<PersistenceError, Schema.
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", {
59
+ readonly slotName: Schema.String;
60
+ readonly phase: Schema.Literals<readonly ["input", "output"]>;
61
+ readonly message: Schema.String;
62
+ }>, _$effect_Cause0.YieldableError>;
63
+ /** Slot input/output schema validation failed */
64
+ declare class SlotCodecError extends SlotCodecError_base {}
58
65
  declare const VersionConflictError_base: Schema.ErrorClass<VersionConflictError, Schema.TaggedStruct<"VersionConflictError", {
59
66
  readonly expected: Schema.Number;
60
67
  readonly actual: Schema.Number;
@@ -62,4 +69,4 @@ declare const VersionConflictError_base: Schema.ErrorClass<VersionConflictError,
62
69
  /** Optimistic locking failure — stored version doesn't match expected */
63
70
  declare class VersionConflictError extends VersionConflictError_base {}
64
71
  //#endregion
65
- export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotProvisionError, VersionConflictError };
72
+ export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotCodecError, SlotProvisionError, VersionConflictError };
package/dist/errors.js CHANGED
@@ -21,7 +21,7 @@ var MissingMatchHandlerError = class extends Schema.TaggedErrorClass()("MissingM
21
21
  /** Slot handler not found at runtime (internal error) */
22
22
  var SlotProvisionError = class extends Schema.TaggedErrorClass()("SlotProvisionError", {
23
23
  slotName: Schema.String,
24
- slotType: Schema.Literals(["guard", "effect"])
24
+ slotType: Schema.Literal("slot")
25
25
  }) {};
26
26
  /** Slot provision validation failed — missing or extra handlers */
27
27
  var ProvisionValidationError = class extends Schema.TaggedErrorClass()("ProvisionValidationError", {
@@ -39,10 +39,16 @@ var NoReplyError = class extends Schema.TaggedErrorClass()("NoReplyError", {
39
39
  }) {};
40
40
  /** Persistence adapter operation failed */
41
41
  var PersistenceError = class extends Schema.TaggedErrorClass()("PersistenceError", { message: Schema.String }) {};
42
+ /** Slot input/output schema validation failed */
43
+ var SlotCodecError = class extends Schema.TaggedErrorClass()("SlotCodecError", {
44
+ slotName: Schema.String,
45
+ phase: Schema.Literals(["input", "output"]),
46
+ message: Schema.String
47
+ }) {};
42
48
  /** Optimistic locking failure — stored version doesn't match expected */
43
49
  var VersionConflictError = class extends Schema.TaggedErrorClass()("VersionConflictError", {
44
50
  expected: Schema.Number,
45
51
  actual: Schema.Number
46
52
  }) {};
47
53
  //#endregion
48
- export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotProvisionError, VersionConflictError };
54
+ export { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotCodecError, SlotProvisionError, VersionConflictError };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { DeferReplyResult, ReplyResult } from "./internal/utils.js";
2
2
  import { Event, MachineEventSchema, MachineStateSchema, ReplyFields, State } from "./schema.js";
3
- import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotProvisionError, VersionConflictError } from "./errors.js";
4
- import { EffectHandlers, EffectSlot, EffectSlots, EffectsDef, EffectsSchema, GuardHandlers, GuardSlot, GuardSlots, GuardsDef, GuardsSchema, MachineContext, Slot } from "./slot.js";
3
+ import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotCodecError, SlotProvisionError, VersionConflictError } from "./errors.js";
4
+ import { HasSlotKeys, MachineContext, ProvideSlots, Slot, SlotCall, SlotCalls, SlotFnDef, SlotHandler, SlotInvocation, SlotRequest, SlotResult, SlotsDef, SlotsSchema } from "./slot.js";
5
5
  import { ActorExit, CellPhase, DefectPhase, Supervision } from "./supervision.js";
6
6
  import { ProcessEventResult } from "./internal/transition.js";
7
- import { BackgroundEffect, HandlerContext, Machine, MachineRef, MakeConfig, ProvideHandlers, SlotContext, SpawnEffect, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, machine_d_exports } from "./machine.js";
7
+ import { BackgroundEffect, HandlerContext, Machine, MachineRef, MakeConfig, SpawnEffect, StateHandlerContext, TaskOptions, TimeoutConfig, Transition, machine_d_exports } from "./machine.js";
8
8
  import { ActorRef, ActorRefSync, 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 EffectEvent, type EffectSlots, type EffectsDef, type EffectsSchema, type ErrorEvent, Event, type EventReceivedEvent, type GuardHandlers, type GuardSlot, type GuardSlots, type GuardsDef, type GuardsSchema, type HandlerContext, type InspectionEvent, type Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, 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 ProvideHandlers, ProvisionValidationError, type ReplyFields, type ReplyResult, type SimulationResult, Slot, type SlotContext, type EffectHandlers as SlotEffectHandlers, type EffectSlot as SlotEffectSlot, SlotProvisionError, 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, ActorStoppedError, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type CellPhase, type DefectPhase, type DeferReplyResult, DuplicateActorError, type EffectEvent, type ErrorEvent, Event, type EventReceivedEvent, type HandlerContext, type HasSlotKeys, type InspectionEvent, type Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, 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 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
@@ -1,9 +1,9 @@
1
+ import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotCodecError, SlotProvisionError, VersionConflictError } from "./errors.js";
1
2
  import { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
2
- import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, NoReplyError, PersistenceError, ProvisionValidationError, SlotProvisionError, VersionConflictError } from "./errors.js";
3
- import { ActorExit, Supervision } from "./supervision.js";
4
- import { ActorSystem, Default } from "./actor.js";
5
3
  import { Slot } from "./slot.js";
6
4
  import { machine_exports } from "./machine.js";
5
+ import { ActorExit, Supervision } from "./supervision.js";
6
+ import { 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, SlotProvisionError, State, Supervision, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
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 };
@@ -1,5 +1,5 @@
1
1
  import { NoReplyError } from "../errors.js";
2
- import { EffectsDef, GuardsDef, MachineContext } from "../slot.js";
2
+ import { MachineContext, SlotsDef } from "../slot.js";
3
3
  import { ActorExit } from "../supervision.js";
4
4
  import { ProcessEventHooks, ProcessEventResult } from "./transition.js";
5
5
  import { Machine, MachineRef } from "../machine.js";
@@ -137,6 +137,6 @@ declare const createRuntime: <S extends {
137
137
  readonly _tag: string;
138
138
  }, E extends {
139
139
  readonly _tag: string;
140
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, any, any, GD, EFD>, system: ActorSystem, config: RuntimeConfig<S, E>) => Effect.Effect<RuntimeHandle<S, E>, never, Scope.Scope | Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
140
+ }, R, SD extends SlotsDef>(machine: Machine<S, E, R, any, any, SD>, system: ActorSystem, config: RuntimeConfig<S, E>) => Effect.Effect<RuntimeHandle<S, E>, never, Scope.Scope | Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
141
141
  //#endregion
142
142
  export { ProcessQueuedResult, RuntimeCellResources, RuntimeConfig, RuntimeHandle, RuntimeLifecycleHooks, RuntimeQueuedEvent, createRuntime };
@@ -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.
@@ -81,14 +81,14 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
81
81
  self,
82
82
  system
83
83
  };
84
- const { effects: effectSlots } = machine._slots;
84
+ const slots = machine._slots;
85
85
  for (const bg of machine.backgroundEffects) {
86
86
  const fiber = yield* bg.handler({
87
87
  actorId,
88
88
  state: machine.initial,
89
89
  event: initEvent,
90
90
  self,
91
- effects: effectSlots,
91
+ slots,
92
92
  system
93
93
  }).pipe(Effect.provideService(machine.Context, ctx), Effect.forkIn(actorScope));
94
94
  backgroundFibers.push(fiber);
@@ -278,12 +278,7 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
278
278
  yield* Ref.set(stoppedRef, true);
279
279
  if (lifecycle?.onShutdown !== void 0) yield* lifecycle.onShutdown();
280
280
  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
- }
281
+ const remaining = yield* Queue.clear(eventQueue);
287
282
  for (const entry of remaining) if (entry._tag === "sendWait") forkEffect(Deferred.succeed(entry.done, void 0));
288
283
  else if (entry._tag === "ask") forkEffect(Deferred.fail(entry.reply, new NoReplyError({
289
284
  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;