effect-machine 0.7.2 → 0.9.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 (51) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +6 -11
  2. package/dist/actor.d.ts +23 -3
  3. package/dist/actor.js +46 -15
  4. package/dist/cluster/entity-machine.d.ts +0 -1
  5. package/dist/cluster/entity-machine.js +3 -5
  6. package/dist/cluster/index.js +1 -2
  7. package/dist/cluster/to-entity.js +1 -3
  8. package/dist/errors.js +1 -3
  9. package/dist/index.d.ts +4 -4
  10. package/dist/index.js +2 -3
  11. package/dist/inspection.d.ts +31 -5
  12. package/dist/inspection.js +97 -5
  13. package/dist/internal/inspection.js +8 -7
  14. package/dist/internal/transition.d.ts +4 -4
  15. package/dist/internal/transition.js +11 -10
  16. package/dist/internal/utils.js +1 -3
  17. package/dist/machine.d.ts +20 -7
  18. package/dist/machine.js +64 -32
  19. package/dist/persistence/adapter.js +1 -3
  20. package/dist/persistence/adapters/in-memory.js +1 -3
  21. package/dist/persistence/index.js +1 -2
  22. package/dist/persistence/persistent-actor.js +11 -10
  23. package/dist/persistence/persistent-machine.js +1 -3
  24. package/dist/schema.js +6 -4
  25. package/dist/slot.d.ts +1 -0
  26. package/dist/slot.js +1 -3
  27. package/dist/testing.js +3 -5
  28. package/dist-v3/_virtual/_rolldown/runtime.js +6 -11
  29. package/dist-v3/actor.js +1 -3
  30. package/dist-v3/cluster/entity-machine.d.ts +0 -1
  31. package/dist-v3/cluster/entity-machine.js +1 -3
  32. package/dist-v3/cluster/index.js +1 -2
  33. package/dist-v3/cluster/to-entity.js +1 -3
  34. package/dist-v3/errors.js +1 -3
  35. package/dist-v3/index.d.ts +0 -1
  36. package/dist-v3/index.js +1 -2
  37. package/dist-v3/inspection.js +1 -3
  38. package/dist-v3/internal/inspection.js +1 -3
  39. package/dist-v3/internal/transition.js +1 -3
  40. package/dist-v3/internal/utils.js +1 -3
  41. package/dist-v3/machine.d.ts +0 -1
  42. package/dist-v3/machine.js +2 -31
  43. package/dist-v3/persistence/adapter.js +1 -3
  44. package/dist-v3/persistence/adapters/in-memory.js +1 -3
  45. package/dist-v3/persistence/index.js +1 -2
  46. package/dist-v3/persistence/persistent-actor.js +1 -3
  47. package/dist-v3/persistence/persistent-machine.js +1 -3
  48. package/dist-v3/schema.js +1 -3
  49. package/dist-v3/slot.js +1 -3
  50. package/dist-v3/testing.js +1 -3
  51. package/package.json +13 -13
@@ -2,17 +2,12 @@
2
2
  var __defProp = Object.defineProperty;
3
3
  var __exportAll = (all, no_symbols) => {
4
4
  let target = {};
5
- for (var name in all) {
6
- __defProp(target, name, {
7
- get: all[name],
8
- enumerable: true
9
- });
10
- }
11
- if (!no_symbols) {
12
- __defProp(target, Symbol.toStringTag, { value: "Module" });
13
- }
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
10
  return target;
15
11
  };
16
-
17
12
  //#endregion
18
- export { __exportAll };
13
+ export { __exportAll };
package/dist/actor.d.ts CHANGED
@@ -5,10 +5,17 @@ import { ProcessEventError, ProcessEventHooks, ProcessEventResult, processEventC
5
5
  import { PersistentActorRef } from "./persistence/persistent-actor.js";
6
6
  import { ActorMetadata, PersistenceAdapterTag, PersistenceError, RestoreResult, VersionConflictError } from "./persistence/adapter.js";
7
7
  import { BuiltMachine, Machine, MachineRef } from "./machine.js";
8
- import { Effect, Layer, Option, Queue, Ref, Scope, ServiceMap, Stream, SubscriptionRef } from "effect";
8
+ import { Deferred, Effect, Layer, Option, Queue, Ref, Scope, ServiceMap, Stream, SubscriptionRef } from "effect";
9
9
  import * as effect_Tracer0 from "effect/Tracer";
10
10
 
11
11
  //#region src/actor.d.ts
12
+ /** Queued event with optional reply channel */
13
+ interface QueuedEvent<E> {
14
+ readonly event: E;
15
+ readonly reply?: Deferred.Deferred<ProcessEventResult<{
16
+ readonly _tag: string;
17
+ }>>;
18
+ }
12
19
  /**
13
20
  * Reference to a running actor.
14
21
  */
@@ -96,6 +103,19 @@ interface ActorRef<State extends {
96
103
  * (e.g. framework hooks, event handlers).
97
104
  */
98
105
  readonly sendSync: (event: Event) => void;
106
+ /**
107
+ * Send event and wait for the transition result (synchronous processing).
108
+ * The event is processed through the queue (preserving serialization)
109
+ * but the caller gets back the ProcessEventResult.
110
+ *
111
+ * OTP gen_server:call equivalent — use when you need to know what happened.
112
+ */
113
+ readonly dispatch: (event: Event) => Effect.Effect<ProcessEventResult<State>>;
114
+ /**
115
+ * Promise-based dispatch — send event and get back the transition result.
116
+ * Use at non-Effect boundaries (React event handlers, framework hooks, tests).
117
+ */
118
+ readonly dispatchPromise: (event: Event) => Promise<ProcessEventResult<State>>;
99
119
  /**
100
120
  * Subscribe to state changes (sync callback)
101
121
  * Returns unsubscribe function
@@ -275,7 +295,7 @@ declare const buildActorRefCore: <S extends {
275
295
  readonly _tag: string;
276
296
  }, E extends {
277
297
  readonly _tag: string;
278
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, any, any, GD, EFD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueue: Queue.Queue<E>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>) => ActorRef<S, E>;
298
+ }, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, any, any, GD, EFD>, stateRef: SubscriptionRef.SubscriptionRef<S>, eventQueue: Queue.Queue<QueuedEvent<E>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>) => ActorRef<S, E>;
279
299
  /**
280
300
  * Create and start an actor for a machine
281
301
  */
@@ -289,4 +309,4 @@ declare const createActor: <S extends {
289
309
  */
290
310
  declare const Default: Layer.Layer<ActorSystem, never, never>;
291
311
  //#endregion
292
- export { ActorRef, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, SystemEvent, SystemEventListener, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
312
+ export { ActorRef, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
package/dist/actor.js CHANGED
@@ -2,12 +2,11 @@ import { Inspector } from "./inspection.js";
2
2
  import { INTERNAL_INIT_EVENT } from "./internal/utils.js";
3
3
  import { DuplicateActorError } from "./errors.js";
4
4
  import { isPersistentMachine } from "./persistence/persistent-machine.js";
5
- import { processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
6
5
  import { emitWithTimestamp } from "./internal/inspection.js";
6
+ import { processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
7
7
  import { PersistenceAdapterTag, PersistenceError } from "./persistence/adapter.js";
8
8
  import { createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
9
9
  import { Cause, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Scope, Semaphore, ServiceMap, Stream, SubscriptionRef } from "effect";
10
-
11
10
  //#region src/actor.ts
12
11
  /**
13
12
  * Actor system: spawning, lifecycle, and event processing.
@@ -35,7 +34,25 @@ const notifyListeners = (listeners, state) => {
35
34
  const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop, system, childrenMap) => {
36
35
  const send = Effect.fn("effect-machine.actor.send")(function* (event) {
37
36
  if (yield* Ref.get(stoppedRef)) return;
38
- yield* Queue.offer(eventQueue, event);
37
+ yield* Queue.offer(eventQueue, { event });
38
+ });
39
+ const dispatch = Effect.fn("effect-machine.actor.dispatch")(function* (event) {
40
+ if (yield* Ref.get(stoppedRef)) {
41
+ const currentState = yield* SubscriptionRef.get(stateRef);
42
+ return {
43
+ newState: currentState,
44
+ previousState: currentState,
45
+ transitioned: false,
46
+ lifecycleRan: false,
47
+ isFinal: machine.finalStates.has(currentState._tag)
48
+ };
49
+ }
50
+ const reply = yield* Deferred.make();
51
+ yield* Queue.offer(eventQueue, {
52
+ event,
53
+ reply
54
+ });
55
+ return yield* Deferred.await(reply);
39
56
  });
40
57
  const snapshot = SubscriptionRef.get(stateRef).pipe(Effect.withSpan("effect-machine.actor.snapshot"));
41
58
  const matches = Effect.fn("effect-machine.actor.matches")(function* (tag) {
@@ -87,8 +104,10 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
87
104
  awaitFinal,
88
105
  sendAndWait,
89
106
  sendSync: (event) => {
90
- if (!Effect.runSync(Ref.get(stoppedRef))) Effect.runSync(Queue.offer(eventQueue, event));
107
+ if (!Effect.runSync(Ref.get(stoppedRef))) Effect.runSync(Queue.offer(eventQueue, { event }));
91
108
  },
109
+ dispatch,
110
+ dispatchPromise: (event) => Effect.runPromise(dispatch(event)),
92
111
  subscribe: (fn) => {
93
112
  listeners.add(fn);
94
113
  return () => {
@@ -120,7 +139,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
120
139
  const self = {
121
140
  send: Effect.fn("effect-machine.actor.self.send")(function* (event) {
122
141
  if (yield* Ref.get(stoppedRef)) return;
123
- yield* Queue.offer(eventQueue, event);
142
+ yield* Queue.offer(eventQueue, { event });
124
143
  }),
125
144
  spawn: (childId, childMachine) => Effect.gen(function* () {
126
145
  const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
@@ -144,6 +163,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
144
163
  const backgroundFibers = [];
145
164
  const initEvent = { _tag: INTERNAL_INIT_EVENT };
146
165
  const ctx = {
166
+ actorId: id,
147
167
  state: machine.initial,
148
168
  event: initEvent,
149
169
  self,
@@ -152,6 +172,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
152
172
  const { effects: effectSlots } = machine._slots;
153
173
  for (const bg of machine.backgroundEffects) {
154
174
  const fiber = yield* Effect.forkDetach(bg.handler({
175
+ actorId: id,
155
176
  state: machine.initial,
156
177
  event: initEvent,
157
178
  self,
@@ -196,13 +217,15 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
196
217
  */
197
218
  const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, actorId, inspector, system) {
198
219
  while (true) {
199
- const event = yield* Queue.take(eventQueue);
220
+ const { event, reply } = yield* Queue.take(eventQueue);
200
221
  const currentState = yield* SubscriptionRef.get(stateRef);
201
- if (yield* Effect.withSpan("effect-machine.event.process", { attributes: {
222
+ const { shouldStop, result } = yield* Effect.withSpan("effect-machine.event.process", { attributes: {
202
223
  "effect_machine.actor.id": actorId,
203
224
  "effect_machine.state.current": currentState._tag,
204
225
  "effect_machine.event.type": event._tag
205
- } })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system))) {
226
+ } })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system));
227
+ if (reply !== void 0) yield* Deferred.succeed(reply, result);
228
+ if (shouldStop) {
206
229
  yield* Ref.set(stoppedRef, true);
207
230
  yield* Scope.close(stateScopeRef.current, Exit.void);
208
231
  yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
@@ -222,7 +245,7 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
222
245
  event,
223
246
  timestamp
224
247
  }));
225
- const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, inspector === void 0 ? void 0 : {
248
+ const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, actorId, inspector === void 0 ? void 0 : {
226
249
  onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
227
250
  type: "@machine.effect",
228
251
  actorId,
@@ -250,7 +273,10 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
250
273
  });
251
274
  if (!result.transitioned) {
252
275
  yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", false);
253
- return false;
276
+ return {
277
+ shouldStop: false,
278
+ result
279
+ };
254
280
  }
255
281
  yield* Effect.annotateCurrentSpan("effect_machine.transition.matched", true);
256
282
  yield* SubscriptionRef.set(stateRef, result.newState);
@@ -265,10 +291,16 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
265
291
  finalState: result.newState,
266
292
  timestamp
267
293
  }));
268
- return true;
294
+ return {
295
+ shouldStop: true,
296
+ result
297
+ };
269
298
  }
270
299
  }
271
- return false;
300
+ return {
301
+ shouldStop: false,
302
+ result
303
+ };
272
304
  });
273
305
  /**
274
306
  * Run spawn effects with actor-specific inspection and tracing.
@@ -283,7 +315,7 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffec
283
315
  state,
284
316
  timestamp
285
317
  }));
286
- yield* runSpawnEffects(machine, state, event, self, stateScope, system, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
318
+ yield* runSpawnEffects(machine, state, event, self, stateScope, system, actorId, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
287
319
  type: "@machine.error",
288
320
  actorId,
289
321
  phase: info.phase,
@@ -452,6 +484,5 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
452
484
  * Default ActorSystem layer
453
485
  */
454
486
  const Default = Layer.effect(ActorSystem, make());
455
-
456
487
  //#endregion
457
- export { ActorSystem, Default, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
488
+ export { ActorSystem, Default, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
@@ -1,7 +1,6 @@
1
1
  import { EffectsDef, GuardsDef } from "../slot.js";
2
2
  import { ProcessEventHooks } from "../internal/transition.js";
3
3
  import { Machine } from "../machine.js";
4
- import "../actor.js";
5
4
  import { Layer } from "effect";
6
5
  import { Entity } from "effect/unstable/cluster";
7
6
  import { Rpc } from "effect/unstable/rpc";
@@ -2,7 +2,6 @@ import { processEventCore, runSpawnEffects } from "../internal/transition.js";
2
2
  import { ActorSystem } from "../actor.js";
3
3
  import { Effect, Option, Queue, Ref, Scope } from "effect";
4
4
  import { Entity } from "effect/unstable/cluster";
5
-
6
5
  //#region src/cluster/entity-machine.ts
7
6
  /**
8
7
  * EntityMachine adapter - wires a machine to a cluster Entity layer.
@@ -14,7 +13,7 @@ import { Entity } from "effect/unstable/cluster";
14
13
  * Returns the new state after processing.
15
14
  */
16
15
  const processEvent = Effect.fn("effect-machine.cluster.processEvent")(function* (machine, stateRef, event, self, stateScopeRef, system, hooks) {
17
- const result = yield* processEventCore(machine, yield* Ref.get(stateRef), event, self, stateScopeRef, system, hooks);
16
+ const result = yield* processEventCore(machine, yield* Ref.get(stateRef), event, self, stateScopeRef, system, "*", hooks);
18
17
  if (result.transitioned) yield* Ref.set(stateRef, result.newState);
19
18
  return result.newState;
20
19
  });
@@ -63,7 +62,7 @@ const EntityMachine = { layer: (entity, machine, options) => {
63
62
  };
64
63
  const stateRef = yield* Ref.make(initialState);
65
64
  const stateScopeRef = { current: yield* Scope.make() };
66
- yield* runSpawnEffects(machine, initialState, { _tag: "$init" }, self, stateScopeRef.current, system, options?.hooks?.onError);
65
+ yield* runSpawnEffects(machine, initialState, { _tag: "$init" }, self, stateScopeRef.current, system, entityId, options?.hooks?.onError);
67
66
  const runInternalEvent = Effect.fn("effect-machine.cluster.internalEvent")(function* () {
68
67
  yield* processEvent(machine, stateRef, yield* Queue.take(internalQueue), self, stateScopeRef, system, options?.hooks);
69
68
  });
@@ -75,6 +74,5 @@ const EntityMachine = { layer: (entity, machine, options) => {
75
74
  });
76
75
  return entity.toLayer(layer());
77
76
  } };
78
-
79
77
  //#endregion
80
- export { EntityMachine };
78
+ export { EntityMachine };
@@ -1,4 +1,3 @@
1
1
  import { EntityMachine } from "./entity-machine.js";
2
2
  import { toEntity } from "./to-entity.js";
3
-
4
- export { EntityMachine, toEntity };
3
+ export { EntityMachine, toEntity };
@@ -1,7 +1,6 @@
1
1
  import { MissingSchemaError } from "../errors.js";
2
2
  import { Entity } from "effect/unstable/cluster";
3
3
  import { Rpc } from "effect/unstable/rpc";
4
-
5
4
  //#region src/cluster/to-entity.ts
6
5
  /**
7
6
  * Generate Entity definition from a machine.
@@ -48,6 +47,5 @@ const toEntity = (machine, options) => {
48
47
  success: stateSchema
49
48
  }), Rpc.make("GetState", { success: stateSchema })]);
50
49
  };
51
-
52
50
  //#endregion
53
- export { toEntity };
51
+ export { toEntity };
package/dist/errors.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Schema } from "effect";
2
-
3
2
  //#region src/errors.ts
4
3
  /**
5
4
  * Typed error classes for effect-machine.
@@ -33,6 +32,5 @@ var ProvisionValidationError = class extends Schema.TaggedErrorClass()("Provisio
33
32
  }) {};
34
33
  /** Assertion failed in testing utilities */
35
34
  var AssertionError = class extends Schema.TaggedErrorClass()("AssertionError", { message: Schema.String }) {};
36
-
37
35
  //#endregion
38
- export { AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError };
36
+ export { AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError };
package/dist/index.d.ts CHANGED
@@ -2,12 +2,12 @@ import { EffectHandlers, EffectSlot, EffectSlots, EffectsDef, EffectsSchema, Gua
2
2
  import { Event, MachineEventSchema, MachineStateSchema, State } from "./schema.js";
3
3
  import { PersistenceConfig, PersistentMachine, isPersistentMachine } from "./persistence/persistent-machine.js";
4
4
  import { AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError } from "./errors.js";
5
+ import { ProcessEventResult } from "./internal/transition.js";
5
6
  import { PersistentActorRef, createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
6
7
  import { ActorMetadata, PersistedEvent, PersistenceAdapter, PersistenceAdapterTag, PersistenceError, RestoreFailure, RestoreResult, Snapshot, VersionConflictError } from "./persistence/adapter.js";
7
8
  import { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter } from "./persistence/adapters/in-memory.js";
8
- import "./persistence/index.js";
9
- import { BackgroundEffect, BuiltMachine, HandlerContext, Machine, MachineRef, MakeConfig, PersistOptions, ProvideHandlers, SpawnEffect, StateHandlerContext, Transition, machine_d_exports } from "./machine.js";
9
+ import { BackgroundEffect, BuiltMachine, HandlerContext, Machine, MachineRef, MakeConfig, PersistOptions, ProvideHandlers, SpawnEffect, StateHandlerContext, TaskOptions, Transition, machine_d_exports } from "./machine.js";
10
10
  import { ActorRef, ActorSystem, Default, SystemEvent, SystemEventListener } from "./actor.js";
11
11
  import { SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
12
- import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, SpawnEvent, StopEvent, TransitionEvent, collectingInspector, consoleInspector, makeInspector } from "./inspection.js";
13
- export { type ActorMetadata, type ActorRef, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type BuiltMachine, 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, InMemoryPersistenceAdapter, type InspectionEvent, type Inspector, 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, type PersistOptions, type PersistedEvent, type PersistenceAdapter, PersistenceAdapterTag, type PersistenceConfig, PersistenceError, type PersistentActorRef, type PersistentMachine, type ProvideHandlers, ProvisionValidationError, type RestoreFailure, type RestoreResult, type SimulationResult, Slot, type EffectHandlers as SlotEffectHandlers, type EffectSlot as SlotEffectSlot, SlotProvisionError, type Snapshot, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, type SystemEvent, type SystemEventListener, type TestHarness, type TestHarnessOptions, type Transition, type TransitionEvent, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, restorePersistentActor, simulate };
12
+ import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
13
+ export { type ActorMetadata, type ActorRef, type ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type BackgroundEffect, type BuiltMachine, 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, InMemoryPersistenceAdapter, 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, type PersistOptions, type PersistedEvent, type PersistenceAdapter, PersistenceAdapterTag, type PersistenceConfig, PersistenceError, type PersistentActorRef, type PersistentMachine, type ProcessEventResult, type ProvideHandlers, ProvisionValidationError, type RestoreFailure, type RestoreResult, type SimulationResult, Slot, type EffectHandlers as SlotEffectHandlers, type EffectSlot as SlotEffectSlot, SlotProvisionError, type Snapshot, type SpawnEffect, type SpawnEvent, State, type StateHandlerContext, type StopEvent, type SystemEvent, type SystemEventListener, type TaskEvent, type TaskOptions, type TestHarness, type TestHarnessOptions, type TracingInspectorOptions, type Transition, type TransitionEvent, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, makeInspectorEffect, restorePersistentActor, simulate, tracingInspector };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Inspector, collectingInspector, consoleInspector, makeInspector } from "./inspection.js";
1
+ import { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
2
2
  import { AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError } from "./errors.js";
3
3
  import { isPersistentMachine } from "./persistence/persistent-machine.js";
4
4
  import { Slot } from "./slot.js";
@@ -10,5 +10,4 @@ import { Event, State } from "./schema.js";
10
10
  import { assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
11
11
  import { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter } from "./persistence/adapters/in-memory.js";
12
12
  import "./persistence/index.js";
13
-
14
- export { Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, InMemoryPersistenceAdapter, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, PersistenceAdapterTag, PersistenceError, ProvisionValidationError, Slot, SlotProvisionError, State, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, restorePersistentActor, simulate };
13
+ export { Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, InMemoryPersistenceAdapter, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, MissingSchemaError, PersistenceAdapterTag, PersistenceError, ProvisionValidationError, Slot, SlotProvisionError, State, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, makeInspectorEffect, restorePersistentActor, simulate, tracingInspector };
@@ -1,4 +1,4 @@
1
- import { Schema, ServiceMap } from "effect";
1
+ import { Effect, Schema, ServiceMap } from "effect";
2
2
 
3
3
  //#region src/inspection.d.ts
4
4
  /**
@@ -45,6 +45,15 @@ interface EffectEvent<S> {
45
45
  readonly state: S;
46
46
  readonly timestamp: number;
47
47
  }
48
+ interface TaskEvent<S> {
49
+ readonly type: "@machine.task";
50
+ readonly actorId: string;
51
+ readonly state: S;
52
+ readonly taskName?: string;
53
+ readonly phase: "start" | "success" | "failure" | "interrupt";
54
+ readonly error?: string;
55
+ readonly timestamp: number;
56
+ }
48
57
  /**
49
58
  * Event emitted when a transition handler or spawn effect fails with a defect
50
59
  */
@@ -69,7 +78,7 @@ interface StopEvent<S> {
69
78
  /**
70
79
  * Union of all inspection events
71
80
  */
72
- type InspectionEvent<S, E> = SpawnEvent<S> | EventReceivedEvent<S, E> | TransitionEvent<S, E> | EffectEvent<S> | ErrorEvent<S, E> | StopEvent<S>;
81
+ type InspectionEvent<S, E> = SpawnEvent<S> | EventReceivedEvent<S, E> | TransitionEvent<S, E> | EffectEvent<S> | TaskEvent<S> | ErrorEvent<S, E> | StopEvent<S>;
73
82
  /**
74
83
  * Convenience alias for untyped inspection events.
75
84
  * Useful for general-purpose inspectors that don't need specific state/event types.
@@ -84,8 +93,9 @@ type AnyInspectionEvent = InspectionEvent<{
84
93
  /**
85
94
  * Inspector interface for observing machine behavior
86
95
  */
96
+ type InspectorHandler<S, E> = (event: InspectionEvent<S, E>) => void | Effect.Effect<void, never, never>;
87
97
  interface Inspector<S, E> {
88
- readonly onInspect: (event: InspectionEvent<S, E>) => void;
98
+ readonly onInspect: InspectorHandler<S, E>;
89
99
  }
90
100
  /**
91
101
  * Inspector service tag - optional service for machine introspection
@@ -104,7 +114,23 @@ declare const makeInspector: <S = {
104
114
  readonly _tag: string;
105
115
  }, E = {
106
116
  readonly _tag: string;
107
- }>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => void) => Inspector<ResolveType<S>, ResolveType<E>>;
117
+ }>(onInspect: InspectorHandler<ResolveType<S>, ResolveType<E>>) => Inspector<ResolveType<S>, ResolveType<E>>;
118
+ declare const makeInspectorEffect: <S = {
119
+ readonly _tag: string;
120
+ }, E = {
121
+ readonly _tag: string;
122
+ }>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => Effect.Effect<void, never, never>) => Inspector<ResolveType<S>, ResolveType<E>>;
123
+ declare const combineInspectors: <S, E>(...inspectors: ReadonlyArray<Inspector<S, E>>) => Inspector<S, E>;
124
+ interface TracingInspectorOptions<S, E> {
125
+ readonly spanName?: string | ((event: InspectionEvent<S, E>) => string);
126
+ readonly attributes?: (event: InspectionEvent<S, E>) => Readonly<Record<string, string | number | boolean>>;
127
+ readonly eventName?: (event: InspectionEvent<S, E>) => string;
128
+ }
129
+ declare const tracingInspector: <S extends {
130
+ readonly _tag: string;
131
+ }, E extends {
132
+ readonly _tag: string;
133
+ }>(options?: TracingInspectorOptions<S, E>) => Inspector<S, E>;
108
134
  /**
109
135
  * Console inspector that logs events in a readable format
110
136
  */
@@ -122,4 +148,4 @@ declare const collectingInspector: <S extends {
122
148
  readonly _tag: string;
123
149
  }>(events: InspectionEvent<S, E>[]) => Inspector<S, E>;
124
150
  //#endregion
125
- export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, SpawnEvent, StopEvent, TransitionEvent, collectingInspector, consoleInspector, makeInspector };
151
+ export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
@@ -1,5 +1,4 @@
1
- import { ServiceMap } from "effect";
2
-
1
+ import { Effect, Option, ServiceMap } from "effect";
3
2
  //#region src/inspection.ts
4
3
  /**
5
4
  * Inspector service tag - optional service for machine introspection
@@ -15,6 +14,95 @@ const Inspector = ServiceMap.Service("@effect/machine/Inspector");
15
14
  * - `makeInspector<typeof MyState, typeof MyEvent>(cb)` — schema constructors (auto-extracts `.Type`)
16
15
  */
17
16
  const makeInspector = (onInspect) => ({ onInspect });
17
+ const makeInspectorEffect = (onInspect) => ({ onInspect });
18
+ const inspectionEffect = (inspector, event) => {
19
+ const result = inspector.onInspect(event);
20
+ return Effect.isEffect(result) ? result : Effect.void;
21
+ };
22
+ const combineInspectors = (...inspectors) => ({ onInspect: (event) => Effect.forEach(inspectors, (inspector) => inspectionEffect(inspector, event).pipe(Effect.catchCause(() => Effect.void)), {
23
+ concurrency: "unbounded",
24
+ discard: true
25
+ }) });
26
+ const inspectionSpanName = (event) => {
27
+ switch (event.type) {
28
+ case "@machine.spawn": return `Machine.inspect ${event.initialState._tag}`;
29
+ case "@machine.event": return `Machine.inspect ${event.event._tag}`;
30
+ case "@machine.transition": return `Machine.inspect ${event.fromState._tag}->${event.toState._tag}`;
31
+ case "@machine.effect": return `Machine.inspect ${event.effectType}`;
32
+ case "@machine.task": return `Machine.inspect task:${event.phase}`;
33
+ case "@machine.error": return `Machine.inspect ${event.phase}`;
34
+ case "@machine.stop": return `Machine.inspect ${event.finalState._tag}`;
35
+ }
36
+ };
37
+ const inspectionTraceName = (event) => {
38
+ switch (event.type) {
39
+ case "@machine.spawn": return `machine.spawn ${event.initialState._tag}`;
40
+ case "@machine.event": return `machine.event ${event.event._tag}`;
41
+ case "@machine.transition": return `machine.transition ${event.fromState._tag}->${event.toState._tag}`;
42
+ case "@machine.effect": return `machine.effect ${event.effectType}`;
43
+ case "@machine.task": return `machine.task ${event.phase}${event.taskName === void 0 ? "" : ` ${event.taskName}`}`;
44
+ case "@machine.error": return `machine.error ${event.phase}`;
45
+ case "@machine.stop": return `machine.stop ${event.finalState._tag}`;
46
+ }
47
+ };
48
+ const inspectionAttributes = (event) => {
49
+ const shared = {
50
+ "machine.actor.id": event.actorId,
51
+ "machine.inspection.type": event.type
52
+ };
53
+ switch (event.type) {
54
+ case "@machine.spawn": return {
55
+ ...shared,
56
+ "machine.state.initial": event.initialState._tag
57
+ };
58
+ case "@machine.event": return {
59
+ ...shared,
60
+ "machine.state.current": event.state._tag,
61
+ "machine.event.tag": event.event._tag
62
+ };
63
+ case "@machine.transition": return {
64
+ ...shared,
65
+ "machine.state.from": event.fromState._tag,
66
+ "machine.state.to": event.toState._tag,
67
+ "machine.event.tag": event.event._tag
68
+ };
69
+ case "@machine.effect": return {
70
+ ...shared,
71
+ "machine.state.current": event.state._tag,
72
+ "machine.effect.kind": event.effectType
73
+ };
74
+ case "@machine.task": return {
75
+ ...shared,
76
+ "machine.state.current": event.state._tag,
77
+ "machine.task.phase": event.phase,
78
+ ...event.taskName === void 0 ? {} : { "machine.task.name": event.taskName }
79
+ };
80
+ case "@machine.error": return {
81
+ ...shared,
82
+ "machine.phase": event.phase,
83
+ "machine.state.current": event.state._tag
84
+ };
85
+ case "@machine.stop": return {
86
+ ...shared,
87
+ "machine.state.final": event.finalState._tag
88
+ };
89
+ }
90
+ };
91
+ const tracingInspector = (options) => ({ onInspect: (event) => {
92
+ const spanName = typeof options?.spanName === "function" ? options.spanName(event) : options?.spanName;
93
+ const traceName = options?.eventName?.(event) ?? inspectionTraceName(event);
94
+ const attributes = {
95
+ ...inspectionAttributes(event),
96
+ ...options?.attributes?.(event) ?? {}
97
+ };
98
+ return Effect.gen(function* () {
99
+ const currentSpan = yield* Effect.option(Effect.currentSpan);
100
+ if (Option.isSome(currentSpan)) currentSpan.value.event(traceName, BigInt(event.timestamp) * 1000000n, {
101
+ actorId: event.actorId,
102
+ inspectionType: event.type
103
+ });
104
+ }).pipe(Effect.withSpan(spanName ?? inspectionSpanName(event), { attributes }));
105
+ } });
18
106
  /**
19
107
  * Console inspector that logs events in a readable format
20
108
  */
@@ -33,6 +121,9 @@ const consoleInspector = () => makeInspector((event) => {
33
121
  case "@machine.effect":
34
122
  console.log(prefix, event.effectType, "effect in", event.state._tag);
35
123
  break;
124
+ case "@machine.task":
125
+ console.log(prefix, "task", event.phase, event.taskName ?? "<unnamed>", "in", event.state._tag);
126
+ break;
36
127
  case "@machine.error":
37
128
  console.log(prefix, "error in", event.phase, event.state._tag, "-", event.error);
38
129
  break;
@@ -44,7 +135,8 @@ const consoleInspector = () => makeInspector((event) => {
44
135
  /**
45
136
  * Collecting inspector that stores events in an array for testing
46
137
  */
47
- const collectingInspector = (events) => ({ onInspect: (event) => events.push(event) });
48
-
138
+ const collectingInspector = (events) => ({ onInspect: (event) => {
139
+ events.push(event);
140
+ } });
49
141
  //#endregion
50
- export { Inspector, collectingInspector, consoleInspector, makeInspector };
142
+ export { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
@@ -1,5 +1,4 @@
1
1
  import { Clock, Effect } from "effect";
2
-
3
2
  //#region src/internal/inspection.ts
4
3
  /**
5
4
  * Emit an inspection event with timestamp from Clock.
@@ -7,13 +6,15 @@ import { Clock, Effect } from "effect";
7
6
  */
8
7
  const emitWithTimestamp = Effect.fn("effect-machine.emitWithTimestamp")(function* (inspector, makeEvent) {
9
8
  if (inspector === void 0) return;
10
- const timestamp = yield* Clock.currentTimeMillis;
11
- yield* Effect.sync(() => {
9
+ const event = makeEvent(yield* Clock.currentTimeMillis);
10
+ const result = yield* Effect.sync(() => {
12
11
  try {
13
- inspector.onInspect(makeEvent(timestamp));
14
- } catch {}
12
+ return inspector.onInspect(event);
13
+ } catch {
14
+ return;
15
+ }
15
16
  });
17
+ if (Effect.isEffect(result)) yield* result.pipe(Effect.catchCause(() => Effect.void));
16
18
  });
17
-
18
19
  //#endregion
19
- export { emitWithTimestamp };
20
+ export { emitWithTimestamp };
@@ -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) => Effect.Effect<S, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
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<S, never, Exclude<R, MachineContext<S, E, MachineRef<E>>>>;
33
33
  /**
34
34
  * Execute a transition for a given state and event.
35
35
  * Handles transition resolution, handler invocation, and guard/effect slot creation.
@@ -45,7 +45,7 @@ declare const executeTransition: <S extends {
45
45
  readonly _tag: string;
46
46
  }, E extends {
47
47
  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<{
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, actorId: string) => Effect.Effect<{
49
49
  newState: S;
50
50
  transitioned: boolean;
51
51
  reenter: boolean;
@@ -103,7 +103,7 @@ declare const processEventCore: <S extends {
103
103
  readonly _tag: string;
104
104
  }, 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
105
  current: Scope.Closeable;
106
- }, system: ActorSystem, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
106
+ }, system: ActorSystem, actorId: string, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
107
107
  newState: S;
108
108
  previousState: S;
109
109
  transitioned: boolean;
@@ -119,7 +119,7 @@ declare const runSpawnEffects: <S extends {
119
119
  readonly _tag: string;
120
120
  }, E extends {
121
121
  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.Closeable, system: ActorSystem, onError?: ((info: ProcessEventError<S, E>) => Effect.Effect<void>) | undefined) => Effect.Effect<void, never, Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>>;
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.Closeable, 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
123
  /**
124
124
  * Resolve which transition should fire for a given state and event.
125
125
  * Uses indexed O(1) lookup. First matching transition wins.