effect-machine 0.21.0 → 0.23.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 +5 -0
- package/dist/actor.js +2 -6
- package/dist/cluster/entity-machine.js +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/inspection.d.ts +19 -1
- package/dist/inspection.js +27 -3
- package/dist/internal/runtime.js +23 -6
- package/dist/machine.d.ts +8 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,6 +95,10 @@ Effect Machine does not add an action queue or a second context system.
|
|
|
95
95
|
|
|
96
96
|
Effect requirements remain in `R`. A machine cannot start until the application provides every required service. Effectful transition handlers must have `never` in their error channel. Convert expected failures to states or events.
|
|
97
97
|
|
|
98
|
+
Machine-lifetime backgrounds can read `self.state` and `self.latestTransition`. These are the
|
|
99
|
+
actor-owned subscription refs. They stay stable across supervision generations. Treat them as
|
|
100
|
+
read-only and use `SubscriptionRef.get` or `SubscriptionRef.changes` to observe them.
|
|
101
|
+
|
|
98
102
|
Read [the Effect model](./docs/effect-model.md) and [async work ownership](./docs/async-work.md).
|
|
99
103
|
|
|
100
104
|
## Guards and stable state
|
|
@@ -204,6 +208,7 @@ Read [Atom and UI integration](./docs/atom-and-ui.md) and browse [all examples](
|
|
|
204
208
|
- Durability saves committed transitions.
|
|
205
209
|
- Supervision restarts defects within an Effect `Schedule` budget.
|
|
206
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.
|
|
207
212
|
|
|
208
213
|
Read [Persistence and supervision](./docs/persistence-and-supervision.md) and [Inspection](./docs/inspection.md).
|
|
209
214
|
|
package/dist/actor.js
CHANGED
|
@@ -259,6 +259,7 @@ const runSupervisionLoop = (cell, options) => Effect.gen(function* () {
|
|
|
259
259
|
const freshQueue = yield* Queue.unbounded();
|
|
260
260
|
yield* Ref.set(cell.eventQueueRef, freshQueue);
|
|
261
261
|
yield* SubscriptionRef.set(cell.stateRef, restartState);
|
|
262
|
+
yield* SubscriptionRef.set(cell.latestTransitionRef, void 0);
|
|
262
263
|
yield* Ref.set(cell.stoppedRef, false);
|
|
263
264
|
cell.children.clear();
|
|
264
265
|
const newRuntime = yield* options.spawnGeneration(cell.machine);
|
|
@@ -354,12 +355,6 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
354
355
|
return {
|
|
355
356
|
onEvent,
|
|
356
357
|
onStateChange: (result, event) => Effect.gen(function* () {
|
|
357
|
-
const latest = result.transitions.at(-1);
|
|
358
|
-
if (latest !== void 0) yield* SubscriptionRef.set(latestTransitionRef, {
|
|
359
|
-
fromState: latest.previousState,
|
|
360
|
-
toState: latest.newState,
|
|
361
|
-
event: latest.event
|
|
362
|
-
});
|
|
363
358
|
notifyListeners(listeners, result.newState);
|
|
364
359
|
const durability = lifecycle?.durability;
|
|
365
360
|
if (durability === void 0 || !result.transitioned) return;
|
|
@@ -406,6 +401,7 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
406
401
|
skipFinalizer: true,
|
|
407
402
|
cellResources: {
|
|
408
403
|
stateRef,
|
|
404
|
+
latestTransitionRef,
|
|
409
405
|
stoppedRef,
|
|
410
406
|
eventQueue: currentQueue
|
|
411
407
|
},
|
|
@@ -55,6 +55,7 @@ const EntityMachine = { layer: (entity, machine, ...optionsArgument) => {
|
|
|
55
55
|
const versionRef = yield* Ref.make(persistCtx.initialVersion);
|
|
56
56
|
const computedInitial = initialState ?? machineInitial;
|
|
57
57
|
const stateRef = yield* SubscriptionRef.make(computedInitial);
|
|
58
|
+
const latestTransitionRef = yield* SubscriptionRef.make(void 0);
|
|
58
59
|
const stoppedRef = yield* Ref.make(false);
|
|
59
60
|
const eventQueue = yield* Queue.unbounded();
|
|
60
61
|
let hooks = void 0;
|
|
@@ -65,6 +66,7 @@ const EntityMachine = { layer: (entity, machine, ...optionsArgument) => {
|
|
|
65
66
|
childIdPrefix: `${entityId}/`,
|
|
66
67
|
cellResources: {
|
|
67
68
|
stateRef,
|
|
69
|
+
latestTransitionRef,
|
|
68
70
|
stoppedRef,
|
|
69
71
|
eventQueue
|
|
70
72
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ import { Durability, DurabilityCommit, FinalContext, GuardPredicate, HandlerCont
|
|
|
6
6
|
import { ProcessEventResult } from "./internal/transition.js";
|
|
7
7
|
import { ActorClient, ActorLifecycle, ActorRef, ActorRefSync, ActorScope, ActorSystem, ActorSystemKey, ActorSystemService, Default, SystemEvent, SystemEventListener, SystemSpawnOptions, TransitionInfo, actorSystemKey } from "./actor.js";
|
|
8
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 };
|
|
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 };
|
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, tracingInspector } from "./inspection.js";
|
|
3
|
+
import { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, makeInspectorHub, 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, 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, makeInspectorHub, simulate, tracingInspector };
|
package/dist/inspection.d.ts
CHANGED
|
@@ -124,6 +124,13 @@ 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
|
+
}
|
|
127
134
|
declare const Inspector_base: Context.ServiceClass<Inspector, "effect-machine/inspection/Inspector", InspectorService<any, any>>;
|
|
128
135
|
/**
|
|
129
136
|
* Inspector service tag - optional service for machine introspection
|
|
@@ -149,6 +156,17 @@ declare const makeInspectorEffect: <S = {
|
|
|
149
156
|
readonly _tag: string;
|
|
150
157
|
}>(onInspect: (event: InspectionEvent<ResolveType<S>, ResolveType<E>>) => Effect.Effect<void>) => InspectorService<ResolveType<S>, ResolveType<E>>;
|
|
151
158
|
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>>;
|
|
152
170
|
interface TracingInspectorOptions<S, E> {
|
|
153
171
|
readonly spanName?: string | ((event: InspectionEvent<S, E>) => string);
|
|
154
172
|
readonly attributes?: (event: InspectionEvent<S, E>) => Readonly<Record<string, string | number | boolean>>;
|
|
@@ -176,4 +194,4 @@ declare const collectingInspector: <S extends {
|
|
|
176
194
|
readonly _tag: string;
|
|
177
195
|
}>(events: InspectionEvent<S, E>[]) => InspectorService<S, E>;
|
|
178
196
|
//#endregion
|
|
179
|
-
export { AnyInspectionEvent, EffectEvent, ErrorEvent, EventReceivedEvent, GuardEvent, InspectionEvent, Inspector, InspectorHandler, InspectorService, OperationEvent, SpawnEvent, StopEvent, TaskEvent, TracingInspectorOptions, TransitionEvent, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
|
|
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 };
|
package/dist/inspection.js
CHANGED
|
@@ -15,15 +15,39 @@ var Inspector = class extends Context.Service()("effect-machine/inspection/Inspe
|
|
|
15
15
|
*/
|
|
16
16
|
const makeInspector = (onInspect) => ({ onInspect });
|
|
17
17
|
const makeInspectorEffect = (onInspect) => ({ onInspect });
|
|
18
|
-
const inspectionEffect = (inspector, event) => {
|
|
18
|
+
const inspectionEffect = (inspector, event) => Effect.suspend(() => {
|
|
19
19
|
const result = inspector.onInspect(event);
|
|
20
20
|
if (Effect.isEffect(result)) return result;
|
|
21
21
|
return Effect.void;
|
|
22
|
-
};
|
|
22
|
+
});
|
|
23
23
|
const combineInspectors = (...inspectors) => ({ onInspect: (event) => Effect.forEach(inspectors, (inspector) => inspectionEffect(inspector, event).pipe(Effect.ignoreCause), {
|
|
24
24
|
concurrency: 16,
|
|
25
25
|
discard: true
|
|
26
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
|
+
};
|
|
27
51
|
const inspectionSpanName = (event) => {
|
|
28
52
|
switch (event.type) {
|
|
29
53
|
case "@machine.spawn": return `Machine.inspect ${event.initialState._tag}`;
|
|
@@ -157,4 +181,4 @@ const collectingInspector = (events) => ({ onInspect: (event) => {
|
|
|
157
181
|
events.push(event);
|
|
158
182
|
} });
|
|
159
183
|
//#endregion
|
|
160
|
-
export { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, tracingInspector };
|
|
184
|
+
export { Inspector, collectingInspector, combineInspectors, consoleInspector, makeInspector, makeInspectorEffect, makeInspectorHub, tracingInspector };
|
package/dist/internal/runtime.js
CHANGED
|
@@ -55,7 +55,7 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
55
55
|
const generation = config.generation ?? 0;
|
|
56
56
|
const services = yield* Effect.context();
|
|
57
57
|
const fork = Effect.runForkWith(services);
|
|
58
|
-
const { stateRef, stoppedRef, eventQueue } = config.cellResources;
|
|
58
|
+
const { stateRef, latestTransitionRef, stoppedRef, eventQueue } = config.cellResources;
|
|
59
59
|
const pendingRequests = /* @__PURE__ */ new Set();
|
|
60
60
|
const exitDeferred = yield* Deferred.make();
|
|
61
61
|
const actorScope = yield* Scope.make();
|
|
@@ -72,6 +72,8 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
72
72
|
let spawn = defaultSpawn;
|
|
73
73
|
if (onChildSpawned !== void 0) spawn = (childId, childMachine) => defaultSpawn(childId, childMachine).pipe(Effect.tap((child) => onChildSpawned(childId, child)));
|
|
74
74
|
const self = {
|
|
75
|
+
state: stateRef,
|
|
76
|
+
latestTransition: latestTransitionRef,
|
|
75
77
|
send: selfSend,
|
|
76
78
|
spawn,
|
|
77
79
|
reply: (value) => Effect.sync(() => {
|
|
@@ -117,6 +119,12 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
117
119
|
else initialResult = initialProcessing;
|
|
118
120
|
if (initialResult.transitioned) {
|
|
119
121
|
yield* SubscriptionRef.set(stateRef, initialResult.newState);
|
|
122
|
+
const latest = initialResult.transitions.at(-1);
|
|
123
|
+
if (latest !== void 0) yield* SubscriptionRef.set(latestTransitionRef, {
|
|
124
|
+
fromState: latest.previousState,
|
|
125
|
+
toState: latest.newState,
|
|
126
|
+
event: latest.event
|
|
127
|
+
});
|
|
120
128
|
if (lifecycle?.onStateChange !== void 0) {
|
|
121
129
|
const stateChange = lifecycle.onStateChange(initialResult, initEvent);
|
|
122
130
|
if (isEffect(stateChange)) yield* stateChange;
|
|
@@ -164,7 +172,7 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
164
172
|
return Effect.void;
|
|
165
173
|
})), Effect.asVoid)
|
|
166
174
|
};
|
|
167
|
-
const loopFiber = yield* runtimeEventLoop(machine, stateRef, eventQueue, pendingRequests, stoppedRef, self, stateScopeRef, actorId, generation, system, exitDeferred, augmentedHooks, deferredReplyRef, lifecycle, fork).pipe(Effect.provide(services), Effect.forkDetach);
|
|
175
|
+
const loopFiber = yield* runtimeEventLoop(machine, stateRef, latestTransitionRef, eventQueue, pendingRequests, stoppedRef, self, stateScopeRef, actorId, generation, system, exitDeferred, augmentedHooks, deferredReplyRef, lifecycle, fork).pipe(Effect.provide(services), Effect.forkDetach);
|
|
168
176
|
loopFiberRef.current = loopFiber;
|
|
169
177
|
if (backgroundFibers.length > 0) yield* Effect.raceAll(backgroundFibers.map((fiber) => Fiber.await(fiber).pipe(Effect.flatMap((exit) => {
|
|
170
178
|
if (exit._tag === "Failure" && !Cause.hasInterruptsOnly(exit.cause)) return setExit(RuntimeExit.Defect(exit.cause, "background")).pipe(Effect.andThen(Ref.set(stoppedRef, true)), Effect.andThen(Fiber.interrupt(loopFiber)));
|
|
@@ -190,7 +198,7 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
190
198
|
}).pipe(Effect.asVoid);
|
|
191
199
|
if (config.skipFinalizer !== true) yield* Effect.addFinalizer(() => stop);
|
|
192
200
|
return {
|
|
193
|
-
...makeHandle(actorId, stateRef, stoppedRef, eventQueue, pendingRequests, exitDeferred),
|
|
201
|
+
...makeHandle(actorId, stateRef, latestTransitionRef, stoppedRef, eventQueue, pendingRequests, exitDeferred),
|
|
194
202
|
stop: stop.pipe(Effect.provide(services)),
|
|
195
203
|
start: start.pipe(Effect.provide(services))
|
|
196
204
|
};
|
|
@@ -199,7 +207,7 @@ const createRuntime = Effect.fn("effect-machine.runtime.create")(function* (mach
|
|
|
199
207
|
* Build the runtime handle.
|
|
200
208
|
* Shared between initial-final and normal paths.
|
|
201
209
|
*/
|
|
202
|
-
const makeHandle = (actorId, stateRef, stoppedRef, eventQueue, pendingRequests, exitDeferred) => {
|
|
210
|
+
const makeHandle = (actorId, stateRef, latestTransitionRef, stoppedRef, eventQueue, pendingRequests, exitDeferred) => {
|
|
203
211
|
const track = (deferred, settle) => {
|
|
204
212
|
pendingRequests.add(settle);
|
|
205
213
|
return Deferred.await(deferred).pipe(Effect.ensuring(Effect.sync(() => pendingRequests.delete(settle))));
|
|
@@ -261,6 +269,7 @@ const makeHandle = (actorId, stateRef, stoppedRef, eventQueue, pendingRequests,
|
|
|
261
269
|
},
|
|
262
270
|
getState: SubscriptionRef.get(stateRef),
|
|
263
271
|
stateRef,
|
|
272
|
+
latestTransitionRef,
|
|
264
273
|
stop: Effect.void,
|
|
265
274
|
start: Effect.void,
|
|
266
275
|
settlePendingRequests: settlePendingRequests(pendingRequests, actorId),
|
|
@@ -272,7 +281,7 @@ const settlePendingRequests = (pendingRequests, actorId) => Effect.gen(function*
|
|
|
272
281
|
for (const settle of pendingRequests) yield* settle(error);
|
|
273
282
|
pendingRequests.clear();
|
|
274
283
|
});
|
|
275
|
-
const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function* (machine, stateRef, eventQueue, pendingRequests, stoppedRef, self, stateScopeRef, actorId, generation, system, exitDeferred, hooks, deferredReplyRef, lifecycle, fork) {
|
|
284
|
+
const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function* (machine, stateRef, latestTransitionRef, eventQueue, pendingRequests, stoppedRef, self, stateScopeRef, actorId, generation, system, exitDeferred, hooks, deferredReplyRef, lifecycle, fork) {
|
|
276
285
|
const forkEffect = fork ?? Effect.runFork;
|
|
277
286
|
/** Set the exit deferred exactly once. */
|
|
278
287
|
const setExit = (exit) => Deferred.succeed(exitDeferred, exit).pipe(Effect.asVoid);
|
|
@@ -320,7 +329,15 @@ const runtimeEventLoop = Effect.fn("effect-machine.runtime.eventLoop")(function*
|
|
|
320
329
|
let result;
|
|
321
330
|
if (isEffect(processing)) result = yield* processing;
|
|
322
331
|
else result = processing;
|
|
323
|
-
if (result.transitioned)
|
|
332
|
+
if (result.transitioned) {
|
|
333
|
+
yield* SubscriptionRef.set(stateRef, result.newState);
|
|
334
|
+
const latest = result.transitions.at(-1);
|
|
335
|
+
if (latest !== void 0) yield* SubscriptionRef.set(latestTransitionRef, {
|
|
336
|
+
fromState: latest.previousState,
|
|
337
|
+
toState: latest.newState,
|
|
338
|
+
event: latest.event
|
|
339
|
+
});
|
|
340
|
+
}
|
|
324
341
|
if (lifecycle?.onStateChange !== void 0 && result.transitioned) {
|
|
325
342
|
const stateChange = lifecycle.onStateChange(result, event);
|
|
326
343
|
if (isEffect(stateChange)) yield* stateChange;
|
package/dist/machine.d.ts
CHANGED
|
@@ -3,15 +3,19 @@ import { BrandedEvent, BrandedState, ExtractReply, TaggedOrConstructor } from ".
|
|
|
3
3
|
import { MachineEventSchema, MachineStateSchema, VariantsUnion } from "./schema.js";
|
|
4
4
|
import { ActorStoppedError, DuplicateActorError } from "./errors.js";
|
|
5
5
|
import { Supervision } from "./supervision.js";
|
|
6
|
-
import { ActorRef, ActorSystemService } from "./actor.js";
|
|
7
|
-
import { Duration, Effect, Option, Schema, Scope } from "effect";
|
|
6
|
+
import { ActorRef, ActorSystemService, TransitionInfo } from "./actor.js";
|
|
7
|
+
import { Duration, Effect, Option, Schema, Scope, SubscriptionRef } from "effect";
|
|
8
8
|
declare namespace machine_d_exports {
|
|
9
9
|
export { DeferReplyResult, Durability, DurabilityCommit, FinalContext, GuardPredicate, HandlerContext, InputMakeConfig, Lifecycle, Machine, MachineRef, MakeConfig, Recovery, RecoveryContext, ReplayOptions, ReplyResult, SpawnOptions, StateEffectHandler, StateHandlerContext, TaskOptions, TimeoutConfig, TransitionHandler, deferReply, make, replay, reply, run, scoped, spawn };
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Self reference for sending events back to the machine
|
|
13
13
|
*/
|
|
14
|
-
interface MachineRef<Event> {
|
|
14
|
+
interface MachineRef<Event, State = never> {
|
|
15
|
+
/** Actor-owned current state. Consumers must treat this ref as read-only. */
|
|
16
|
+
readonly state: SubscriptionRef.SubscriptionRef<State>;
|
|
17
|
+
/** Actor-owned latest accepted transition. Consumers must treat this ref as read-only. */
|
|
18
|
+
readonly latestTransition: SubscriptionRef.SubscriptionRef<TransitionInfo<State, Event> | undefined>;
|
|
15
19
|
readonly send: (event: Event) => Effect.Effect<void>;
|
|
16
20
|
readonly spawn: <S2 extends {
|
|
17
21
|
readonly _tag: string;
|
|
@@ -40,7 +44,7 @@ interface StateHandlerContext<State, Event> {
|
|
|
40
44
|
readonly generation: number;
|
|
41
45
|
readonly state: State;
|
|
42
46
|
readonly event: Event;
|
|
43
|
-
readonly self: MachineRef<Event>;
|
|
47
|
+
readonly self: MachineRef<Event, State>;
|
|
44
48
|
readonly system: ActorSystemService;
|
|
45
49
|
}
|
|
46
50
|
/**
|