effect-machine 0.19.0 → 0.21.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.
package/dist/atom.js CHANGED
@@ -1,5 +1,8 @@
1
+ import { Match } from "effect";
1
2
  import { dual } from "effect/Function";
3
+ import * as Option$1 from "effect/Option";
2
4
  import * as Atom from "effect/unstable/reactivity/Atom";
5
+ import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
3
6
  //#region src/atom.ts
4
7
  /**
5
8
  * Effect Atom integration for actors.
@@ -7,6 +10,17 @@ import * as Atom from "effect/unstable/reactivity/Atom";
7
10
  * The adapter keeps the actor as the state owner. Atom registries observe the
8
11
  * actor's SubscriptionRef and write events through its synchronous boundary.
9
12
  */
13
+ /** Observe the current ActorRef for one ActorSystem key. */
14
+ const fromSystem = dual(2, (system, key) => Atom.make(system.watch(key), { initialValue: Option$1.none() }));
15
+ /** Suspend while an ActorSystem key is absent and follow later generations. */
16
+ const acquire = dual(2, (system, key) => Atom.map(fromSystem(system, key), (result) => Match.value(result).pipe(Match.tagsExhaustive({
17
+ Initial: (initial) => AsyncResult.initial(initial.waiting),
18
+ Failure: (failure) => AsyncResult.failure(failure.cause, { waiting: failure.waiting }),
19
+ Success: (success) => Option$1.match(success.value, {
20
+ onNone: () => AsyncResult.initial(true),
21
+ onSome: (actor) => AsyncResult.success(actor, success)
22
+ })
23
+ }))));
10
24
  /**
11
25
  * Make a writable Atom from an actor.
12
26
  *
@@ -16,7 +30,7 @@ import * as Atom from "effect/unstable/reactivity/Atom";
16
30
  */
17
31
  const make = (actor) => {
18
32
  const state = Atom.subscriptionRef(actor.state);
19
- return Atom.writable((get) => get(state), (_ctx, event) => actor.sync.send(event));
33
+ return Atom.writable((get) => get(state), (_ctx, event) => actor.client.send(event));
20
34
  };
21
35
  /**
22
36
  * Select part of an actor state.
@@ -25,5 +39,22 @@ const make = (actor) => {
25
39
  * The equality function controls when Atom subscribers receive a new value.
26
40
  */
27
41
  const select = dual((args) => Atom.isAtom(args[0]), (self, selector, equals = Object.is) => Atom.withEquality(Atom.map(self, selector), equals));
42
+ /** Observe actor lifecycle without coupling it to domain state. */
43
+ const lifecycle = (actor) => Atom.subscriptionRef(actor.lifecycle);
44
+ /** Observe the latest accepted edge. The value remains after actor exit. */
45
+ const latestTransition = (actor) => Atom.subscriptionRef(actor.latestTransition);
46
+ /**
47
+ * Observe whether an event has an enabled transition.
48
+ *
49
+ * The Atom reevaluates after each actor state change. It supports pure and
50
+ * Effect predicates. Effect predicates use the context captured by the actor.
51
+ */
52
+ const can = dual(2, (actor, event) => {
53
+ const state = Atom.subscriptionRef(actor.state);
54
+ return Atom.make((get) => {
55
+ get(state);
56
+ return actor.can(event);
57
+ }).pipe(Atom.withEquality((value, next) => AsyncResult.isSuccess(value) && AsyncResult.isSuccess(next) && Object.is(value.value, next.value)));
58
+ });
28
59
  //#endregion
29
- export { make, select };
60
+ export { acquire, can, fromSystem, latestTransition, lifecycle, make, select };
@@ -7,7 +7,7 @@ import { Rpc } from "effect/unstable/rpc";
7
7
  /**
8
8
  * Options for EntityMachine.layer
9
9
  */
10
- interface EntityMachineOptions<S> {
10
+ interface EntityMachineBaseOptions<S> {
11
11
  /**
12
12
  * Initialize state from entity ID.
13
13
  * Called once when entity is first activated.
@@ -38,6 +38,12 @@ interface EntityMachineOptions<S> {
38
38
  */
39
39
  readonly persistence?: EntityPersistenceConfig;
40
40
  }
41
+ type EntityMachineOptions<S, Input = void> = EntityMachineBaseOptions<S> & ([Input] extends [void] ? {
42
+ readonly input?: never;
43
+ } : {
44
+ /** Map the entity ID to the machine input. */
45
+ readonly input: (entityId: string) => Input;
46
+ });
41
47
  /**
42
48
  * Create an Entity layer that wires a machine to handle RPC calls.
43
49
  *
@@ -59,7 +65,7 @@ declare const EntityMachine: {
59
65
  readonly _tag: string;
60
66
  }, E extends {
61
67
  readonly _tag: string;
62
- }, R, EntityType extends string, Rpcs extends Rpc.Any>(entity: Entity.Entity<EntityType, Rpcs>, machine: Machine<S, E, R, any, any>, options?: EntityMachineOptions<S>) => Layer.Layer<never, never, R>;
68
+ }, R, Input, Output, EntityType extends string, Rpcs extends Rpc.Any>(entity: Entity.Entity<EntityType, Rpcs>, machine: Machine<S, E, R, any, any, Input, Output>, ...optionsArgument: [Input] extends [void] ? [options?: EntityMachineOptions<S, Input>] : [options: EntityMachineOptions<S, Input>]) => Layer.Layer<never, never, R>;
63
69
  };
64
70
  //#endregion
65
- export { EntityMachine, EntityMachineOptions };
71
+ export { EntityMachine, EntityMachineBaseOptions, EntityMachineOptions };
@@ -35,7 +35,8 @@ import { Entity } from "effect/unstable/cluster";
35
35
  * })
36
36
  * ```
37
37
  */
38
- const EntityMachine = { layer: (entity, machine, options) => {
38
+ const EntityMachine = { layer: (entity, machine, ...optionsArgument) => {
39
+ const options = optionsArgument[0];
39
40
  const persistence = options?.persistence;
40
41
  const build = Effect.gen(function* () {
41
42
  const entityId = yield* Effect.serviceOption(Entity.CurrentAddress).pipe(Effect.map((opt) => {
@@ -43,23 +44,22 @@ const EntityMachine = { layer: (entity, machine, options) => {
43
44
  return "";
44
45
  }));
45
46
  const inspector = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
47
+ const machineInitial = machine._initial(options?.input?.(entityId));
46
48
  const existingSystem = yield* Effect.serviceOption(ActorSystem);
47
49
  let system;
48
50
  if (Option.isSome(existingSystem)) system = existingSystem.value;
49
51
  else system = yield* makeSystem();
50
- const persistCtx = yield* hydratePersistence(persistence, entity, entityId, machine, options?.initializeState);
52
+ const persistCtx = yield* hydratePersistence(persistence, entity, entityId, machine, machineInitial, options?.initializeState);
51
53
  let initialState = persistCtx.hydratedState;
52
54
  if (initialState === void 0 && options?.initializeState !== void 0) initialState = options.initializeState(entityId);
53
- let machineWithState = machine;
54
- if (initialState !== void 0) machineWithState = machine._withInitial(initialState);
55
55
  const versionRef = yield* Ref.make(persistCtx.initialVersion);
56
- const computedInitial = initialState ?? machine.initial;
56
+ const computedInitial = initialState ?? machineInitial;
57
57
  const stateRef = yield* SubscriptionRef.make(computedInitial);
58
58
  const stoppedRef = yield* Ref.make(false);
59
59
  const eventQueue = yield* Queue.unbounded();
60
60
  let hooks = void 0;
61
61
  if (inspector !== void 0) hooks = makeInspectionHooks(entityId, inspector);
62
- const runtime = yield* createRuntime(machineWithState, system, {
62
+ const runtime = yield* createRuntime(machine, system, {
63
63
  actorId: entityId,
64
64
  hooks,
65
65
  childIdPrefix: `${entityId}/`,
@@ -152,7 +152,7 @@ const noPersistence = {
152
152
  initialVersion: 0
153
153
  };
154
154
  /** Load snapshot/journal and compute hydrated state. */
155
- const hydratePersistence = (persistence, entityDef, entityId, machine, initializeState) => Effect.gen(function* () {
155
+ const hydratePersistence = (persistence, entityDef, entityId, machine, machineInitial, initializeState) => Effect.gen(function* () {
156
156
  if (persistence === void 0) return noPersistence;
157
157
  const adapter = yield* PersistenceAdapter;
158
158
  const key = {
@@ -164,7 +164,7 @@ const hydratePersistence = (persistence, entityDef, entityId, machine, initializ
164
164
  let baseState;
165
165
  if (Option.isSome(maybeSnapshot)) baseState = maybeSnapshot.value.state;
166
166
  else if (initializeState !== void 0) baseState = initializeState(entityId);
167
- else baseState = machine.initial;
167
+ else baseState = machineInitial;
168
168
  let snapshotVersion = 0;
169
169
  if (Option.isSome(maybeSnapshot)) snapshotVersion = maybeSnapshot.value.version;
170
170
  const events = yield* adapter.loadEvents(key, snapshotVersion);
@@ -2,5 +2,5 @@ import { EntityPersistenceConfig, PersistedEvent, PersistenceAdapter, Persistenc
2
2
  import { makeInMemoryPersistenceAdapter } from "./adapters/in-memory.js";
3
3
  import { EntityRpcs, ToEntityOptions, toEntity } from "./to-entity.js";
4
4
  import { EntityActorRef, makeEntityActorRef } from "./entity-actor-ref.js";
5
- import { EntityMachine, EntityMachineOptions } from "./entity-machine.js";
6
- export { type EntityActorRef, EntityMachine, type EntityMachineOptions, type EntityPersistenceConfig, type EntityRpcs, type PersistedEvent, PersistenceAdapter, type PersistenceAdapterService as PersistenceAdapterInterface, type PersistenceKey, type Snapshot, type ToEntityOptions, makeEntityActorRef, makeInMemoryPersistenceAdapter, toEntity };
5
+ import { EntityMachine, EntityMachineBaseOptions, EntityMachineOptions } from "./entity-machine.js";
6
+ export { type EntityActorRef, EntityMachine, type EntityMachineBaseOptions, type EntityMachineOptions, type EntityPersistenceConfig, type EntityRpcs, type PersistedEvent, PersistenceAdapter, type PersistenceAdapterService as PersistenceAdapterInterface, type PersistenceKey, type Snapshot, type ToEntityOptions, makeEntityActorRef, makeInMemoryPersistenceAdapter, toEntity };
@@ -59,7 +59,7 @@ declare const toEntity: <S extends {
59
59
  readonly _tag: string;
60
60
  }, E extends {
61
61
  readonly _tag: string;
62
- }, R, SD extends Record<string, Schema.Struct.Fields>, ED extends Record<string, Schema.Struct.Fields>>(machine: Machine<S, E, R, SD, ED>, options: ToEntityOptions) => Entity.Entity<string, EntityRpcs<MachineStateSchema<SD> & {
62
+ }, R, SD extends Record<string, Schema.Struct.Fields>, ED extends Record<string, Schema.Struct.Fields>, Input, Output>(machine: Machine<S, E, R, SD, ED, Input, Output>, options: ToEntityOptions) => Entity.Entity<string, EntityRpcs<MachineStateSchema<SD> & {
63
63
  readonly Type: S;
64
64
  }, MachineEventSchema<ED> & {
65
65
  readonly Type: E;
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ import { DeferReplyResult, ReplyResult } from "./internal/utils.js";
2
2
  import { Event, MachineEventSchema, MachineStateSchema, ReplyFields, State } from "./schema.js";
3
3
  import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, NoReplyError, PersistenceError, VersionConflictError } from "./errors.js";
4
4
  import { ActorExit, DefectPhase, Supervision } from "./supervision.js";
5
- import { Durability, DurabilityCommit, HandlerContext, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, StateHandlerContext, TaskOptions, TimeoutConfig, machine_d_exports } from "./machine.js";
5
+ import { Durability, DurabilityCommit, FinalContext, GuardPredicate, HandlerContext, InputMakeConfig, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, ReplayOptions, SpawnOptions, StateHandlerContext, TaskOptions, TimeoutConfig, machine_d_exports } from "./machine.js";
6
6
  import { ProcessEventResult } from "./internal/transition.js";
7
- import { ActorRef, ActorRefSync, ActorScope, ActorSystem, ActorSystemService, Default, SystemEvent, SystemEventListener, TransitionInfo } from "./actor.js";
8
- import { SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
9
- import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, InspectorService, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
10
- export { ActorExit, type ActorRef, type ActorRefSync, ActorScope, ActorStoppedError, type ActorSystemService as ActorSystem, Default as ActorSystemDefault, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type DefectPhase, type DeferReplyResult, DuplicateActorError, type Durability, type DurabilityCommit, type EffectEvent, type ErrorEvent, Event, type EventReceivedEvent, type HandlerContext, type InspectionEvent, type InspectorService as Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, type Lifecycle, machine_d_exports as Machine, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, NoReplyError, PersistenceError, type ProcessEventResult, type Recovery, type RecoveryContext, type ReplyFields, type ReplyResult, type SimulationResult, 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 TransitionEvent, type TransitionInfo, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
7
+ import { ActorClient, ActorLifecycle, ActorRef, ActorRefSync, ActorScope, ActorSystem, ActorSystemKey, ActorSystemService, Default, SystemEvent, SystemEventListener, SystemSpawnOptions, TransitionInfo, actorSystemKey } from "./actor.js";
8
+ import { InputTestHarnessOptions, SimulationOptions, SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
9
+ import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, GuardEvent, InspectionEvent, Inspector, InspectorHandler, InspectorService, OperationEvent, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
10
+ export { type ActorClient, ActorExit, type ActorExit as ActorExitType, type ActorLifecycle, type ActorRef, type ActorRefSync, ActorScope, ActorStoppedError, type ActorSystemService as ActorSystem, Default as ActorSystemDefault, ActorSystemKey, ActorSystem as ActorSystemService, type AnyInspectionEvent, AssertionError, type DefectPhase, type DeferReplyResult, DuplicateActorError, type Durability, type DurabilityCommit, type EffectEvent, type ErrorEvent, Event, type EventReceivedEvent, type FinalContext, type GuardEvent, type GuardPredicate, type HandlerContext, type InputMakeConfig, type InputTestHarnessOptions, type InspectionEvent, type InspectorService as Inspector, type InspectorHandler, Inspector as InspectorService, InvalidSchemaError, type Lifecycle, machine_d_exports as Machine, type MachineEventSchema, type MachineRef, type MachineStateSchema, type Machine as MachineType, type MakeConfig, MissingMatchHandlerError, NoReplyError, type OperationEvent, PersistenceError, type ProcessEventResult, type Recovery, type RecoveryContext, type ReplayOptions, type ReplyFields, type ReplyResult, type SimulationOptions, type SimulationResult, type SpawnEvent, type SpawnOptions, State, type StateHandlerContext, type StopEvent, Supervision, type SystemEvent, type SystemEventListener, type SystemSpawnOptions, type TaskEvent, type TaskOptions, type TestHarness, type TestHarnessOptions, type TimeoutConfig, type TracingInspectorOptions, type TransitionEvent, type TransitionInfo, VersionConflictError, actorSystemKey, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaEr
2
2
  import { Event, State } from "./schema.js";
3
3
  import { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
4
4
  import { ActorExit, Supervision } from "./supervision.js";
5
- import { ActorScope, ActorSystem, Default } from "./actor.js";
5
+ import { ActorScope, ActorSystem, ActorSystemKey, Default, actorSystemKey } from "./actor.js";
6
6
  import { machine_exports } from "./machine.js";
7
7
  import { assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
8
- export { ActorExit, ActorScope, ActorStoppedError, Default as ActorSystemDefault, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, NoReplyError, PersistenceError, State, Supervision, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
8
+ export { ActorExit, ActorScope, ActorStoppedError, Default as ActorSystemDefault, ActorSystemKey, ActorSystem as ActorSystemService, AssertionError, DuplicateActorError, Event, Inspector as InspectorService, InvalidSchemaError, machine_exports as Machine, MissingMatchHandlerError, NoReplyError, PersistenceError, State, Supervision, VersionConflictError, actorSystemKey, assertNeverReaches, assertPath, assertReaches, collectingInspector, combineInspectors, consoleInspector, createTestHarness, makeInspector, makeInspectorEffect, simulate, tracingInspector };
@@ -10,6 +10,7 @@ type ResolveType<T> = T extends Schema.Schema<infer A> ? A : T;
10
10
  interface SpawnEvent<S> {
11
11
  readonly type: "@machine.spawn";
12
12
  readonly actorId: string;
13
+ readonly generation: number;
13
14
  readonly initialState: S;
14
15
  readonly timestamp: number;
15
16
  }
@@ -19,6 +20,7 @@ interface SpawnEvent<S> {
19
20
  interface EventReceivedEvent<S, E> {
20
21
  readonly type: "@machine.event";
21
22
  readonly actorId: string;
23
+ readonly generation: number;
22
24
  readonly state: S;
23
25
  readonly event: E;
24
26
  readonly timestamp: number;
@@ -29,17 +31,40 @@ interface EventReceivedEvent<S, E> {
29
31
  interface TransitionEvent<S, E> {
30
32
  readonly type: "@machine.transition";
31
33
  readonly actorId: string;
34
+ readonly generation: number;
32
35
  readonly fromState: S;
33
36
  readonly toState: S;
34
37
  readonly event: E;
35
38
  readonly timestamp: number;
36
39
  }
40
+ /** Event emitted after a transition guard runs. */
41
+ interface GuardEvent<S, E> {
42
+ readonly type: "@machine.guard";
43
+ readonly actorId: string;
44
+ readonly generation: number;
45
+ readonly state: S;
46
+ readonly event: E;
47
+ readonly guard: string;
48
+ readonly result: boolean;
49
+ readonly timestamp: number;
50
+ }
51
+ /** Event emitted before an accepted transition handler runs. */
52
+ interface OperationEvent<S, E> {
53
+ readonly type: "@machine.operation";
54
+ readonly actorId: string;
55
+ readonly generation: number;
56
+ readonly operation: string;
57
+ readonly state: S;
58
+ readonly event: E;
59
+ readonly timestamp: number;
60
+ }
37
61
  /**
38
62
  * Event emitted when a spawn effect runs
39
63
  */
40
64
  interface EffectEvent<S> {
41
65
  readonly type: "@machine.effect";
42
66
  readonly actorId: string;
67
+ readonly generation: number;
43
68
  readonly effectType: "spawn";
44
69
  readonly state: S;
45
70
  readonly timestamp: number;
@@ -47,9 +72,10 @@ interface EffectEvent<S> {
47
72
  interface TaskEvent<S> {
48
73
  readonly type: "@machine.task";
49
74
  readonly actorId: string;
75
+ readonly generation: number;
50
76
  readonly state: S;
51
77
  readonly taskName?: string;
52
- readonly phase: "start" | "success" | "failure" | "interrupt";
78
+ readonly phase: "start" | "success" | "failure" | "defect" | "interrupt";
53
79
  readonly error?: string;
54
80
  readonly timestamp: number;
55
81
  }
@@ -59,6 +85,7 @@ interface TaskEvent<S> {
59
85
  interface ErrorEvent<S, E> {
60
86
  readonly type: "@machine.error";
61
87
  readonly actorId: string;
88
+ readonly generation: number;
62
89
  readonly phase: "transition" | "spawn";
63
90
  readonly state: S;
64
91
  readonly event: E;
@@ -71,13 +98,14 @@ interface ErrorEvent<S, E> {
71
98
  interface StopEvent<S> {
72
99
  readonly type: "@machine.stop";
73
100
  readonly actorId: string;
101
+ readonly generation: number;
74
102
  readonly finalState: S;
75
103
  readonly timestamp: number;
76
104
  }
77
105
  /**
78
106
  * Union of all inspection events
79
107
  */
80
- type InspectionEvent<S, E> = SpawnEvent<S> | EventReceivedEvent<S, E> | TransitionEvent<S, E> | EffectEvent<S> | TaskEvent<S> | ErrorEvent<S, E> | StopEvent<S>;
108
+ type InspectionEvent<S, E> = SpawnEvent<S> | EventReceivedEvent<S, E> | TransitionEvent<S, E> | GuardEvent<S, E> | OperationEvent<S, E> | EffectEvent<S> | TaskEvent<S> | ErrorEvent<S, E> | StopEvent<S>;
81
109
  /**
82
110
  * Convenience alias for untyped inspection events.
83
111
  * Useful for general-purpose inspectors that don't need specific state/event types.
@@ -148,4 +176,4 @@ declare const collectingInspector: <S extends {
148
176
  readonly _tag: string;
149
177
  }>(events: InspectionEvent<S, E>[]) => InspectorService<S, E>;
150
178
  //#endregion
151
- export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, InspectionEvent, Inspector, InspectorHandler, InspectorService, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
179
+ export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, GuardEvent, InspectionEvent, Inspector, InspectorHandler, InspectorService, OperationEvent, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
@@ -29,6 +29,8 @@ const inspectionSpanName = (event) => {
29
29
  case "@machine.spawn": return `Machine.inspect ${event.initialState._tag}`;
30
30
  case "@machine.event": return `Machine.inspect ${event.event._tag}`;
31
31
  case "@machine.transition": return `Machine.inspect ${event.fromState._tag}->${event.toState._tag}`;
32
+ case "@machine.guard": return `Machine.inspect guard:${event.guard}`;
33
+ case "@machine.operation": return `Machine.inspect operation:${event.operation}`;
32
34
  case "@machine.effect": return `Machine.inspect ${event.effectType}`;
33
35
  case "@machine.task": return `Machine.inspect task:${event.phase}`;
34
36
  case "@machine.error": return `Machine.inspect ${event.phase}`;
@@ -40,6 +42,8 @@ const inspectionTraceName = (event) => {
40
42
  case "@machine.spawn": return `machine.spawn ${event.initialState._tag}`;
41
43
  case "@machine.event": return `machine.event ${event.event._tag}`;
42
44
  case "@machine.transition": return `machine.transition ${event.fromState._tag}->${event.toState._tag}`;
45
+ case "@machine.guard": return `machine.guard ${event.guard}`;
46
+ case "@machine.operation": return `machine.operation ${event.operation}`;
43
47
  case "@machine.effect": return `machine.effect ${event.effectType}`;
44
48
  case "@machine.task": {
45
49
  let taskSuffix = "";
@@ -53,6 +57,7 @@ const inspectionTraceName = (event) => {
53
57
  const inspectionAttributes = (event) => {
54
58
  const shared = {
55
59
  "machine.actor.id": event.actorId,
60
+ "machine.actor.generation": event.generation,
56
61
  "machine.inspection.type": event.type
57
62
  };
58
63
  switch (event.type) {
@@ -71,6 +76,19 @@ const inspectionAttributes = (event) => {
71
76
  "machine.state.to": event.toState._tag,
72
77
  "machine.event.tag": event.event._tag
73
78
  };
79
+ case "@machine.guard": return {
80
+ ...shared,
81
+ "machine.state.current": event.state._tag,
82
+ "machine.event.tag": event.event._tag,
83
+ "machine.guard.name": event.guard,
84
+ "machine.guard.result": event.result
85
+ };
86
+ case "@machine.operation": return {
87
+ ...shared,
88
+ "machine.state.current": event.state._tag,
89
+ "machine.event.tag": event.event._tag,
90
+ "machine.operation.name": event.operation
91
+ };
74
92
  case "@machine.effect": return {
75
93
  ...shared,
76
94
  "machine.state.current": event.state._tag,
@@ -110,6 +128,7 @@ const tracingInspector = (options) => ({ onInspect: (event) => {
110
128
  const currentSpan = yield* Effect.option(Effect.currentSpan);
111
129
  if (Option.isSome(currentSpan)) currentSpan.value.event(traceName, BigInt(event.timestamp) * 1000000n, {
112
130
  actorId: event.actorId,
131
+ generation: event.generation,
113
132
  inspectionType: event.type
114
133
  });
115
134
  })().pipe(Effect.withSpan(spanName ?? inspectionSpanName(event), { attributes }));
@@ -123,6 +142,8 @@ const consoleInspector = () => makeInspectorEffect((event) => {
123
142
  case "@machine.spawn": return Effect.log(`${prefix} spawned -> ${event.initialState._tag}`);
124
143
  case "@machine.event": return Effect.log(`${prefix} received ${event.event._tag} in ${event.state._tag}`);
125
144
  case "@machine.transition": return Effect.log(`${prefix} ${event.fromState._tag} -> ${event.toState._tag}`);
145
+ case "@machine.guard": return Effect.log(`${prefix} guard ${event.guard} -> ${String(event.result)}`);
146
+ case "@machine.operation": return Effect.log(`${prefix} operation ${event.operation} in ${event.state._tag}`);
126
147
  case "@machine.effect": return Effect.log(`${prefix} ${event.effectType} effect in ${event.state._tag}`);
127
148
  case "@machine.task": return Effect.log(`${prefix} task ${event.phase} ${event.taskName ?? "<unnamed>"} in ${event.state._tag}`);
128
149
  case "@machine.error": return Effect.log(`${prefix} error in ${event.phase} ${event.state._tag} - ${String(event.error)}`);
@@ -2,6 +2,6 @@ import { ProcessEventHooks } from "./transition.js";
2
2
  import { InspectorService } from "../inspection.js";
3
3
  //#region src/internal/inspection.d.ts
4
4
  /** Adapt the Inspector service to the transition kernel. */
5
- declare const makeInspectionHooks: <S, E>(actorId: string, inspector: InspectorService<S, E>) => ProcessEventHooks<S, E>;
5
+ declare const makeInspectionHooks: <S, E>(actorId: string, inspector: InspectorService<S, E>, getGeneration?: () => number) => ProcessEventHooks<S, E>;
6
6
  //#endregion
7
7
  export { makeInspectionHooks };
@@ -11,10 +11,30 @@ const emitWithTimestamp = Effect.fn("effect-machine.emitWithTimestamp")(function
11
11
  if (Effect.isEffect(result)) yield* result.pipe(Effect.ignoreCause);
12
12
  });
13
13
  /** Adapt the Inspector service to the transition kernel. */
14
- const makeInspectionHooks = (actorId, inspector) => ({
14
+ const makeInspectionHooks = (actorId, inspector, getGeneration = () => 0) => ({
15
+ onGuard: (evaluation) => emitWithTimestamp(inspector, (timestamp) => ({
16
+ type: "@machine.guard",
17
+ actorId,
18
+ generation: getGeneration(),
19
+ state: evaluation.state,
20
+ event: evaluation.event,
21
+ guard: evaluation.guard,
22
+ result: evaluation.result,
23
+ timestamp
24
+ })),
25
+ onOperation: (operation) => emitWithTimestamp(inspector, (timestamp) => ({
26
+ type: "@machine.operation",
27
+ actorId,
28
+ generation: getGeneration(),
29
+ operation: operation.operation,
30
+ state: operation.state,
31
+ event: operation.event,
32
+ timestamp
33
+ })),
15
34
  onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
16
35
  type: "@machine.effect",
17
36
  actorId,
37
+ generation: getGeneration(),
18
38
  effectType: "spawn",
19
39
  state,
20
40
  timestamp
@@ -22,6 +42,7 @@ const makeInspectionHooks = (actorId, inspector) => ({
22
42
  onTransition: (from, to, event) => emitWithTimestamp(inspector, (timestamp) => ({
23
43
  type: "@machine.transition",
24
44
  actorId,
45
+ generation: getGeneration(),
25
46
  fromState: from,
26
47
  toState: to,
27
48
  event,
@@ -30,6 +51,7 @@ const makeInspectionHooks = (actorId, inspector) => ({
30
51
  onError: (info) => emitWithTimestamp(inspector, (timestamp) => ({
31
52
  type: "@machine.error",
32
53
  actorId,
54
+ generation: getGeneration(),
33
55
  phase: info.phase,
34
56
  state: info.state,
35
57
  event: info.event,
@@ -4,6 +4,7 @@ interface Transition<State, Event, R> {
4
4
  readonly eventTag: string;
5
5
  readonly handler: TransitionHandler<State, Event, State, R>;
6
6
  readonly reenter?: boolean;
7
+ readonly guard?: GuardPredicate<State, Event, never>;
7
8
  }
8
9
  interface SpawnEffect<State, Event, R> {
9
10
  readonly stateTag: string;
@@ -0,0 +1,21 @@
1
+ //#region src/internal/machine-initialization.d.ts
2
+ /** How a machine creates the initial state for one actor. */
3
+ type MachineInitialization<Input, State> = {
4
+ readonly _tag: "Static";
5
+ readonly state: State;
6
+ } | {
7
+ readonly _tag: "Input";
8
+ readonly initialize: (input: Input) => State;
9
+ };
10
+ declare const make: <Input, State>(initial: State | ((input: Input) => State)) => MachineInitialization<Input, State>;
11
+ /** Resolve one actor initial state. This is the only input erasure seam. */
12
+ declare const resolve: <Input, State>(initialization: MachineInitialization<Input, State>, input: unknown) => State;
13
+ /** Return the static value, or undefined for an input machine. */
14
+ declare const staticValue: <Input, State>(initialization: MachineInitialization<Input, State>) => State | undefined;
15
+ declare const MachineInitialization: {
16
+ make: typeof make;
17
+ resolve: typeof resolve;
18
+ staticValue: typeof staticValue;
19
+ };
20
+ //#endregion
21
+ export { MachineInitialization };
@@ -0,0 +1,27 @@
1
+ //#region src/internal/machine-initialization.ts
2
+ const make = (initial) => {
3
+ if (typeof initial === "function") return {
4
+ _tag: "Input",
5
+ initialize: initial
6
+ };
7
+ return {
8
+ _tag: "Static",
9
+ state: initial
10
+ };
11
+ };
12
+ /** Resolve one actor initial state. This is the only input erasure seam. */
13
+ const resolve = (initialization, input) => {
14
+ if (initialization._tag === "Static") return initialization.state;
15
+ return initialization.initialize(input);
16
+ };
17
+ /** Return the static value, or undefined for an input machine. */
18
+ const staticValue = (initialization) => {
19
+ if (initialization._tag === "Static") return initialization.state;
20
+ };
21
+ const MachineInitialization = {
22
+ make,
23
+ resolve,
24
+ staticValue
25
+ };
26
+ //#endregion
27
+ export { MachineInitialization };
@@ -1 +1,15 @@
1
- export {}
1
+ import { DefectPhase } from "../supervision.js";
2
+ //#region src/internal/runtime.d.ts
3
+ /** Internal exit reason for one runtime generation. */
4
+ type RuntimeExit<S> = {
5
+ readonly _tag: "Final";
6
+ readonly state: S;
7
+ } | {
8
+ readonly _tag: "Stopped";
9
+ } | {
10
+ readonly _tag: "Defect";
11
+ readonly cause: Cause.Cause<unknown>;
12
+ readonly phase: DefectPhase;
13
+ };
14
+ //#endregion
15
+ export { RuntimeExit };