effect-machine 0.23.0 → 0.24.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/README.md CHANGED
@@ -208,7 +208,8 @@ Read [Atom and UI integration](./docs/atom-and-ui.md) and browse [all examples](
208
208
  - Durability saves committed transitions.
209
209
  - Supervision restarts defects within an Effect `Schedule` budget.
210
210
  - Inspection reports events, named transition operations, transitions, named guards, tasks, Effects, errors, stops, and actor generations.
211
- - Dynamic Inspector hubs let lazy tools observe existing actors without actor restart or state duplication.
211
+ - Typed `inspect` spawn options observe one actor.
212
+ - `actor.system.inspect` lets a late tool observe all actors in one system.
212
213
 
213
214
  Read [Persistence and supervision](./docs/persistence-and-supervision.md) and [Inspection](./docs/inspection.md).
214
215
 
package/dist/actor.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ExtractReply, ReplyTypeBrand } from "./internal/brands.js";
2
2
  import { ActorStoppedError, DuplicateActorError, NoReplyError } from "./errors.js";
3
+ import { InspectorService } from "./inspection.js";
3
4
  import { ActorExit, Supervision } from "./supervision.js";
4
5
  import { Lifecycle, Machine } from "./machine.js";
5
6
  import { ProcessEventResult } from "./internal/transition.js";
@@ -238,11 +239,22 @@ interface ActorSystemService {
238
239
  * Returns an unsubscribe function.
239
240
  */
240
241
  readonly subscribe: (fn: SystemEventListener) => () => void;
242
+ /**
243
+ * Inspect all actors in this system.
244
+ *
245
+ * Registration affects future events only. The returned function removes the inspector.
246
+ */
247
+ readonly inspect: (inspector: InspectorService<{
248
+ readonly _tag: string;
249
+ }, {
250
+ readonly _tag: string;
251
+ }>) => () => void;
241
252
  }
242
253
  type SystemSpawnOptions<S, E, Input> = {
243
254
  readonly supervision?: Supervision.Policy;
244
255
  readonly lifecycle?: Lifecycle<S, E>;
245
256
  readonly hydrate?: S;
257
+ readonly inspect?: InspectorService<S, E>;
246
258
  } & ([Input] extends [void] ? {
247
259
  readonly input?: never;
248
260
  } : {
@@ -278,6 +290,7 @@ declare const createActor: <S extends {
278
290
  hydrated?: boolean;
279
291
  supervision?: Supervision.Policy;
280
292
  lifecycle?: Lifecycle<S, E>;
293
+ inspect?: InspectorService<S, E>;
281
294
  /** @internal Called by system after each restart — emits ActorRestarted system event */
282
295
  onRestart?: (generation: number, exit: ActorExit<unknown>) => Effect.Effect<void>;
283
296
  }) => Effect.Effect<ActorRef<S, E, O>, never, R>;
package/dist/actor.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ActorStoppedError, DuplicateActorError } from "./errors.js";
2
2
  import { resolveTransition, resolveTransitionEffect } from "./internal/transition.js";
3
- import { emitWithTimestamp, makeInspectionHooks } from "./internal/inspection.js";
3
+ import { emitWithTimestamp, makeInspectionDispatcher, makeInspectionHooks } from "./internal/inspection.js";
4
4
  import { Inspector } from "./inspection.js";
5
5
  import { ActorExit } from "./supervision.js";
6
6
  import { createRuntime } from "./internal/runtime.js";
@@ -27,6 +27,7 @@ const actorSystemId = (id) => {
27
27
  if (typeof id === "string") return id;
28
28
  return id.id;
29
29
  };
30
+ const systemInspectorsBySystem = /* @__PURE__ */ new WeakMap();
30
31
  /**
31
32
  * ActorSystem service tag
32
33
  */
@@ -280,20 +281,21 @@ const runSupervisionLoop = (cell, options) => Effect.gen(function* () {
280
281
  */
281
282
  const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine, options) {
282
283
  const lifecycle = options.lifecycle;
283
- const serviceContext = yield* Effect.context();
284
+ const capturedContext = yield* Effect.context();
284
285
  const initial = options.initialState;
285
286
  yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
286
287
  yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", initial._tag);
287
288
  const { system, implicitSystemScope } = yield* resolveActorSystem();
288
- const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
289
+ const ambientInspector = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
290
+ const localInspector = options.inspect ?? ambientInspector;
291
+ const systemInspectors = systemInspectorsBySystem.get(system) ?? /* @__PURE__ */ new Set();
292
+ const inspectorValue = makeInspectionDispatcher(localInspector, systemInspectors);
293
+ const serviceContext = Context.add(capturedContext, Inspector, inspectorValue);
289
294
  const childrenMap = /* @__PURE__ */ new Map();
290
295
  const listeners = /* @__PURE__ */ new Set();
291
296
  const transitionsPubSub = yield* PubSub.unbounded();
292
297
  const generation = { current: 0 };
293
- const inspectionHooks = (runtimeGeneration) => {
294
- if (inspectorValue === void 0) return void 0;
295
- return makeInspectionHooks(id, inspectorValue, () => runtimeGeneration);
296
- };
298
+ const inspectionHooks = (runtimeGeneration) => makeInspectionHooks(id, inspectorValue, () => runtimeGeneration);
297
299
  const stateRef = yield* SubscriptionRef.make(initial);
298
300
  const lifecycleRef = yield* SubscriptionRef.make({ _tag: "Created" });
299
301
  const latestTransitionRef = yield* SubscriptionRef.make(void 0);
@@ -323,8 +325,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
323
325
  /** Build lifecycle hooks for a generation */
324
326
  const buildRuntimeLifecycle = (runtimeGeneration) => {
325
327
  let stopEmitted = false;
326
- let onEvent = void 0;
327
- if (inspectorValue !== void 0) onEvent = (state, event) => emitWithTimestamp(inspectorValue, (timestamp) => ({
328
+ const onEvent = (state, event) => emitWithTimestamp(inspectorValue, (timestamp) => ({
328
329
  type: "@machine.event",
329
330
  actorId: id,
330
331
  generation: runtimeGeneration,
@@ -332,8 +333,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
332
333
  event,
333
334
  timestamp
334
335
  }));
335
- let onFinal = void 0;
336
- if (inspectorValue !== void 0) onFinal = (state) => Effect.gen(function* () {
336
+ const onFinal = (state) => Effect.gen(function* () {
337
337
  stopEmitted = true;
338
338
  yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
339
339
  type: "@machine.stop",
@@ -343,8 +343,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
343
343
  timestamp
344
344
  }));
345
345
  });
346
- let onInitialSpawnEffects = void 0;
347
- if (inspectorValue !== void 0) onInitialSpawnEffects = (state) => emitWithTimestamp(inspectorValue, (timestamp) => ({
346
+ const onInitialSpawnEffects = (state) => emitWithTimestamp(inspectorValue, (timestamp) => ({
348
347
  type: "@machine.effect",
349
348
  actorId: id,
350
349
  generation: runtimeGeneration,
@@ -484,13 +483,14 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
484
483
  const withSpawnGate = (yield* Semaphore.make(1)).withPermits(1);
485
484
  const eventPubSub = yield* PubSub.unbounded();
486
485
  const eventListeners = /* @__PURE__ */ new Set();
486
+ const systemInspectors = /* @__PURE__ */ new Set();
487
487
  const emitSystemEvent = (event) => Effect.sync(() => notifySystemListeners(eventListeners, event)).pipe(Effect.andThen(PubSub.publish(eventPubSub, event)), Effect.catchCause(() => Effect.void), Effect.asVoid);
488
488
  yield* Effect.addFinalizer(() => {
489
489
  const stops = [];
490
490
  MutableHashMap.forEach(actorsMap, (actor) => {
491
491
  stops.push(actor.stop);
492
492
  });
493
- return Effect.all(stops).pipe(Effect.andThen(PubSub.shutdown(eventPubSub)), Effect.asVoid);
493
+ return Effect.all(stops).pipe(Effect.andThen(Effect.sync(() => systemInspectors.clear())), Effect.andThen(PubSub.shutdown(eventPubSub)), Effect.asVoid);
494
494
  });
495
495
  /** Check for duplicate ID, register actor, attach scope cleanup if available */
496
496
  const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* (id, actor) {
@@ -554,6 +554,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
554
554
  hydrated: spawnOptions?.hydrate !== void 0,
555
555
  supervision: spawnOptions?.supervision,
556
556
  lifecycle: spawnOptions?.lifecycle,
557
+ inspect: spawnOptions?.inspect,
557
558
  onRestart
558
559
  });
559
560
  actorRef = actor;
@@ -602,7 +603,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
602
603
  yield* actor.stop;
603
604
  return true;
604
605
  });
605
- return ActorSystem.of({
606
+ const system = ActorSystem.of({
606
607
  spawn,
607
608
  get,
608
609
  watch,
@@ -620,8 +621,16 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
620
621
  return () => {
621
622
  eventListeners.delete(fn);
622
623
  };
624
+ },
625
+ inspect: (inspector) => {
626
+ systemInspectors.add(inspector);
627
+ return () => {
628
+ systemInspectors.delete(inspector);
629
+ };
623
630
  }
624
631
  });
632
+ systemInspectorsBySystem.set(system, systemInspectors);
633
+ return system;
625
634
  });
626
635
  /**
627
636
  * Create an ActorSystem instance. Must be run in a Scope.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  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
+ 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";
4
5
  import { ActorExit, DefectPhase, Supervision } from "./supervision.js";
5
6
  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
7
  import { ProcessEventResult } from "./internal/transition.js";
7
8
  import { ActorClient, ActorLifecycle, ActorRef, ActorRefSync, ActorScope, ActorSystem, ActorSystemKey, ActorSystemService, Default, SystemEvent, SystemEventListener, SystemSpawnOptions, TransitionInfo, actorSystemKey } from "./actor.js";
8
9
  import { InputTestHarnessOptions, SimulationOptions, SimulationResult, TestHarness, TestHarnessOptions, assertNeverReaches, assertPath, assertReaches, createTestHarness, simulate } from "./testing.js";
9
- import { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, GuardEvent, InspectionEvent, Inspector, InspectorHandler, InspectorHub, InspectorService, OperationEvent, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, makeInspectorHub, 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, type InspectorHub, 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, makeInspectorHub, simulate, tracingInspector };
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
@@ -1,8 +1,8 @@
1
1
  import { ActorStoppedError, AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, NoReplyError, PersistenceError, VersionConflictError } from "./errors.js";
2
2
  import { Event, State } from "./schema.js";
3
- import { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, makeInspectorHub, tracingInspector } from "./inspection.js";
3
+ import { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector } from "./inspection.js";
4
4
  import { ActorExit, Supervision } from "./supervision.js";
5
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, 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, makeInspectorHub, 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 };
@@ -124,13 +124,6 @@ type InspectorHandler<S, E> = (event: InspectionEvent<S, E>) => void | Effect.Ef
124
124
  interface InspectorService<S, E> {
125
125
  readonly onInspect: InspectorHandler<S, E>;
126
126
  }
127
- /**
128
- * Dynamic Inspector fan-out for applications that load inspection consumers after actors start.
129
- */
130
- interface InspectorHub<S, E> {
131
- readonly inspector: InspectorService<S, E>;
132
- readonly register: (inspector: InspectorService<S, E>) => () => void;
133
- }
134
127
  declare const Inspector_base: Context.ServiceClass<Inspector, "effect-machine/inspection/Inspector", InspectorService<any, any>>;
135
128
  /**
136
129
  * Inspector service tag - optional service for machine introspection
@@ -156,17 +149,6 @@ declare const makeInspectorEffect: <S = {
156
149
  readonly _tag: string;
157
150
  }>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => Effect.Effect<void>) => InspectorService<ResolveType<S>, ResolveType<E>>;
158
151
  declare const combineInspectors: <S, E>(...inspectors: ReadonlyArray<InspectorService<S, E>>) => InspectorService<S, E>;
159
- /**
160
- * Create an Inspector that accepts sinks throughout its lifetime.
161
- *
162
- * Provide `hub.inspector` before actor startup. Consumers can then register and unregister sinks
163
- * without restarting the actor. A sink failure does not affect the actor or other sinks.
164
- */
165
- declare const makeInspectorHub: <S = {
166
- readonly _tag: string;
167
- }, E = {
168
- readonly _tag: string;
169
- }>() => InspectorHub<ResolveType<S>, ResolveType<E>>;
170
152
  interface TracingInspectorOptions<S, E> {
171
153
  readonly spanName?: string | ((event: InspectionEvent<S, E>) => string);
172
154
  readonly attributes?: (event: InspectionEvent<S, E>) => Readonly<Record<string, string | number | boolean>>;
@@ -194,4 +176,4 @@ declare const collectingInspector: <S extends {
194
176
  readonly _tag: string;
195
177
  }>(events: InspectionEvent<S, E>[]) => InspectorService<S, E>;
196
178
  //#endregion
197
- export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, GuardEvent, InspectionEvent, Inspector, InspectorHandler, InspectorHub, InspectorService, OperationEvent, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, makeInspectorHub, tracingInspector };
179
+ export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, GuardEvent, InspectionEvent, Inspector, InspectorHandler, InspectorService, OperationEvent, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
@@ -1,3 +1,4 @@
1
+ import { runInspectors } from "./internal/inspection.js";
1
2
  import { Context, Effect, Option } from "effect";
2
3
  //#region src/inspection.ts
3
4
  /**
@@ -15,39 +16,7 @@ var Inspector = class extends Context.Service()("effect-machine/inspection/Inspe
15
16
  */
16
17
  const makeInspector = (onInspect) => ({ onInspect });
17
18
  const makeInspectorEffect = (onInspect) => ({ onInspect });
18
- const inspectionEffect = (inspector, event) => Effect.suspend(() => {
19
- const result = inspector.onInspect(event);
20
- if (Effect.isEffect(result)) return result;
21
- return Effect.void;
22
- });
23
- const combineInspectors = (...inspectors) => ({ onInspect: (event) => Effect.forEach(inspectors, (inspector) => inspectionEffect(inspector, event).pipe(Effect.ignoreCause), {
24
- concurrency: 16,
25
- discard: true
26
- }) });
27
- /**
28
- * Create an Inspector that accepts sinks throughout its lifetime.
29
- *
30
- * Provide `hub.inspector` before actor startup. Consumers can then register and unregister sinks
31
- * without restarting the actor. A sink failure does not affect the actor or other sinks.
32
- */
33
- const makeInspectorHub = () => {
34
- const inspectorByRegistration = /* @__PURE__ */ new Map();
35
- const inspector = { onInspect: (event) => Effect.forEach(Array.from(inspectorByRegistration.values()), (registeredInspector) => inspectionEffect(registeredInspector, event).pipe(Effect.ignoreCause), {
36
- concurrency: 16,
37
- discard: true
38
- }) };
39
- const register = (registeredInspector) => {
40
- const registration = Symbol();
41
- inspectorByRegistration.set(registration, registeredInspector);
42
- return () => {
43
- inspectorByRegistration.delete(registration);
44
- };
45
- };
46
- return {
47
- inspector,
48
- register
49
- };
50
- };
19
+ const combineInspectors = (...inspectors) => ({ onInspect: (event) => runInspectors(inspectors, event) });
51
20
  const inspectionSpanName = (event) => {
52
21
  switch (event.type) {
53
22
  case "@machine.spawn": return `Machine.inspect ${event.initialState._tag}`;
@@ -181,4 +150,4 @@ const collectingInspector = (events) => ({ onInspect: (event) => {
181
150
  events.push(event);
182
151
  } });
183
152
  //#endregion
184
- export { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, makeInspectorHub, tracingInspector };
153
+ export { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
@@ -1,7 +1,17 @@
1
+ import { InspectionEvent, InspectorService } from "../inspection.js";
1
2
  import { ProcessEventHooks } from "./transition.js";
2
- import { InspectorService } from "../inspection.js";
3
+ import { Effect } from "effect";
3
4
  //#region src/internal/inspection.d.ts
5
+ type Tagged = {
6
+ readonly _tag: string;
7
+ };
8
+ /** Run one inspector without letting its failure affect the machine. */
9
+ declare const runInspector: <S, E>(inspector: InspectorService<S, E>, event: InspectionEvent<S, E>) => Effect.Effect<void, never, never>;
10
+ /** Run inspectors in order and isolate each failure. */
11
+ declare const runInspectors: <S, E>(inspectors: Iterable<InspectorService<S, E>>, event: InspectionEvent<S, E>) => Effect.Effect<void, never, never>;
12
+ /** Build the actor-owned dispatcher for local and system inspection. */
13
+ declare const makeInspectionDispatcher: <S extends Tagged, E extends Tagged>(localInspector: InspectorService<S, E> | undefined, systemInspectors: ReadonlySet<InspectorService<Tagged, Tagged>>) => InspectorService<S, E>;
4
14
  /** Adapt the Inspector service to the transition kernel. */
5
15
  declare const makeInspectionHooks: <S, E>(actorId: string, inspector: InspectorService<S, E>, getGeneration?: () => number) => ProcessEventHooks<S, E>;
6
16
  //#endregion
7
- export { makeInspectionHooks };
17
+ export { makeInspectionDispatcher, makeInspectionHooks, runInspector, runInspectors };
@@ -1,14 +1,32 @@
1
1
  import { Cause, Clock, Effect } from "effect";
2
2
  //#region src/internal/inspection.ts
3
+ const inspectorActivity = /* @__PURE__ */ new WeakMap();
4
+ /** Run one inspector without letting its failure affect the machine. */
5
+ const runInspector = Effect.fn("effect-machine.runInspector")(function* (inspector, event) {
6
+ const result = yield* Effect.try(() => inspector.onInspect(event)).pipe(Effect.orElseSucceed(() => void 0));
7
+ if (Effect.isEffect(result)) yield* result.pipe(Effect.ignoreCause);
8
+ });
9
+ /** Run inspectors in order and isolate each failure. */
10
+ const runInspectors = Effect.fn("effect-machine.runInspectors")(function* (inspectors, event) {
11
+ for (const inspector of inspectors) yield* runInspector(inspector, event);
12
+ });
13
+ /** Build the actor-owned dispatcher for local and system inspection. */
14
+ const makeInspectionDispatcher = (localInspector, systemInspectors) => {
15
+ const inspector = { onInspect: (event) => Effect.gen(function* () {
16
+ if (localInspector !== void 0) yield* runInspector(localInspector, event);
17
+ for (const systemInspector of systemInspectors) yield* runInspector(systemInspector, event);
18
+ }) };
19
+ inspectorActivity.set(inspector, () => localInspector !== void 0 || systemInspectors.size > 0);
20
+ return inspector;
21
+ };
3
22
  /**
4
23
  * Emit an inspection event with timestamp from Clock.
5
24
  * @internal
6
25
  */
7
26
  const emitWithTimestamp = Effect.fn("effect-machine.emitWithTimestamp")(function* (inspector, makeEvent) {
8
- if (inspector === void 0) return;
27
+ if (inspector === void 0 || inspectorActivity.get(inspector)?.() === false) return;
9
28
  const event = makeEvent(yield* Clock.currentTimeMillis);
10
- const result = yield* Effect.try(() => inspector.onInspect(event)).pipe(Effect.orElseSucceed(() => void 0));
11
- if (Effect.isEffect(result)) yield* result.pipe(Effect.ignoreCause);
29
+ yield* runInspector(inspector, event);
12
30
  });
13
31
  /** Adapt the Inspector service to the transition kernel. */
14
32
  const makeInspectionHooks = (actorId, inspector, getGeneration = () => 0) => ({
@@ -60,4 +78,4 @@ const makeInspectionHooks = (actorId, inspector, getGeneration = () => 0) => ({
60
78
  }))
61
79
  });
62
80
  //#endregion
63
- export { emitWithTimestamp, makeInspectionHooks };
81
+ export { emitWithTimestamp, makeInspectionDispatcher, makeInspectionHooks, runInspector, runInspectors };
@@ -70,7 +70,8 @@ const stubSystem = {
70
70
  get actors() {
71
71
  return /* @__PURE__ */ new Map();
72
72
  },
73
- subscribe: () => () => {}
73
+ subscribe: () => () => {},
74
+ inspect: () => () => {}
74
75
  };
75
76
  //#endregion
76
77
  export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isDeferReplyResult, isEffect, isReplyResult, makeDeferReply, makeReply, stubSystem };
package/dist/machine.d.ts CHANGED
@@ -2,6 +2,7 @@ import { DeferReplyResult, ReplyResult, TransitionResult, makeDeferReply, makeRe
2
2
  import { BrandedEvent, BrandedState, ExtractReply, TaggedOrConstructor } from "./internal/brands.js";
3
3
  import { MachineEventSchema, MachineStateSchema, VariantsUnion } from "./schema.js";
4
4
  import { ActorStoppedError, DuplicateActorError } from "./errors.js";
5
+ import { InspectorService } from "./inspection.js";
5
6
  import { Supervision } from "./supervision.js";
6
7
  import { ActorRef, ActorSystemService, TransitionInfo } from "./actor.js";
7
8
  import { Duration, Effect, Option, Schema, Scope, SubscriptionRef } from "effect";
@@ -329,6 +330,7 @@ type SpawnOptions<S, E, Input> = {
329
330
  readonly hydrate?: S;
330
331
  readonly supervision?: Supervision.Policy;
331
332
  readonly lifecycle?: Lifecycle<S, E>;
333
+ readonly inspect?: InspectorService<S, E>;
332
334
  } & ([Input] extends [void] ? {
333
335
  readonly input?: never;
334
336
  } : {
@@ -343,6 +345,7 @@ type SpawnOptions<S, E, Input> = {
343
345
  *
344
346
  * // With lifecycle (recovery + durability)
345
347
  * const actor = yield* Machine.spawn(machine, {
348
+ * inspect: consoleInspector(),
346
349
  * lifecycle: {
347
350
  * recovery: { resolve: (ctx) => storage.get("actor-state") },
348
351
  * durability: { save: (commit) => storage.set("actor-state", commit.nextState) },
package/dist/machine.js CHANGED
@@ -443,7 +443,8 @@ const spawnImpl = Effect.fn("effect-machine.spawn")(function* (machine, idOrOpti
443
443
  machineInitial,
444
444
  hydrated: opts?.hydrate !== void 0,
445
445
  supervision: opts?.supervision,
446
- lifecycle: opts?.lifecycle
446
+ lifecycle: opts?.lifecycle,
447
+ inspect: opts?.inspect
447
448
  });
448
449
  const maybeScope = yield* Effect.serviceOption(ActorScope);
449
450
  if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, actor.stop);
@@ -458,6 +459,7 @@ const spawnImpl = Effect.fn("effect-machine.spawn")(function* (machine, idOrOpti
458
459
  *
459
460
  * // With lifecycle (recovery + durability)
460
461
  * const actor = yield* Machine.spawn(machine, {
462
+ * inspect: consoleInspector(),
461
463
  * lifecycle: {
462
464
  * recovery: { resolve: (ctx) => storage.get("actor-state") },
463
465
  * durability: { save: (commit) => storage.set("actor-state", commit.nextState) },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-machine",
3
- "version": "0.23.0",
3
+ "version": "0.24.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cevr/effect-machine.git"