effect-machine 0.8.0 → 0.10.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 (76) hide show
  1. package/README.md +76 -16
  2. package/dist/_virtual/_rolldown/runtime.js +6 -11
  3. package/dist/actor.d.ts +58 -72
  4. package/dist/actor.js +166 -32
  5. package/dist/cluster/entity-machine.d.ts +0 -1
  6. package/dist/cluster/entity-machine.js +6 -6
  7. package/dist/cluster/index.js +1 -2
  8. package/dist/cluster/to-entity.js +1 -3
  9. package/dist/errors.d.ts +12 -1
  10. package/dist/errors.js +8 -3
  11. package/dist/index.d.ts +4 -4
  12. package/dist/index.js +2 -3
  13. package/dist/inspection.js +1 -3
  14. package/dist/internal/inspection.js +1 -3
  15. package/dist/internal/transition.d.ts +26 -2
  16. package/dist/internal/transition.js +37 -10
  17. package/dist/internal/utils.d.ts +7 -2
  18. package/dist/internal/utils.js +1 -3
  19. package/dist/machine.d.ts +66 -4
  20. package/dist/machine.js +67 -31
  21. package/dist/persistence/adapter.js +1 -3
  22. package/dist/persistence/adapters/in-memory.js +1 -3
  23. package/dist/persistence/index.js +1 -2
  24. package/dist/persistence/persistent-actor.js +54 -19
  25. package/dist/persistence/persistent-machine.js +1 -3
  26. package/dist/schema.js +1 -3
  27. package/dist/slot.js +1 -3
  28. package/dist/testing.js +58 -6
  29. package/package.json +19 -18
  30. package/v3/dist/_virtual/_rolldown/runtime.js +13 -0
  31. package/{dist-v3 → v3/dist}/actor.d.ts +65 -78
  32. package/{dist-v3 → v3/dist}/actor.js +173 -37
  33. package/{dist-v3 → v3/dist}/cluster/entity-machine.d.ts +1 -2
  34. package/{dist-v3 → v3/dist}/cluster/entity-machine.js +9 -9
  35. package/{dist-v3 → v3/dist}/cluster/index.js +1 -2
  36. package/{dist-v3 → v3/dist}/cluster/to-entity.d.ts +1 -1
  37. package/{dist-v3 → v3/dist}/cluster/to-entity.js +2 -4
  38. package/v3/dist/errors.d.ts +76 -0
  39. package/{dist-v3 → v3/dist}/errors.js +9 -4
  40. package/v3/dist/index.d.ts +13 -0
  41. package/v3/dist/index.js +13 -0
  42. package/{dist-v3 → v3/dist}/inspection.d.ts +53 -8
  43. package/v3/dist/inspection.js +156 -0
  44. package/{dist-v3 → v3/dist}/internal/brands.d.ts +1 -1
  45. package/{dist-v3 → v3/dist}/internal/inspection.d.ts +1 -1
  46. package/v3/dist/internal/inspection.js +20 -0
  47. package/{dist-v3 → v3/dist}/internal/transition.d.ts +35 -11
  48. package/{dist-v3 → v3/dist}/internal/transition.js +47 -17
  49. package/{dist-v3 → v3/dist}/internal/utils.d.ts +9 -4
  50. package/{dist-v3 → v3/dist}/internal/utils.js +2 -4
  51. package/{dist-v3 → v3/dist}/machine.d.ts +86 -10
  52. package/{dist-v3 → v3/dist}/machine.js +130 -33
  53. package/{dist-v3 → v3/dist}/persistence/adapter.d.ts +18 -5
  54. package/{dist-v3 → v3/dist}/persistence/adapter.js +2 -4
  55. package/{dist-v3 → v3/dist}/persistence/adapters/in-memory.d.ts +1 -1
  56. package/{dist-v3 → v3/dist}/persistence/adapters/in-memory.js +2 -4
  57. package/{dist-v3 → v3/dist}/persistence/index.js +1 -2
  58. package/{dist-v3 → v3/dist}/persistence/persistent-actor.d.ts +7 -6
  59. package/{dist-v3 → v3/dist}/persistence/persistent-actor.js +59 -22
  60. package/{dist-v3 → v3/dist}/persistence/persistent-machine.d.ts +1 -1
  61. package/{dist-v3 → v3/dist}/persistence/persistent-machine.js +2 -4
  62. package/{dist-v3 → v3/dist}/schema.d.ts +1 -1
  63. package/{dist-v3 → v3/dist}/schema.js +6 -5
  64. package/{dist-v3 → v3/dist}/slot.d.ts +4 -3
  65. package/{dist-v3 → v3/dist}/slot.js +2 -4
  66. package/{dist-v3 → v3/dist}/testing.d.ts +14 -8
  67. package/{dist-v3 → v3/dist}/testing.js +61 -9
  68. package/dist-v3/_virtual/_rolldown/runtime.js +0 -18
  69. package/dist-v3/errors.d.ts +0 -27
  70. package/dist-v3/index.d.ts +0 -13
  71. package/dist-v3/index.js +0 -14
  72. package/dist-v3/inspection.js +0 -50
  73. package/dist-v3/internal/inspection.js +0 -15
  74. /package/{dist-v3 → v3/dist}/cluster/index.d.ts +0 -0
  75. /package/{dist-v3 → v3/dist}/internal/brands.js +0 -0
  76. /package/{dist-v3 → v3/dist}/persistence/index.d.ts +0 -0
@@ -1,6 +1,6 @@
1
- import { Schema } from "effect";
1
+ import { Context, Effect, Schema } from "effect";
2
2
 
3
- //#region src-v3/inspection.d.ts
3
+ //#region src/inspection.d.ts
4
4
  /**
5
5
  * Resolve a type param: if it's a Schema, extract `.Type`; otherwise use as-is.
6
6
  */
@@ -45,6 +45,18 @@ interface EffectEvent<S> {
45
45
  readonly state: S;
46
46
  readonly timestamp: number;
47
47
  }
48
+ /**
49
+ * Event emitted when a task lifecycle phase occurs
50
+ */
51
+ interface TaskEvent<S> {
52
+ readonly type: "@machine.task";
53
+ readonly actorId: string;
54
+ readonly state: S;
55
+ readonly taskName?: string;
56
+ readonly phase: "start" | "success" | "failure" | "interrupt";
57
+ readonly error?: string;
58
+ readonly timestamp: number;
59
+ }
48
60
  /**
49
61
  * Event emitted when a transition handler or spawn effect fails with a defect
50
62
  */
@@ -69,7 +81,7 @@ interface StopEvent<S> {
69
81
  /**
70
82
  * Union of all inspection events
71
83
  */
72
- type InspectionEvent<S, E> = SpawnEvent<S> | EventReceivedEvent<S, E> | TransitionEvent<S, E> | EffectEvent<S> | ErrorEvent<S, E> | StopEvent<S>;
84
+ type InspectionEvent<S, E> = SpawnEvent<S> | EventReceivedEvent<S, E> | TransitionEvent<S, E> | EffectEvent<S> | TaskEvent<S> | ErrorEvent<S, E> | StopEvent<S>;
73
85
  /**
74
86
  * Convenience alias for untyped inspection events.
75
87
  * Useful for general-purpose inspectors that don't need specific state/event types.
@@ -81,19 +93,23 @@ type AnyInspectionEvent = InspectionEvent<{
81
93
  }, {
82
94
  readonly _tag: string;
83
95
  }>;
96
+ /**
97
+ * Inspector handler — sync callback or Effect-returning callback.
98
+ */
99
+ type InspectorHandler<S, E> = (event: InspectionEvent<S, E>) => void | Effect.Effect<void, never, never>;
84
100
  /**
85
101
  * Inspector interface for observing machine behavior
86
102
  */
87
103
  interface Inspector<S, E> {
88
- readonly onInspect: (event: InspectionEvent<S, E>) => void;
104
+ readonly onInspect: InspectorHandler<S, E>;
89
105
  }
90
106
  /**
91
107
  * Inspector service tag - optional service for machine introspection
92
108
  * Uses `any` types to allow variance flexibility when providing the service
93
109
  */
94
- declare const Inspector: any;
110
+ declare const Inspector: Context.Tag<Inspector<any, any>, Inspector<any, any>>;
95
111
  /**
96
- * Create an inspector from a callback function.
112
+ * Create an inspector from a sync callback function.
97
113
  *
98
114
  * Type params accept either raw tagged types or Schema constructors:
99
115
  * - `makeInspector(cb)` — defaults to `AnyInspectionEvent`
@@ -104,7 +120,36 @@ declare const makeInspector: <S = {
104
120
  readonly _tag: string;
105
121
  }, E = {
106
122
  readonly _tag: string;
107
- }>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => void) => Inspector<ResolveType<S>, ResolveType<E>>;
123
+ }>(onInspect: InspectorHandler<ResolveType<S>, ResolveType<E>>) => Inspector<ResolveType<S>, ResolveType<E>>;
124
+ /**
125
+ * Create an inspector from an Effect-returning callback function.
126
+ */
127
+ declare const makeInspectorEffect: <S = {
128
+ readonly _tag: string;
129
+ }, E = {
130
+ readonly _tag: string;
131
+ }>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => Effect.Effect<void, never, never>) => Inspector<ResolveType<S>, ResolveType<E>>;
132
+ /**
133
+ * Combine multiple inspectors into one. All run concurrently per event.
134
+ * Individual inspector failures are swallowed.
135
+ */
136
+ declare const combineInspectors: <S, E>(...inspectors: ReadonlyArray<Inspector<S, E>>) => Inspector<S, E>;
137
+ /**
138
+ * Options for the tracing inspector.
139
+ */
140
+ interface TracingInspectorOptions<S, E> {
141
+ readonly spanName?: string | ((event: InspectionEvent<S, E>) => string);
142
+ readonly attributes?: (event: InspectionEvent<S, E>) => Readonly<Record<string, string | number | boolean>>;
143
+ readonly eventName?: (event: InspectionEvent<S, E>) => string;
144
+ }
145
+ /**
146
+ * Inspector that emits OpenTelemetry spans and events for each inspection event.
147
+ */
148
+ declare const tracingInspector: <S extends {
149
+ readonly _tag: string;
150
+ }, E extends {
151
+ readonly _tag: string;
152
+ }>(options?: TracingInspectorOptions<S, E>) => Inspector<S, E>;
108
153
  /**
109
154
  * Console inspector that logs events in a readable format
110
155
  */
@@ -122,4 +167,4 @@ declare const collectingInspector: <S extends {
122
167
  readonly _tag: string;
123
168
  }>(events: InspectionEvent<S, E>[]) => Inspector<S, E>;
124
169
  //#endregion
125
- export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, SpawnEvent, StopEvent, TransitionEvent, collectingInspector, consoleInspector, makeInspector };
170
+ export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
@@ -0,0 +1,156 @@
1
+ import { Context, Effect, Option } from "effect";
2
+ //#region src/inspection.ts
3
+ /**
4
+ * Inspector service tag - optional service for machine introspection
5
+ * Uses `any` types to allow variance flexibility when providing the service
6
+ */
7
+ const Inspector = Context.GenericTag("@effect/machine/Inspector");
8
+ /**
9
+ * Create an inspector from a sync callback function.
10
+ *
11
+ * Type params accept either raw tagged types or Schema constructors:
12
+ * - `makeInspector(cb)` — defaults to `AnyInspectionEvent`
13
+ * - `makeInspector<MyState, MyEvent>(cb)` — explicit tagged types
14
+ * - `makeInspector<typeof MyState, typeof MyEvent>(cb)` — schema constructors (auto-extracts `.Type`)
15
+ */
16
+ const makeInspector = (onInspect) => ({ onInspect });
17
+ /**
18
+ * Create an inspector from an Effect-returning callback function.
19
+ */
20
+ const makeInspectorEffect = (onInspect) => ({ onInspect });
21
+ /**
22
+ * Run an inspector handler, handling both sync and Effect returns.
23
+ * @internal
24
+ */
25
+ const inspectionEffect = (inspector, event) => {
26
+ const result = inspector.onInspect(event);
27
+ return Effect.isEffect(result) ? result : Effect.void;
28
+ };
29
+ /**
30
+ * Combine multiple inspectors into one. All run concurrently per event.
31
+ * Individual inspector failures are swallowed.
32
+ */
33
+ const combineInspectors = (...inspectors) => ({ onInspect: (event) => Effect.forEach(inspectors, (inspector) => inspectionEffect(inspector, event).pipe(Effect.catchAllCause(() => Effect.void)), {
34
+ concurrency: "unbounded",
35
+ discard: true
36
+ }) });
37
+ const inspectionSpanName = (event) => {
38
+ switch (event.type) {
39
+ case "@machine.spawn": return `Machine.inspect ${event.initialState._tag}`;
40
+ case "@machine.event": return `Machine.inspect ${event.event._tag}`;
41
+ case "@machine.transition": return `Machine.inspect ${event.fromState._tag}->${event.toState._tag}`;
42
+ case "@machine.effect": return `Machine.inspect ${event.effectType}`;
43
+ case "@machine.task": return `Machine.inspect task:${event.phase}`;
44
+ case "@machine.error": return `Machine.inspect ${event.phase}`;
45
+ case "@machine.stop": return `Machine.inspect ${event.finalState._tag}`;
46
+ }
47
+ };
48
+ const inspectionTraceName = (event) => {
49
+ switch (event.type) {
50
+ case "@machine.spawn": return `machine.spawn ${event.initialState._tag}`;
51
+ case "@machine.event": return `machine.event ${event.event._tag}`;
52
+ case "@machine.transition": return `machine.transition ${event.fromState._tag}->${event.toState._tag}`;
53
+ case "@machine.effect": return `machine.effect ${event.effectType}`;
54
+ case "@machine.task": return `machine.task ${event.phase}${event.taskName === void 0 ? "" : ` ${event.taskName}`}`;
55
+ case "@machine.error": return `machine.error ${event.phase}`;
56
+ case "@machine.stop": return `machine.stop ${event.finalState._tag}`;
57
+ }
58
+ };
59
+ const inspectionAttributes = (event) => {
60
+ const shared = {
61
+ "machine.actor.id": event.actorId,
62
+ "machine.inspection.type": event.type
63
+ };
64
+ switch (event.type) {
65
+ case "@machine.spawn": return {
66
+ ...shared,
67
+ "machine.state.initial": event.initialState._tag
68
+ };
69
+ case "@machine.event": return {
70
+ ...shared,
71
+ "machine.state.current": event.state._tag,
72
+ "machine.event.tag": event.event._tag
73
+ };
74
+ case "@machine.transition": return {
75
+ ...shared,
76
+ "machine.state.from": event.fromState._tag,
77
+ "machine.state.to": event.toState._tag,
78
+ "machine.event.tag": event.event._tag
79
+ };
80
+ case "@machine.effect": return {
81
+ ...shared,
82
+ "machine.state.current": event.state._tag,
83
+ "machine.effect.kind": event.effectType
84
+ };
85
+ case "@machine.task": return {
86
+ ...shared,
87
+ "machine.state.current": event.state._tag,
88
+ "machine.task.phase": event.phase,
89
+ ...event.taskName === void 0 ? {} : { "machine.task.name": event.taskName }
90
+ };
91
+ case "@machine.error": return {
92
+ ...shared,
93
+ "machine.phase": event.phase,
94
+ "machine.state.current": event.state._tag
95
+ };
96
+ case "@machine.stop": return {
97
+ ...shared,
98
+ "machine.state.final": event.finalState._tag
99
+ };
100
+ }
101
+ };
102
+ /**
103
+ * Inspector that emits OpenTelemetry spans and events for each inspection event.
104
+ */
105
+ const tracingInspector = (options) => ({ onInspect: (event) => {
106
+ const spanName = typeof options?.spanName === "function" ? options.spanName(event) : options?.spanName;
107
+ const traceName = options?.eventName?.(event) ?? inspectionTraceName(event);
108
+ const attributes = {
109
+ ...inspectionAttributes(event),
110
+ ...options?.attributes?.(event) ?? {}
111
+ };
112
+ return Effect.gen(function* () {
113
+ const currentSpan = yield* Effect.option(Effect.currentSpan);
114
+ if (Option.isSome(currentSpan)) currentSpan.value.event(traceName, BigInt(event.timestamp) * 1000000n, {
115
+ actorId: event.actorId,
116
+ inspectionType: event.type
117
+ });
118
+ }).pipe(Effect.withSpan(spanName ?? inspectionSpanName(event), { attributes }));
119
+ } });
120
+ /**
121
+ * Console inspector that logs events in a readable format
122
+ */
123
+ const consoleInspector = () => makeInspector((event) => {
124
+ const prefix = `[${event.actorId}]`;
125
+ switch (event.type) {
126
+ case "@machine.spawn":
127
+ console.log(prefix, "spawned →", event.initialState._tag);
128
+ break;
129
+ case "@machine.event":
130
+ console.log(prefix, "received", event.event._tag, "in", event.state._tag);
131
+ break;
132
+ case "@machine.transition":
133
+ console.log(prefix, event.fromState._tag, "→", event.toState._tag);
134
+ break;
135
+ case "@machine.effect":
136
+ console.log(prefix, event.effectType, "effect in", event.state._tag);
137
+ break;
138
+ case "@machine.task":
139
+ console.log(prefix, "task", event.phase, event.taskName ?? "<unnamed>", "in", event.state._tag);
140
+ break;
141
+ case "@machine.error":
142
+ console.log(prefix, "error in", event.phase, event.state._tag, "-", event.error);
143
+ break;
144
+ case "@machine.stop":
145
+ console.log(prefix, "stopped in", event.finalState._tag);
146
+ break;
147
+ }
148
+ });
149
+ /**
150
+ * Collecting inspector that stores events in an array for testing
151
+ */
152
+ const collectingInspector = (events) => ({ onInspect: (event) => {
153
+ events.push(event);
154
+ } });
155
+ //#endregion
156
+ export { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
@@ -1,6 +1,6 @@
1
1
  import { Brand } from "effect";
2
2
 
3
- //#region src-v3/internal/brands.d.ts
3
+ //#region src/internal/brands.d.ts
4
4
  declare const StateTypeId: unique symbol;
5
5
  declare const EventTypeId: unique symbol;
6
6
  type StateTypeId = typeof StateTypeId;
@@ -1,7 +1,7 @@
1
1
  import { InspectionEvent, Inspector } from "../inspection.js";
2
2
  import { Effect } from "effect";
3
3
 
4
- //#region src-v3/internal/inspection.d.ts
4
+ //#region src/internal/inspection.d.ts
5
5
  /**
6
6
  * Emit an inspection event with timestamp from Clock.
7
7
  * @internal
@@ -0,0 +1,20 @@
1
+ import { Clock, Effect } from "effect";
2
+ //#region src/internal/inspection.ts
3
+ /**
4
+ * Emit an inspection event with timestamp from Clock.
5
+ * @internal
6
+ */
7
+ const emitWithTimestamp = Effect.fn("effect-machine.emitWithTimestamp")(function* (inspector, makeEvent) {
8
+ if (inspector === void 0) return;
9
+ const event = makeEvent(yield* Clock.currentTimeMillis);
10
+ const result = yield* Effect.sync(() => {
11
+ try {
12
+ return inspector.onInspect(event);
13
+ } catch {
14
+ return;
15
+ }
16
+ });
17
+ if (result !== void 0 && Effect.isEffect(result)) yield* result.pipe(Effect.catchAllCause(() => Effect.void));
18
+ });
19
+ //#endregion
20
+ export { emitWithTimestamp };
@@ -1,9 +1,9 @@
1
- import { EffectsDef, GuardsDef } from "../slot.js";
1
+ import { EffectsDef, GuardsDef, MachineContext } from "../slot.js";
2
2
  import { BuiltMachine, Machine, MachineRef, SpawnEffect, Transition } from "../machine.js";
3
3
  import { ActorSystem } from "../actor.js";
4
4
  import { Cause, Effect, Scope } from "effect";
5
5
 
6
- //#region src-v3/internal/transition.d.ts
6
+ //#region src/internal/transition.d.ts
7
7
  /**
8
8
  * Result of executing a transition.
9
9
  */
@@ -29,7 +29,11 @@ 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) => Effect.Effect<S, never, Exclude<R, unknown>>;
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<{
33
+ newState: S;
34
+ hasReply: boolean;
35
+ reply: unknown;
36
+ }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
33
37
  /**
34
38
  * Execute a transition for a given state and event.
35
39
  * Handles transition resolution, handler invocation, and guard/effect slot creation.
@@ -45,11 +49,13 @@ declare const executeTransition: <S extends {
45
49
  readonly _tag: string;
46
50
  }, E extends {
47
51
  readonly _tag: string;
48
- }, 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) => Effect.Effect<{
52
+ }, 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<{
49
53
  newState: S;
50
54
  transitioned: boolean;
51
55
  reenter: boolean;
52
- }, never, Exclude<R, unknown>>;
56
+ hasReply: boolean;
57
+ reply: unknown;
58
+ }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
53
59
  /**
54
60
  * Optional hooks for event processing inspection/tracing.
55
61
  */
@@ -84,7 +90,22 @@ interface ProcessEventResult<S> {
84
90
  readonly lifecycleRan: boolean;
85
91
  /** Whether new state is final */
86
92
  readonly isFinal: boolean;
93
+ /** Whether the handler provided a reply (structural, not value-based) */
94
+ readonly hasReply: boolean;
95
+ /** Domain reply value from handler (used by ask). Only meaningful when hasReply is true. */
96
+ readonly reply?: unknown;
97
+ /** Whether the event was postponed (buffered for retry after next state change) */
98
+ readonly postponed: boolean;
87
99
  }
100
+ /**
101
+ * Check if an event should be postponed in the current state.
102
+ * @internal
103
+ */
104
+ declare const shouldPostpone: <S extends {
105
+ readonly _tag: string;
106
+ }, E extends {
107
+ readonly _tag: string;
108
+ }, R>(machine: Machine<S, E, R, any, any, any, any>, stateTag: string, eventTag: string) => boolean;
88
109
  /**
89
110
  * Process a single event through the machine.
90
111
  *
@@ -103,13 +124,16 @@ declare const processEventCore: <S extends {
103
124
  readonly _tag: string;
104
125
  }, 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: {
105
126
  current: Scope.CloseableScope;
106
- }, system: ActorSystem, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
107
- newState: any;
127
+ }, system: ActorSystem, actorId: string, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
128
+ newState: S;
108
129
  previousState: S;
109
130
  transitioned: boolean;
110
- lifecycleRan: any;
131
+ lifecycleRan: boolean;
111
132
  isFinal: boolean;
112
- }, unknown, unknown>;
133
+ hasReply: boolean;
134
+ reply: unknown;
135
+ postponed: boolean;
136
+ }, never, Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
113
137
  /**
114
138
  * Run spawn effects for a state (forked into state scope, auto-cancelled on state exit).
115
139
  *
@@ -119,7 +143,7 @@ declare const runSpawnEffects: <S extends {
119
143
  readonly _tag: string;
120
144
  }, E extends {
121
145
  readonly _tag: string;
122
- }, 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.CloseableScope, system: ActorSystem, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never, unknown>;
146
+ }, 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.CloseableScope, system: ActorSystem, actorId: string, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never, Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
123
147
  /**
124
148
  * Resolve which transition should fire for a given state and event.
125
149
  * Uses indexed O(1) lookup. First matching transition wins.
@@ -157,4 +181,4 @@ declare const findSpawnEffects: <S extends {
157
181
  readonly _tag: string;
158
182
  }, 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>>;
159
183
  //#endregion
160
- export { ProcessEventError, ProcessEventHooks, ProcessEventResult, TransitionExecutionResult, executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler };
184
+ export { ProcessEventError, ProcessEventHooks, ProcessEventResult, TransitionExecutionResult, executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler, shouldPostpone };
@@ -1,8 +1,7 @@
1
1
  import { INTERNAL_ENTER_EVENT, isEffect } from "./utils.js";
2
2
  import { BuiltMachine } from "../machine.js";
3
3
  import { Cause, Effect, Exit, Scope } from "effect";
4
-
5
- //#region src-v3/internal/transition.ts
4
+ //#region src/internal/transition.ts
6
5
  /**
7
6
  * Transition execution and indexing.
8
7
  *
@@ -23,8 +22,9 @@ import { Cause, Effect, Exit, Scope } from "effect";
23
22
  *
24
23
  * @internal
25
24
  */
26
- const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(function* (machine, transition, state, event, self, system) {
25
+ const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(function* (machine, transition, state, event, self, system, actorId) {
27
26
  const ctx = {
27
+ actorId,
28
28
  state,
29
29
  event,
30
30
  self,
@@ -37,8 +37,18 @@ const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(fu
37
37
  guards,
38
38
  effects
39
39
  };
40
- const result = transition.handler(handlerCtx);
41
- return isEffect(result) ? yield* result.pipe(Effect.provideService(machine.Context, ctx)) : result;
40
+ const raw = transition.handler(handlerCtx);
41
+ const resolved = isEffect(raw) ? yield* raw.pipe(Effect.provideService(machine.Context, ctx)) : raw;
42
+ if (resolved !== null && typeof resolved === "object" && "state" in resolved && "reply" in resolved && !("_tag" in resolved)) return {
43
+ newState: resolved.state,
44
+ hasReply: true,
45
+ reply: resolved.reply
46
+ };
47
+ return {
48
+ newState: resolved,
49
+ hasReply: false,
50
+ reply: void 0
51
+ };
42
52
  });
43
53
  /**
44
54
  * Execute a transition for a given state and event.
@@ -51,20 +61,33 @@ const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(fu
51
61
  *
52
62
  * @internal
53
63
  */
54
- const executeTransition = Effect.fn("effect-machine.executeTransition")(function* (machine, currentState, event, self, system) {
64
+ const executeTransition = Effect.fn("effect-machine.executeTransition")(function* (machine, currentState, event, self, system, actorId) {
55
65
  const transition = resolveTransition(machine, currentState, event);
56
66
  if (transition === void 0) return {
57
67
  newState: currentState,
58
68
  transitioned: false,
59
- reenter: false
69
+ reenter: false,
70
+ hasReply: false,
71
+ reply: void 0
60
72
  };
73
+ const { newState, hasReply, reply } = yield* runTransitionHandler(machine, transition, currentState, event, self, system, actorId);
61
74
  return {
62
- newState: yield* runTransitionHandler(machine, transition, currentState, event, self, system),
75
+ newState,
63
76
  transitioned: true,
64
- reenter: transition.reenter === true
77
+ reenter: transition.reenter === true,
78
+ hasReply,
79
+ reply
65
80
  };
66
81
  });
67
82
  /**
83
+ * Check if an event should be postponed in the current state.
84
+ * @internal
85
+ */
86
+ const shouldPostpone = (machine, stateTag, eventTag) => {
87
+ for (const rule of machine.postponeRules) if (rule.stateTag === stateTag && rule.eventTag === eventTag) return true;
88
+ return false;
89
+ };
90
+ /**
68
91
  * Process a single event through the machine.
69
92
  *
70
93
  * Handles:
@@ -76,8 +99,8 @@ const executeTransition = Effect.fn("effect-machine.executeTransition")(function
76
99
  *
77
100
  * @internal
78
101
  */
79
- const processEventCore = Effect.fn("effect-machine.processEventCore")(function* (machine, currentState, event, self, stateScopeRef, system, hooks) {
80
- const result = yield* executeTransition(machine, currentState, event, self, system).pipe(Effect.catchAllCause((cause) => {
102
+ const processEventCore = Effect.fn("effect-machine.processEventCore")(function* (machine, currentState, event, self, stateScopeRef, system, actorId, hooks) {
103
+ const result = yield* executeTransition(machine, currentState, event, self, system, actorId).pipe(Effect.catchAllCause((cause) => {
81
104
  if (Cause.isInterruptedOnly(cause)) return Effect.interrupt;
82
105
  const onError = hooks?.onError;
83
106
  if (onError === void 0) return Effect.failCause(cause).pipe(Effect.orDie);
@@ -93,7 +116,10 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
93
116
  previousState: currentState,
94
117
  transitioned: false,
95
118
  lifecycleRan: false,
96
- isFinal: false
119
+ isFinal: false,
120
+ hasReply: false,
121
+ reply: void 0,
122
+ postponed: false
97
123
  };
98
124
  const newState = result.newState;
99
125
  const runLifecycle = newState._tag !== currentState._tag || result.reenter;
@@ -102,14 +128,17 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
102
128
  stateScopeRef.current = yield* Scope.make();
103
129
  if (hooks?.onTransition !== void 0) yield* hooks.onTransition(currentState, newState, event);
104
130
  if (hooks?.onSpawnEffect !== void 0) yield* hooks.onSpawnEffect(newState);
105
- yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, system, hooks?.onError);
131
+ yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, system, actorId, hooks?.onError);
106
132
  }
107
133
  return {
108
134
  newState,
109
135
  previousState: currentState,
110
136
  transitioned: true,
111
137
  lifecycleRan: runLifecycle,
112
- isFinal: machine.finalStates.has(newState._tag)
138
+ isFinal: machine.finalStates.has(newState._tag),
139
+ hasReply: result.hasReply,
140
+ reply: result.reply,
141
+ postponed: false
113
142
  };
114
143
  });
115
144
  /**
@@ -117,9 +146,10 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
117
146
  *
118
147
  * @internal
119
148
  */
120
- const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, system, onError) {
149
+ const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, system, actorId, onError) {
121
150
  const spawnEffects = findSpawnEffects(machine, state._tag);
122
151
  const ctx = {
152
+ actorId,
123
153
  state,
124
154
  event,
125
155
  self,
@@ -129,6 +159,7 @@ const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (m
129
159
  const reportError = onError;
130
160
  for (const spawnEffect of spawnEffects) {
131
161
  const effect = spawnEffect.handler({
162
+ actorId,
132
163
  state,
133
164
  event,
134
165
  self,
@@ -233,6 +264,5 @@ const findTransitions = (input, stateTag, eventTag) => {
233
264
  const findSpawnEffects = (machine, stateTag) => {
234
265
  return getIndex(machine).spawn.get(stateTag) ?? [];
235
266
  };
236
-
237
267
  //#endregion
238
- export { executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler };
268
+ export { executeTransition, findSpawnEffects, findTransitions, invalidateIndex, processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler, shouldPostpone };
@@ -1,7 +1,7 @@
1
1
  import { ActorSystem } from "../actor.js";
2
2
  import { Effect } from "effect";
3
3
 
4
- //#region src-v3/internal/utils.d.ts
4
+ //#region src/internal/utils.d.ts
5
5
  /**
6
6
  * Extracts _tag from a tagged union member
7
7
  */
@@ -23,10 +23,15 @@ type InstanceOf<C> = C extends ((...args: unknown[]) => infer R) ? R : never;
23
23
  type TaggedConstructor<T extends {
24
24
  readonly _tag: string;
25
25
  }> = (args: Omit<T, "_tag">) => T;
26
+ /** Reply tuple returned from transition handlers for ask support */
27
+ interface TransitionReply<State> {
28
+ readonly state: State;
29
+ readonly reply: unknown;
30
+ }
26
31
  /**
27
- * Transition handler result - either a new state or Effect producing one
32
+ * Transition handler result - either a new state, reply tuple, or Effect producing one
28
33
  */
29
- type TransitionResult<State, R> = State | Effect.Effect<State, never, R>;
34
+ type TransitionResult<State, R> = State | TransitionReply<State> | Effect.Effect<State | TransitionReply<State>, never, R>;
30
35
  /**
31
36
  * Internal event tags used for lifecycle effect contexts.
32
37
  * Prefixed with $ to distinguish from user events.
@@ -57,4 +62,4 @@ declare const isEffect: (value: unknown) => value is Effect.Effect<unknown, unkn
57
62
  */
58
63
  declare const stubSystem: ActorSystem;
59
64
  //#endregion
60
- export { ArgsOf, INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, InstanceOf, TagOf, TaggedConstructor, TransitionResult, getTag, isEffect, stubSystem };
65
+ export { ArgsOf, INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, InstanceOf, TagOf, TaggedConstructor, TransitionReply, TransitionResult, getTag, isEffect, stubSystem };
@@ -1,6 +1,5 @@
1
1
  import { Effect, Stream } from "effect";
2
-
3
- //#region src-v3/internal/utils.ts
2
+ //#region src/internal/utils.ts
4
3
  /**
5
4
  * Internal event tags used for lifecycle effect contexts.
6
5
  * Prefixed with $ to distinguish from user events.
@@ -46,6 +45,5 @@ const stubSystem = {
46
45
  restoreMany: () => Effect.die("restoreMany not supported in stub system"),
47
46
  restoreAll: () => Effect.die("restoreAll not supported in stub system")
48
47
  };
49
-
50
48
  //#endregion
51
- export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isEffect, stubSystem };
49
+ export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isEffect, stubSystem };