effect-machine 0.3.2 → 0.6.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 +60 -0
- package/dist/actor.d.ts +44 -3
- package/dist/actor.js +109 -35
- package/dist/cluster/entity-machine.js +16 -10
- package/dist/index.d.ts +3 -3
- package/dist/internal/transition.d.ts +5 -4
- package/dist/internal/transition.js +13 -10
- package/dist/internal/utils.d.ts +9 -1
- package/dist/internal/utils.js +22 -2
- package/dist/machine.d.ts +8 -1
- package/dist/persistence/adapter.d.ts +1 -1
- package/dist/persistence/persistent-actor.js +40 -21
- package/dist/slot.d.ts +2 -0
- package/dist/testing.js +11 -4
- package/package.json +11 -8
package/README.md
CHANGED
|
@@ -183,6 +183,53 @@ machine.task(State.Loading, ({ effects, state }) => effects.fetchData({ url: sta
|
|
|
183
183
|
});
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
+
### Child Actors
|
|
187
|
+
|
|
188
|
+
Spawn children from `.spawn()` handlers with `self.spawn`. Children are state-scoped — auto-stopped on state exit:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
machine
|
|
192
|
+
.spawn(State.Active, ({ self }) =>
|
|
193
|
+
Effect.gen(function* () {
|
|
194
|
+
const child = yield* self.spawn("worker-1", workerMachine).pipe(Effect.orDie);
|
|
195
|
+
yield* child.send(WorkerEvent.Start);
|
|
196
|
+
// child auto-stopped when parent exits Active state
|
|
197
|
+
}),
|
|
198
|
+
)
|
|
199
|
+
.build();
|
|
200
|
+
|
|
201
|
+
// Access children externally via actor.system
|
|
202
|
+
const parent = yield * Machine.spawn(parentMachine);
|
|
203
|
+
yield * parent.send(Event.Activate);
|
|
204
|
+
const child = yield * parent.system.get("worker-1"); // Option<ActorRef>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Every actor always has a system — `Machine.spawn` creates an implicit one if no `ActorSystem` is in context.
|
|
208
|
+
|
|
209
|
+
### System Observation
|
|
210
|
+
|
|
211
|
+
React to actors joining and leaving the system:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const system = yield * ActorSystemService;
|
|
215
|
+
|
|
216
|
+
// Sync callback — like ActorRef.subscribe
|
|
217
|
+
const unsub = system.subscribe((event) => {
|
|
218
|
+
// event._tag: "ActorSpawned" | "ActorStopped"
|
|
219
|
+
console.log(`${event._tag}: ${event.id}`);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Sync snapshot of all registered actors
|
|
223
|
+
const actors = system.actors; // ReadonlyMap<string, ActorRef>
|
|
224
|
+
|
|
225
|
+
// Async stream (each subscriber gets own queue)
|
|
226
|
+
yield *
|
|
227
|
+
system.events.pipe(
|
|
228
|
+
Stream.tap((e) => Effect.log(e._tag, e.id)),
|
|
229
|
+
Stream.runDrain,
|
|
230
|
+
);
|
|
231
|
+
```
|
|
232
|
+
|
|
186
233
|
### Testing
|
|
187
234
|
|
|
188
235
|
Test transitions without actors:
|
|
@@ -273,6 +320,19 @@ See the [primer](./primer/) for comprehensive documentation:
|
|
|
273
320
|
| `actor.awaitFinal` | Wait final state |
|
|
274
321
|
| `actor.sendAndWait(ev, State.X)` | Send + wait for state |
|
|
275
322
|
| `actor.subscribe(fn)` | Sync callback |
|
|
323
|
+
| `actor.system` | Access the actor's `ActorSystem` |
|
|
324
|
+
| `actor.children` | Child actors (`ReadonlyMap`) |
|
|
325
|
+
|
|
326
|
+
### ActorSystem
|
|
327
|
+
|
|
328
|
+
| Method / Property | Description |
|
|
329
|
+
| ---------------------- | ------------------------------------------- |
|
|
330
|
+
| `system.spawn(id, m)` | Spawn actor |
|
|
331
|
+
| `system.get(id)` | Get actor by ID |
|
|
332
|
+
| `system.stop(id)` | Stop actor by ID |
|
|
333
|
+
| `system.actors` | Sync snapshot of all actors (`ReadonlyMap`) |
|
|
334
|
+
| `system.subscribe(fn)` | Sync callback for spawn/stop events |
|
|
335
|
+
| `system.events` | Async `Stream<SystemEvent>` for spawn/stop |
|
|
276
336
|
|
|
277
337
|
## License
|
|
278
338
|
|
package/dist/actor.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { EffectsDef, GuardsDef, MachineContext } from "./slot.js";
|
|
2
2
|
import { PersistentMachine } from "./persistence/persistent-machine.js";
|
|
3
|
+
import { DuplicateActorError } from "./errors.js";
|
|
3
4
|
import { ProcessEventError, ProcessEventHooks, ProcessEventResult, processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
|
|
4
5
|
import { PersistentActorRef } from "./persistence/persistent-actor.js";
|
|
5
|
-
import { DuplicateActorError } from "./errors.js";
|
|
6
6
|
import { ActorMetadata, PersistenceAdapterTag, PersistenceError, RestoreResult, VersionConflictError } from "./persistence/adapter.js";
|
|
7
7
|
import { BuiltMachine, Machine, MachineRef } from "./machine.js";
|
|
8
8
|
import { Context, Effect, Layer, Option, Queue, Ref, Scope, Stream, SubscriptionRef } from "effect";
|
|
@@ -101,11 +101,37 @@ interface ActorRef<State extends {
|
|
|
101
101
|
* Returns unsubscribe function
|
|
102
102
|
*/
|
|
103
103
|
readonly subscribe: (fn: (state: State) => void) => () => void;
|
|
104
|
+
/**
|
|
105
|
+
* The actor system this actor belongs to.
|
|
106
|
+
* Every actor always has a system — either inherited from context or implicitly created.
|
|
107
|
+
*/
|
|
108
|
+
readonly system: ActorSystem;
|
|
109
|
+
/**
|
|
110
|
+
* Child actors spawned via `self.spawn` in this actor's handlers.
|
|
111
|
+
* State-scoped children are auto-removed on state exit.
|
|
112
|
+
*/
|
|
113
|
+
readonly children: ReadonlyMap<string, ActorRef<AnyState, unknown>>;
|
|
104
114
|
}
|
|
105
115
|
/** Base type for stored actors (internal) */
|
|
106
116
|
type AnyState = {
|
|
107
117
|
readonly _tag: string;
|
|
108
118
|
};
|
|
119
|
+
/**
|
|
120
|
+
* Events emitted by the ActorSystem when actors are spawned or stopped.
|
|
121
|
+
*/
|
|
122
|
+
type SystemEvent = {
|
|
123
|
+
readonly _tag: "ActorSpawned";
|
|
124
|
+
readonly id: string;
|
|
125
|
+
readonly actor: ActorRef<AnyState, unknown>;
|
|
126
|
+
} | {
|
|
127
|
+
readonly _tag: "ActorStopped";
|
|
128
|
+
readonly id: string;
|
|
129
|
+
readonly actor: ActorRef<AnyState, unknown>;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Listener callback for system events.
|
|
133
|
+
*/
|
|
134
|
+
type SystemEventListener = (event: SystemEvent) => void;
|
|
109
135
|
/**
|
|
110
136
|
* Actor system for managing actor lifecycles
|
|
111
137
|
*/
|
|
@@ -169,6 +195,21 @@ interface ActorSystem {
|
|
|
169
195
|
* Stop an actor by ID
|
|
170
196
|
*/
|
|
171
197
|
readonly stop: (id: string) => Effect.Effect<boolean>;
|
|
198
|
+
/**
|
|
199
|
+
* Async stream of system events (actor spawned/stopped).
|
|
200
|
+
* Each subscriber gets their own queue — late subscribers miss prior events.
|
|
201
|
+
*/
|
|
202
|
+
readonly events: Stream.Stream<SystemEvent>;
|
|
203
|
+
/**
|
|
204
|
+
* Sync snapshot of all currently registered actors.
|
|
205
|
+
* Returns a new Map on each access (not live).
|
|
206
|
+
*/
|
|
207
|
+
readonly actors: ReadonlyMap<string, ActorRef<AnyState, unknown>>;
|
|
208
|
+
/**
|
|
209
|
+
* Subscribe to system events synchronously.
|
|
210
|
+
* Returns an unsubscribe function.
|
|
211
|
+
*/
|
|
212
|
+
readonly subscribe: (fn: SystemEventListener) => () => void;
|
|
172
213
|
/**
|
|
173
214
|
* List all persisted actor metadata.
|
|
174
215
|
* Returns empty array if adapter doesn't support registry.
|
|
@@ -234,7 +275,7 @@ declare const buildActorRefCore: <S extends {
|
|
|
234
275
|
readonly _tag: string;
|
|
235
276
|
}, E extends {
|
|
236
277
|
readonly _tag: string;
|
|
237
|
-
}, 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
|
|
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>;
|
|
238
279
|
/**
|
|
239
280
|
* Create and start an actor for a machine
|
|
240
281
|
*/
|
|
@@ -248,4 +289,4 @@ declare const createActor: <S extends {
|
|
|
248
289
|
*/
|
|
249
290
|
declare const Default: Layer.Layer<ActorSystem, never, never>;
|
|
250
291
|
//#endregion
|
|
251
|
-
export { ActorRef, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
|
|
292
|
+
export { ActorRef, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, SystemEvent, SystemEventListener, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
|
package/dist/actor.js
CHANGED
|
@@ -6,10 +6,18 @@ import { processEventCore, resolveTransition, runSpawnEffects } from "./internal
|
|
|
6
6
|
import { emitWithTimestamp } from "./internal/inspection.js";
|
|
7
7
|
import { PersistenceAdapterTag, PersistenceError } from "./persistence/adapter.js";
|
|
8
8
|
import { createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
|
|
9
|
-
import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, Queue, Ref, Runtime, Scope, SubscriptionRef } from "effect";
|
|
9
|
+
import { Cause, Context, Deferred, Effect, Exit, Fiber, Layer, MutableHashMap, Option, PubSub, Queue, Ref, Runtime, Scope, Stream, SubscriptionRef } from "effect";
|
|
10
10
|
|
|
11
11
|
//#region src/actor.ts
|
|
12
12
|
/**
|
|
13
|
+
* Actor system: spawning, lifecycle, and event processing.
|
|
14
|
+
*
|
|
15
|
+
* Combines:
|
|
16
|
+
* - ActorRef interface (running actor handle)
|
|
17
|
+
* - ActorSystem service (spawn/stop/get actors)
|
|
18
|
+
* - Actor creation and event loop
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
13
21
|
* ActorSystem service tag
|
|
14
22
|
*/
|
|
15
23
|
const ActorSystem = Context.GenericTag("@effect/machine/ActorSystem");
|
|
@@ -24,7 +32,7 @@ const notifyListeners = (listeners, state) => {
|
|
|
24
32
|
/**
|
|
25
33
|
* Build core ActorRef methods shared between regular and persistent actors.
|
|
26
34
|
*/
|
|
27
|
-
const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop) => {
|
|
35
|
+
const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop, system, childrenMap) => {
|
|
28
36
|
const send = Effect.fn("effect-machine.actor.send")(function* (event) {
|
|
29
37
|
if (yield* Ref.get(stoppedRef)) return;
|
|
30
38
|
yield* Queue.offer(eventQueue, event);
|
|
@@ -88,7 +96,9 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
88
96
|
return () => {
|
|
89
97
|
listeners.delete(fn);
|
|
90
98
|
};
|
|
91
|
-
}
|
|
99
|
+
},
|
|
100
|
+
system,
|
|
101
|
+
children: childrenMap
|
|
92
102
|
};
|
|
93
103
|
};
|
|
94
104
|
/**
|
|
@@ -96,13 +106,34 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
96
106
|
*/
|
|
97
107
|
const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine) {
|
|
98
108
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
|
|
109
|
+
const existingSystem = yield* Effect.serviceOption(ActorSystem);
|
|
110
|
+
let system;
|
|
111
|
+
let implicitSystemScope;
|
|
112
|
+
if (Option.isSome(existingSystem)) system = existingSystem.value;
|
|
113
|
+
else {
|
|
114
|
+
const scope = yield* Scope.make();
|
|
115
|
+
system = yield* make().pipe(Effect.provideService(Scope.Scope, scope));
|
|
116
|
+
implicitSystemScope = scope;
|
|
117
|
+
}
|
|
99
118
|
const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
|
|
100
119
|
const eventQueue = yield* Queue.unbounded();
|
|
101
120
|
const stoppedRef = yield* Ref.make(false);
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
121
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
122
|
+
const self = {
|
|
123
|
+
send: Effect.fn("effect-machine.actor.self.send")(function* (event) {
|
|
124
|
+
if (yield* Ref.get(stoppedRef)) return;
|
|
125
|
+
yield* Queue.offer(eventQueue, event);
|
|
126
|
+
}),
|
|
127
|
+
spawn: (childId, childMachine) => Effect.gen(function* () {
|
|
128
|
+
const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
|
|
129
|
+
childrenMap.set(childId, child);
|
|
130
|
+
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
131
|
+
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
|
|
132
|
+
childrenMap.delete(childId);
|
|
133
|
+
}));
|
|
134
|
+
return child;
|
|
135
|
+
})
|
|
136
|
+
};
|
|
106
137
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", machine.initial._tag);
|
|
107
138
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
108
139
|
type: "@machine.spawn",
|
|
@@ -117,7 +148,8 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
117
148
|
const ctx = {
|
|
118
149
|
state: machine.initial,
|
|
119
150
|
event: initEvent,
|
|
120
|
-
self
|
|
151
|
+
self,
|
|
152
|
+
system
|
|
121
153
|
};
|
|
122
154
|
const { effects: effectSlots } = machine._slots;
|
|
123
155
|
for (const bg of machine.backgroundEffects) {
|
|
@@ -125,12 +157,13 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
125
157
|
state: machine.initial,
|
|
126
158
|
event: initEvent,
|
|
127
159
|
self,
|
|
128
|
-
effects: effectSlots
|
|
160
|
+
effects: effectSlots,
|
|
161
|
+
system
|
|
129
162
|
}).pipe(Effect.provideService(machine.Context, ctx)));
|
|
130
163
|
backgroundFibers.push(fiber);
|
|
131
164
|
}
|
|
132
165
|
const stateScopeRef = { current: yield* Scope.make() };
|
|
133
|
-
yield* runSpawnEffectsWithInspection(machine, machine.initial, initEvent, self, stateScopeRef.current, id, inspectorValue);
|
|
166
|
+
yield* runSpawnEffectsWithInspection(machine, machine.initial, initEvent, self, stateScopeRef.current, id, inspectorValue, system);
|
|
134
167
|
if (machine.finalStates.has(machine.initial._tag)) {
|
|
135
168
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
136
169
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
@@ -141,9 +174,10 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
141
174
|
timestamp
|
|
142
175
|
}));
|
|
143
176
|
yield* Ref.set(stoppedRef, true);
|
|
144
|
-
|
|
177
|
+
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
178
|
+
return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap);
|
|
145
179
|
}
|
|
146
|
-
const loopFiber = yield* Effect.forkDaemon(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue));
|
|
180
|
+
const loopFiber = yield* Effect.forkDaemon(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue, system));
|
|
147
181
|
return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
|
|
148
182
|
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
149
183
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
@@ -156,12 +190,13 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
156
190
|
yield* Fiber.interrupt(loopFiber);
|
|
157
191
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
158
192
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
159
|
-
|
|
193
|
+
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
194
|
+
}).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system, childrenMap);
|
|
160
195
|
});
|
|
161
196
|
/**
|
|
162
197
|
* Main event loop for the actor
|
|
163
198
|
*/
|
|
164
|
-
const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, actorId, inspector) {
|
|
199
|
+
const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, actorId, inspector, system) {
|
|
165
200
|
while (true) {
|
|
166
201
|
const event = yield* Queue.take(eventQueue);
|
|
167
202
|
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
@@ -169,7 +204,7 @@ const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine
|
|
|
169
204
|
"effect_machine.actor.id": actorId,
|
|
170
205
|
"effect_machine.state.current": currentState._tag,
|
|
171
206
|
"effect_machine.event.type": event._tag
|
|
172
|
-
} })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector))) {
|
|
207
|
+
} })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system))) {
|
|
173
208
|
yield* Ref.set(stoppedRef, true);
|
|
174
209
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
175
210
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
@@ -181,7 +216,7 @@ const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine
|
|
|
181
216
|
* Process a single event, returning true if the actor should stop.
|
|
182
217
|
* Wraps processEventCore with actor-specific concerns (inspection, listeners, state ref).
|
|
183
218
|
*/
|
|
184
|
-
const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector) {
|
|
219
|
+
const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system) {
|
|
185
220
|
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
186
221
|
type: "@machine.event",
|
|
187
222
|
actorId,
|
|
@@ -189,7 +224,7 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
|
|
|
189
224
|
event,
|
|
190
225
|
timestamp
|
|
191
226
|
}));
|
|
192
|
-
const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, inspector === void 0 ? void 0 : {
|
|
227
|
+
const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, inspector === void 0 ? void 0 : {
|
|
193
228
|
onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
194
229
|
type: "@machine.effect",
|
|
195
230
|
actorId,
|
|
@@ -242,7 +277,7 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
|
|
|
242
277
|
* Wraps the core runSpawnEffects with inspection events and spans.
|
|
243
278
|
* @internal
|
|
244
279
|
*/
|
|
245
|
-
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector) {
|
|
280
|
+
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector, system) {
|
|
246
281
|
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
247
282
|
type: "@machine.effect",
|
|
248
283
|
actorId,
|
|
@@ -250,7 +285,7 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffec
|
|
|
250
285
|
state,
|
|
251
286
|
timestamp
|
|
252
287
|
}));
|
|
253
|
-
yield* runSpawnEffects(machine, state, event, self, stateScope, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
288
|
+
yield* runSpawnEffects(machine, state, event, self, stateScope, system, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
254
289
|
type: "@machine.error",
|
|
255
290
|
actorId,
|
|
256
291
|
phase: info.phase,
|
|
@@ -260,39 +295,58 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffec
|
|
|
260
295
|
timestamp
|
|
261
296
|
})));
|
|
262
297
|
});
|
|
263
|
-
/**
|
|
264
|
-
|
|
265
|
-
|
|
298
|
+
/** Notify all system event listeners (sync). */
|
|
299
|
+
const notifySystemListeners = (listeners, event) => {
|
|
300
|
+
for (const listener of listeners) try {
|
|
301
|
+
listener(event);
|
|
302
|
+
} catch {}
|
|
303
|
+
};
|
|
266
304
|
const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
267
|
-
const
|
|
305
|
+
const actorsMap = MutableHashMap.empty();
|
|
268
306
|
const withSpawnGate = (yield* Effect.makeSemaphore(1)).withPermits(1);
|
|
307
|
+
const eventPubSub = yield* PubSub.unbounded();
|
|
308
|
+
const eventListeners = /* @__PURE__ */ new Set();
|
|
309
|
+
const emitSystemEvent = (event) => Effect.sync(() => notifySystemListeners(eventListeners, event)).pipe(Effect.andThen(PubSub.publish(eventPubSub, event)), Effect.catchAllCause(() => Effect.void), Effect.asVoid);
|
|
269
310
|
yield* Effect.addFinalizer(() => {
|
|
270
311
|
const stops = [];
|
|
271
|
-
MutableHashMap.forEach(
|
|
312
|
+
MutableHashMap.forEach(actorsMap, (actor) => {
|
|
272
313
|
stops.push(actor.stop);
|
|
273
314
|
});
|
|
274
|
-
return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.asVoid);
|
|
315
|
+
return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.andThen(PubSub.shutdown(eventPubSub)), Effect.asVoid);
|
|
275
316
|
});
|
|
276
317
|
/** Check for duplicate ID, register actor, attach scope cleanup if available */
|
|
277
318
|
const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* (id, actor) {
|
|
278
|
-
if (MutableHashMap.has(
|
|
319
|
+
if (MutableHashMap.has(actorsMap, id)) {
|
|
279
320
|
yield* actor.stop;
|
|
280
321
|
return yield* new DuplicateActorError({ actorId: id });
|
|
281
322
|
}
|
|
282
|
-
|
|
323
|
+
const actorRef = actor;
|
|
324
|
+
MutableHashMap.set(actorsMap, id, actorRef);
|
|
325
|
+
yield* emitSystemEvent({
|
|
326
|
+
_tag: "ActorSpawned",
|
|
327
|
+
id,
|
|
328
|
+
actor: actorRef
|
|
329
|
+
});
|
|
283
330
|
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
284
331
|
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.gen(function* () {
|
|
332
|
+
if (MutableHashMap.has(actorsMap, id)) {
|
|
333
|
+
yield* emitSystemEvent({
|
|
334
|
+
_tag: "ActorStopped",
|
|
335
|
+
id,
|
|
336
|
+
actor: actorRef
|
|
337
|
+
});
|
|
338
|
+
MutableHashMap.remove(actorsMap, id);
|
|
339
|
+
}
|
|
285
340
|
yield* actor.stop;
|
|
286
|
-
MutableHashMap.remove(actors, id);
|
|
287
341
|
}));
|
|
288
342
|
return actor;
|
|
289
343
|
});
|
|
290
344
|
const spawnRegular = Effect.fn("effect-machine.actorSystem.spawnRegular")(function* (id, built) {
|
|
291
|
-
if (MutableHashMap.has(
|
|
345
|
+
if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
|
|
292
346
|
return yield* registerActor(id, yield* createActor(id, built._inner));
|
|
293
347
|
});
|
|
294
348
|
const spawnPersistent = Effect.fn("effect-machine.actorSystem.spawnPersistent")(function* (id, persistentMachine) {
|
|
295
|
-
if (MutableHashMap.has(
|
|
349
|
+
if (MutableHashMap.has(actorsMap, id)) return yield* new DuplicateActorError({ actorId: id });
|
|
296
350
|
const adapter = yield* PersistenceAdapterTag;
|
|
297
351
|
const maybeSnapshot = yield* adapter.loadSnapshot(id, persistentMachine.persistence.stateSchema);
|
|
298
352
|
return yield* registerActor(id, yield* createPersistentActor(id, persistentMachine, maybeSnapshot, yield* adapter.loadEvents(id, persistentMachine.persistence.eventSchema, Option.isSome(maybeSnapshot) ? maybeSnapshot.value.version : void 0)));
|
|
@@ -311,13 +365,19 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
311
365
|
});
|
|
312
366
|
const restore = (id, persistentMachine) => withSpawnGate(restoreImpl(id, persistentMachine));
|
|
313
367
|
const get = Effect.fn("effect-machine.actorSystem.get")(function* (id) {
|
|
314
|
-
return yield* Effect.sync(() => MutableHashMap.get(
|
|
368
|
+
return yield* Effect.sync(() => MutableHashMap.get(actorsMap, id));
|
|
315
369
|
});
|
|
316
370
|
const stop = Effect.fn("effect-machine.actorSystem.stop")(function* (id) {
|
|
317
|
-
const maybeActor = MutableHashMap.get(
|
|
371
|
+
const maybeActor = MutableHashMap.get(actorsMap, id);
|
|
318
372
|
if (Option.isNone(maybeActor)) return false;
|
|
319
|
-
|
|
320
|
-
MutableHashMap.remove(
|
|
373
|
+
const actor = maybeActor.value;
|
|
374
|
+
MutableHashMap.remove(actorsMap, id);
|
|
375
|
+
yield* emitSystemEvent({
|
|
376
|
+
_tag: "ActorStopped",
|
|
377
|
+
id,
|
|
378
|
+
actor
|
|
379
|
+
});
|
|
380
|
+
yield* actor.stop;
|
|
321
381
|
return true;
|
|
322
382
|
});
|
|
323
383
|
const listPersisted = Effect.fn("effect-machine.actorSystem.listPersisted")(function* () {
|
|
@@ -329,7 +389,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
329
389
|
const restored = [];
|
|
330
390
|
const failed = [];
|
|
331
391
|
for (const id of ids) {
|
|
332
|
-
if (MutableHashMap.has(
|
|
392
|
+
if (MutableHashMap.has(actorsMap, id)) continue;
|
|
333
393
|
const result = yield* Effect.either(restore(id, persistentMachine));
|
|
334
394
|
if (result._tag === "Left") failed.push({
|
|
335
395
|
id,
|
|
@@ -371,6 +431,20 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
371
431
|
restore,
|
|
372
432
|
get,
|
|
373
433
|
stop,
|
|
434
|
+
events: Stream.fromPubSub(eventPubSub),
|
|
435
|
+
get actors() {
|
|
436
|
+
const snapshot = /* @__PURE__ */ new Map();
|
|
437
|
+
MutableHashMap.forEach(actorsMap, (actor, id) => {
|
|
438
|
+
snapshot.set(id, actor);
|
|
439
|
+
});
|
|
440
|
+
return snapshot;
|
|
441
|
+
},
|
|
442
|
+
subscribe: (fn) => {
|
|
443
|
+
eventListeners.add(fn);
|
|
444
|
+
return () => {
|
|
445
|
+
eventListeners.delete(fn);
|
|
446
|
+
};
|
|
447
|
+
},
|
|
374
448
|
listPersisted,
|
|
375
449
|
restoreMany,
|
|
376
450
|
restoreAll
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { processEventCore, runSpawnEffects } from "../internal/transition.js";
|
|
2
|
-
import "../actor.js";
|
|
3
|
-
import { Effect, Queue, Ref, Scope } from "effect";
|
|
2
|
+
import { ActorSystem } from "../actor.js";
|
|
3
|
+
import { Effect, Option, Queue, Ref, Scope } from "effect";
|
|
4
4
|
import { Entity } from "@effect/cluster";
|
|
5
5
|
|
|
6
6
|
//#region src/cluster/entity-machine.ts
|
|
@@ -13,8 +13,8 @@ import { Entity } from "@effect/cluster";
|
|
|
13
13
|
* Process a single event through the machine using shared core.
|
|
14
14
|
* Returns the new state after processing.
|
|
15
15
|
*/
|
|
16
|
-
const processEvent = Effect.fn("effect-machine.cluster.processEvent")(function* (machine, stateRef, event, self, stateScopeRef, hooks) {
|
|
17
|
-
const result = yield* processEventCore(machine, yield* Ref.get(stateRef), event, self, stateScopeRef, hooks);
|
|
16
|
+
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);
|
|
18
18
|
if (result.transitioned) yield* Ref.set(stateRef, result.newState);
|
|
19
19
|
return result.newState;
|
|
20
20
|
});
|
|
@@ -51,19 +51,25 @@ const EntityMachine = { layer: (entity, machine, options) => {
|
|
|
51
51
|
const layer = Effect.fn("effect-machine.cluster.layer")(function* () {
|
|
52
52
|
const entityId = yield* Effect.serviceOption(Entity.CurrentAddress).pipe(Effect.map((opt) => opt._tag === "Some" ? opt.value.entityId : ""));
|
|
53
53
|
const initialState = options?.initializeState !== void 0 ? options.initializeState(entityId) : machine.initial;
|
|
54
|
+
const existingSystem = yield* Effect.serviceOption(ActorSystem);
|
|
55
|
+
if (Option.isNone(existingSystem)) return yield* Effect.die("EntityMachine requires ActorSystem in context");
|
|
56
|
+
const system = existingSystem.value;
|
|
54
57
|
const internalQueue = yield* Queue.unbounded();
|
|
55
|
-
const self = {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
+
const self = {
|
|
59
|
+
send: Effect.fn("effect-machine.cluster.self.send")(function* (event) {
|
|
60
|
+
yield* Queue.offer(internalQueue, event);
|
|
61
|
+
}),
|
|
62
|
+
spawn: (childId, childMachine) => system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system))
|
|
63
|
+
};
|
|
58
64
|
const stateRef = yield* Ref.make(initialState);
|
|
59
65
|
const stateScopeRef = { current: yield* Scope.make() };
|
|
60
|
-
yield* runSpawnEffects(machine, initialState, { _tag: "$init" }, self, stateScopeRef.current, options?.hooks?.onError);
|
|
66
|
+
yield* runSpawnEffects(machine, initialState, { _tag: "$init" }, self, stateScopeRef.current, system, options?.hooks?.onError);
|
|
61
67
|
const runInternalEvent = Effect.fn("effect-machine.cluster.internalEvent")(function* () {
|
|
62
|
-
yield* processEvent(machine, stateRef, yield* Queue.take(internalQueue), self, stateScopeRef, options?.hooks);
|
|
68
|
+
yield* processEvent(machine, stateRef, yield* Queue.take(internalQueue), self, stateScopeRef, system, options?.hooks);
|
|
63
69
|
});
|
|
64
70
|
yield* Effect.forkScoped(Effect.forever(runInternalEvent()));
|
|
65
71
|
return entity.of({
|
|
66
|
-
Send: (envelope) => processEvent(machine, stateRef, envelope.payload.event, self, stateScopeRef, options?.hooks),
|
|
72
|
+
Send: (envelope) => processEvent(machine, stateRef, envelope.payload.event, self, stateScopeRef, system, options?.hooks),
|
|
67
73
|
GetState: () => Ref.get(stateRef)
|
|
68
74
|
});
|
|
69
75
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { EffectHandlers, EffectSlot, EffectSlots, EffectsDef, EffectsSchema, GuardHandlers, GuardSlot, GuardSlots, GuardsDef, GuardsSchema, MachineContext, Slot } from "./slot.js";
|
|
2
2
|
import { Event, MachineEventSchema, MachineStateSchema, State } from "./schema.js";
|
|
3
3
|
import { PersistenceConfig, PersistentMachine, isPersistentMachine } from "./persistence/persistent-machine.js";
|
|
4
|
-
import { PersistentActorRef, createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
|
|
5
4
|
import { AssertionError, DuplicateActorError, InvalidSchemaError, MissingMatchHandlerError, MissingSchemaError, ProvisionValidationError, SlotProvisionError, UnprovidedSlotsError } from "./errors.js";
|
|
5
|
+
import { PersistentActorRef, createPersistentActor, restorePersistentActor } from "./persistence/persistent-actor.js";
|
|
6
6
|
import { ActorMetadata, PersistedEvent, PersistenceAdapter, PersistenceAdapterTag, PersistenceError, RestoreFailure, RestoreResult, Snapshot, VersionConflictError } from "./persistence/adapter.js";
|
|
7
7
|
import { InMemoryPersistenceAdapter, makeInMemoryPersistenceAdapter } from "./persistence/adapters/in-memory.js";
|
|
8
8
|
import "./persistence/index.js";
|
|
9
9
|
import { BackgroundEffect, BuiltMachine, HandlerContext, Machine, MachineRef, MakeConfig, PersistOptions, ProvideHandlers, SpawnEffect, StateHandlerContext, Transition, machine_d_exports } from "./machine.js";
|
|
10
|
-
import { ActorRef, ActorSystem, Default } from "./actor.js";
|
|
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
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 TestHarness, type TestHarnessOptions, type Transition, type TransitionEvent, UnprovidedSlotsError, VersionConflictError, assertNeverReaches, assertPath, assertReaches, collectingInspector, consoleInspector, createPersistentActor, createTestHarness, isPersistentMachine, makeInMemoryPersistenceAdapter, makeInspector, restorePersistentActor, simulate };
|
|
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 };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EffectsDef, GuardsDef, MachineContext } from "../slot.js";
|
|
2
2
|
import { BuiltMachine, Machine, MachineRef, SpawnEffect, Transition } from "../machine.js";
|
|
3
|
+
import { ActorSystem } from "../actor.js";
|
|
3
4
|
import { Cause, Effect, Scope } from "effect";
|
|
4
5
|
|
|
5
6
|
//#region src/internal/transition.d.ts
|
|
@@ -28,7 +29,7 @@ declare const runTransitionHandler: <S extends {
|
|
|
28
29
|
readonly _tag: string;
|
|
29
30
|
}, E extends {
|
|
30
31
|
readonly _tag: string;
|
|
31
|
-
}, 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
|
|
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
33
|
/**
|
|
33
34
|
* Execute a transition for a given state and event.
|
|
34
35
|
* Handles transition resolution, handler invocation, and guard/effect slot creation.
|
|
@@ -44,7 +45,7 @@ declare const executeTransition: <S extends {
|
|
|
44
45
|
readonly _tag: string;
|
|
45
46
|
}, E extends {
|
|
46
47
|
readonly _tag: string;
|
|
47
|
-
}, 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
|
|
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
49
|
newState: S;
|
|
49
50
|
transitioned: boolean;
|
|
50
51
|
reenter: boolean;
|
|
@@ -102,7 +103,7 @@ declare const processEventCore: <S extends {
|
|
|
102
103
|
readonly _tag: string;
|
|
103
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: {
|
|
104
105
|
current: Scope.CloseableScope;
|
|
105
|
-
}, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
|
|
106
|
+
}, system: ActorSystem, hooks?: ProcessEventHooks<S, E> | undefined) => Effect.Effect<{
|
|
106
107
|
newState: S;
|
|
107
108
|
previousState: S;
|
|
108
109
|
transitioned: boolean;
|
|
@@ -118,7 +119,7 @@ declare const runSpawnEffects: <S extends {
|
|
|
118
119
|
readonly _tag: string;
|
|
119
120
|
}, E extends {
|
|
120
121
|
readonly _tag: string;
|
|
121
|
-
}, R, GD extends GuardsDef, EFD extends EffectsDef>(machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, state: S, event: E, self: MachineRef<E>, stateScope: Scope.CloseableScope, 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.CloseableScope, 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
123
|
/**
|
|
123
124
|
* Resolve which transition should fire for a given state and event.
|
|
124
125
|
* Uses indexed O(1) lookup. First matching transition wins.
|
|
@@ -23,11 +23,12 @@ import { Cause, Effect, Exit, Scope } from "effect";
|
|
|
23
23
|
*
|
|
24
24
|
* @internal
|
|
25
25
|
*/
|
|
26
|
-
const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(function* (machine, transition, state, event, self) {
|
|
26
|
+
const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(function* (machine, transition, state, event, self, system) {
|
|
27
27
|
const ctx = {
|
|
28
28
|
state,
|
|
29
29
|
event,
|
|
30
|
-
self
|
|
30
|
+
self,
|
|
31
|
+
system
|
|
31
32
|
};
|
|
32
33
|
const { guards, effects } = machine._slots;
|
|
33
34
|
const handlerCtx = {
|
|
@@ -50,7 +51,7 @@ const runTransitionHandler = Effect.fn("effect-machine.runTransitionHandler")(fu
|
|
|
50
51
|
*
|
|
51
52
|
* @internal
|
|
52
53
|
*/
|
|
53
|
-
const executeTransition = Effect.fn("effect-machine.executeTransition")(function* (machine, currentState, event, self) {
|
|
54
|
+
const executeTransition = Effect.fn("effect-machine.executeTransition")(function* (machine, currentState, event, self, system) {
|
|
54
55
|
const transition = resolveTransition(machine, currentState, event);
|
|
55
56
|
if (transition === void 0) return {
|
|
56
57
|
newState: currentState,
|
|
@@ -58,7 +59,7 @@ const executeTransition = Effect.fn("effect-machine.executeTransition")(function
|
|
|
58
59
|
reenter: false
|
|
59
60
|
};
|
|
60
61
|
return {
|
|
61
|
-
newState: yield* runTransitionHandler(machine, transition, currentState, event, self),
|
|
62
|
+
newState: yield* runTransitionHandler(machine, transition, currentState, event, self, system),
|
|
62
63
|
transitioned: true,
|
|
63
64
|
reenter: transition.reenter === true
|
|
64
65
|
};
|
|
@@ -75,8 +76,8 @@ const executeTransition = Effect.fn("effect-machine.executeTransition")(function
|
|
|
75
76
|
*
|
|
76
77
|
* @internal
|
|
77
78
|
*/
|
|
78
|
-
const processEventCore = Effect.fn("effect-machine.processEventCore")(function* (machine, currentState, event, self, stateScopeRef, hooks) {
|
|
79
|
-
const result = yield* executeTransition(machine, currentState, event, self).pipe(Effect.catchAllCause((cause) => {
|
|
79
|
+
const processEventCore = Effect.fn("effect-machine.processEventCore")(function* (machine, currentState, event, self, stateScopeRef, system, hooks) {
|
|
80
|
+
const result = yield* executeTransition(machine, currentState, event, self, system).pipe(Effect.catchAllCause((cause) => {
|
|
80
81
|
if (Cause.isInterruptedOnly(cause)) return Effect.interrupt;
|
|
81
82
|
const onError = hooks?.onError;
|
|
82
83
|
if (onError === void 0) return Effect.failCause(cause).pipe(Effect.orDie);
|
|
@@ -101,7 +102,7 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
|
|
|
101
102
|
stateScopeRef.current = yield* Scope.make();
|
|
102
103
|
if (hooks?.onTransition !== void 0) yield* hooks.onTransition(currentState, newState, event);
|
|
103
104
|
if (hooks?.onSpawnEffect !== void 0) yield* hooks.onSpawnEffect(newState);
|
|
104
|
-
yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, hooks?.onError);
|
|
105
|
+
yield* runSpawnEffects(machine, newState, { _tag: INTERNAL_ENTER_EVENT }, self, stateScopeRef.current, system, hooks?.onError);
|
|
105
106
|
}
|
|
106
107
|
return {
|
|
107
108
|
newState,
|
|
@@ -116,12 +117,13 @@ const processEventCore = Effect.fn("effect-machine.processEventCore")(function*
|
|
|
116
117
|
*
|
|
117
118
|
* @internal
|
|
118
119
|
*/
|
|
119
|
-
const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, onError) {
|
|
120
|
+
const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (machine, state, event, self, stateScope, system, onError) {
|
|
120
121
|
const spawnEffects = findSpawnEffects(machine, state._tag);
|
|
121
122
|
const ctx = {
|
|
122
123
|
state,
|
|
123
124
|
event,
|
|
124
|
-
self
|
|
125
|
+
self,
|
|
126
|
+
system
|
|
125
127
|
};
|
|
126
128
|
const { effects: effectSlots } = machine._slots;
|
|
127
129
|
const reportError = onError;
|
|
@@ -130,7 +132,8 @@ const runSpawnEffects = Effect.fn("effect-machine.runSpawnEffects")(function* (m
|
|
|
130
132
|
state,
|
|
131
133
|
event,
|
|
132
134
|
self,
|
|
133
|
-
effects: effectSlots
|
|
135
|
+
effects: effectSlots,
|
|
136
|
+
system
|
|
134
137
|
}).pipe(Effect.provideService(machine.Context, ctx), Effect.catchAllCause((cause) => {
|
|
135
138
|
if (Cause.isInterruptedOnly(cause)) return Effect.interrupt;
|
|
136
139
|
if (reportError === void 0) return Effect.failCause(cause).pipe(Effect.orDie);
|
package/dist/internal/utils.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ActorSystem } from "../actor.js";
|
|
1
2
|
import { Effect } from "effect";
|
|
2
3
|
|
|
3
4
|
//#region src/internal/utils.d.ts
|
|
@@ -48,5 +49,12 @@ declare const getTag: (constructorOrValue: {
|
|
|
48
49
|
})) => string;
|
|
49
50
|
/** Check if a value is an Effect */
|
|
50
51
|
declare const isEffect: (value: unknown) => value is Effect.Effect<unknown, unknown, unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* Stub ActorSystem that dies on any method call.
|
|
54
|
+
* Used in contexts where spawning/system access isn't supported
|
|
55
|
+
* (testing simulation, persistent actor replay).
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
declare const stubSystem: ActorSystem;
|
|
51
59
|
//#endregion
|
|
52
|
-
export { ArgsOf, INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, InstanceOf, TagOf, TaggedConstructor, TransitionResult, getTag, isEffect };
|
|
60
|
+
export { ArgsOf, INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, InstanceOf, TagOf, TaggedConstructor, TransitionResult, getTag, isEffect, stubSystem };
|
package/dist/internal/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Effect } from "effect";
|
|
1
|
+
import { Effect, Stream } from "effect";
|
|
2
2
|
|
|
3
3
|
//#region src/internal/utils.ts
|
|
4
4
|
/**
|
|
@@ -26,6 +26,26 @@ const getTag = (constructorOrValue) => {
|
|
|
26
26
|
};
|
|
27
27
|
/** Check if a value is an Effect */
|
|
28
28
|
const isEffect = (value) => typeof value === "object" && value !== null && Effect.EffectTypeId in value;
|
|
29
|
+
/**
|
|
30
|
+
* Stub ActorSystem that dies on any method call.
|
|
31
|
+
* Used in contexts where spawning/system access isn't supported
|
|
32
|
+
* (testing simulation, persistent actor replay).
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
const stubSystem = {
|
|
36
|
+
spawn: () => Effect.die("spawn not supported in stub system"),
|
|
37
|
+
restore: () => Effect.die("restore not supported in stub system"),
|
|
38
|
+
get: () => Effect.die("get not supported in stub system"),
|
|
39
|
+
stop: () => Effect.die("stop not supported in stub system"),
|
|
40
|
+
events: Stream.empty,
|
|
41
|
+
get actors() {
|
|
42
|
+
return /* @__PURE__ */ new Map();
|
|
43
|
+
},
|
|
44
|
+
subscribe: () => () => {},
|
|
45
|
+
listPersisted: () => Effect.die("listPersisted not supported in stub system"),
|
|
46
|
+
restoreMany: () => Effect.die("restoreMany not supported in stub system"),
|
|
47
|
+
restoreAll: () => Effect.die("restoreAll not supported in stub system")
|
|
48
|
+
};
|
|
29
49
|
|
|
30
50
|
//#endregion
|
|
31
|
-
export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isEffect };
|
|
51
|
+
export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isEffect, stubSystem };
|
package/dist/machine.d.ts
CHANGED
|
@@ -3,9 +3,10 @@ import { TransitionResult } from "./internal/utils.js";
|
|
|
3
3
|
import { BrandedEvent, BrandedState, TaggedOrConstructor } from "./internal/brands.js";
|
|
4
4
|
import { MachineEventSchema, MachineStateSchema, VariantsUnion } from "./schema.js";
|
|
5
5
|
import { PersistenceConfig, PersistentMachine } from "./persistence/persistent-machine.js";
|
|
6
|
+
import { DuplicateActorError } from "./errors.js";
|
|
6
7
|
import { findTransitions } from "./internal/transition.js";
|
|
7
8
|
import "./persistence/index.js";
|
|
8
|
-
import { ActorRef } from "./actor.js";
|
|
9
|
+
import { ActorRef, ActorSystem } from "./actor.js";
|
|
9
10
|
import { Cause, Context, Effect, Schedule, Schema, Scope } from "effect";
|
|
10
11
|
|
|
11
12
|
//#region src/machine.d.ts
|
|
@@ -17,6 +18,11 @@ declare namespace machine_d_exports {
|
|
|
17
18
|
*/
|
|
18
19
|
interface MachineRef<Event> {
|
|
19
20
|
readonly send: (event: Event) => Effect.Effect<void>;
|
|
21
|
+
readonly spawn: <S2 extends {
|
|
22
|
+
readonly _tag: string;
|
|
23
|
+
}, E2 extends {
|
|
24
|
+
readonly _tag: string;
|
|
25
|
+
}, R2>(id: string, machine: BuiltMachine<S2, E2, R2>) => Effect.Effect<ActorRef<S2, E2>, DuplicateActorError, R2>;
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
22
28
|
* Handler context passed to transition handlers
|
|
@@ -35,6 +41,7 @@ interface StateHandlerContext<State, Event, ED extends EffectsDef> {
|
|
|
35
41
|
readonly event: Event;
|
|
36
42
|
readonly self: MachineRef<Event>;
|
|
37
43
|
readonly effects: EffectSlots<ED>;
|
|
44
|
+
readonly system: ActorSystem;
|
|
38
45
|
}
|
|
39
46
|
/**
|
|
40
47
|
* Transition handler function
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { PersistentActorRef } from "./persistent-actor.js";
|
|
2
1
|
import { DuplicateActorError } from "../errors.js";
|
|
2
|
+
import { PersistentActorRef } from "./persistent-actor.js";
|
|
3
3
|
import { Context, Effect, Option, Schema } from "effect";
|
|
4
4
|
|
|
5
5
|
//#region src/persistence/adapter.d.ts
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Inspector } from "../inspection.js";
|
|
2
|
-
import { INTERNAL_INIT_EVENT } from "../internal/utils.js";
|
|
2
|
+
import { INTERNAL_INIT_EVENT, stubSystem } from "../internal/utils.js";
|
|
3
3
|
import { processEventCore, resolveTransition, runSpawnEffects, runTransitionHandler } from "../internal/transition.js";
|
|
4
4
|
import { emitWithTimestamp } from "../internal/inspection.js";
|
|
5
5
|
import { PersistenceAdapterTag } from "./adapter.js";
|
|
6
|
-
import { buildActorRefCore, notifyListeners } from "../actor.js";
|
|
6
|
+
import { ActorSystem, buildActorRefCore, notifyListeners } from "../actor.js";
|
|
7
7
|
import { Cause, Clock, Effect, Exit, Fiber, Option, Queue, Ref, Schedule, Scope, SubscriptionRef } from "effect";
|
|
8
8
|
|
|
9
9
|
//#region src/persistence/persistent-actor.ts
|
|
@@ -20,7 +20,7 @@ const replayEvents = Effect.fn("effect-machine.persistentActor.replayEvents")(fu
|
|
|
20
20
|
for (const persistedEvent of events) {
|
|
21
21
|
if (stopVersion !== void 0 && persistedEvent.version > stopVersion) break;
|
|
22
22
|
const transition = resolveTransition(machine, state, persistedEvent.event);
|
|
23
|
-
if (transition !== void 0) state = yield* runTransitionHandler(machine, transition, state, persistedEvent.event, self);
|
|
23
|
+
if (transition !== void 0) state = yield* runTransitionHandler(machine, transition, state, persistedEvent.event, self, stubSystem);
|
|
24
24
|
version = persistedEvent.version;
|
|
25
25
|
}
|
|
26
26
|
return {
|
|
@@ -31,7 +31,7 @@ const replayEvents = Effect.fn("effect-machine.persistentActor.replayEvents")(fu
|
|
|
31
31
|
/**
|
|
32
32
|
* Build PersistentActorRef with all methods
|
|
33
33
|
*/
|
|
34
|
-
const buildPersistentActorRef = (id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, stop, adapter) => {
|
|
34
|
+
const buildPersistentActorRef = (id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, stop, adapter, system, childrenMap) => {
|
|
35
35
|
const { machine, persistence } = persistentMachine;
|
|
36
36
|
const typedMachine = machine;
|
|
37
37
|
const persist = Effect.gen(function* () {
|
|
@@ -45,12 +45,15 @@ const buildPersistentActorRef = (id, persistentMachine, stateRef, versionRef, ev
|
|
|
45
45
|
const version = Ref.get(versionRef).pipe(Effect.withSpan("effect-machine.persistentActor.version"));
|
|
46
46
|
const replayTo = Effect.fn("effect-machine.persistentActor.replayTo")(function* (targetVersion) {
|
|
47
47
|
if (targetVersion <= (yield* Ref.get(versionRef))) {
|
|
48
|
+
const dummySelf = {
|
|
49
|
+
send: Effect.fn("effect-machine.persistentActor.replay.send")((_event) => Effect.void),
|
|
50
|
+
spawn: () => Effect.die("spawn not supported in replay")
|
|
51
|
+
};
|
|
48
52
|
const maybeSnapshot = yield* adapter.loadSnapshot(id, persistence.stateSchema);
|
|
49
53
|
if (Option.isSome(maybeSnapshot)) {
|
|
50
54
|
const snapshot = maybeSnapshot.value;
|
|
51
55
|
if (snapshot.version <= targetVersion) {
|
|
52
56
|
const events = yield* adapter.loadEvents(id, persistence.eventSchema, snapshot.version);
|
|
53
|
-
const dummySelf = { send: Effect.fn("effect-machine.persistentActor.replay.send")((_event) => Effect.void) };
|
|
54
57
|
const result = yield* replayEvents(typedMachine, snapshot.state, events, dummySelf, targetVersion);
|
|
55
58
|
yield* SubscriptionRef.set(stateRef, result.state);
|
|
56
59
|
yield* Ref.set(versionRef, result.version);
|
|
@@ -59,7 +62,6 @@ const buildPersistentActorRef = (id, persistentMachine, stateRef, versionRef, ev
|
|
|
59
62
|
} else {
|
|
60
63
|
const events = yield* adapter.loadEvents(id, persistence.eventSchema);
|
|
61
64
|
if (events.length > 0) {
|
|
62
|
-
const dummySelf = { send: Effect.fn("effect-machine.persistentActor.replay.send")((_event) => Effect.void) };
|
|
63
65
|
const result = yield* replayEvents(typedMachine, typedMachine.initial, events, dummySelf, targetVersion);
|
|
64
66
|
yield* SubscriptionRef.set(stateRef, result.state);
|
|
65
67
|
yield* Ref.set(versionRef, result.version);
|
|
@@ -69,7 +71,7 @@ const buildPersistentActorRef = (id, persistentMachine, stateRef, versionRef, ev
|
|
|
69
71
|
}
|
|
70
72
|
});
|
|
71
73
|
return {
|
|
72
|
-
...buildActorRefCore(id, typedMachine, stateRef, eventQueue, stoppedRef, listeners, stop),
|
|
74
|
+
...buildActorRefCore(id, typedMachine, stateRef, eventQueue, stoppedRef, listeners, stop, system, childrenMap),
|
|
73
75
|
persist,
|
|
74
76
|
version,
|
|
75
77
|
replayTo
|
|
@@ -84,13 +86,28 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
84
86
|
const adapter = yield* PersistenceAdapterTag;
|
|
85
87
|
const { machine, persistence } = persistentMachine;
|
|
86
88
|
const typedMachine = machine;
|
|
89
|
+
const existingSystem = yield* Effect.serviceOption(ActorSystem);
|
|
90
|
+
if (Option.isNone(existingSystem)) return yield* Effect.die("PersistentActor requires ActorSystem in context");
|
|
91
|
+
const system = existingSystem.value;
|
|
87
92
|
const inspector = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
|
|
88
93
|
const eventQueue = yield* Queue.unbounded();
|
|
89
94
|
const stoppedRef = yield* Ref.make(false);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
const childrenMap = /* @__PURE__ */ new Map();
|
|
96
|
+
const self = {
|
|
97
|
+
send: Effect.fn("effect-machine.persistentActor.self.send")(function* (event) {
|
|
98
|
+
if (yield* Ref.get(stoppedRef)) return;
|
|
99
|
+
yield* Queue.offer(eventQueue, event);
|
|
100
|
+
}),
|
|
101
|
+
spawn: (childId, childMachine) => Effect.gen(function* () {
|
|
102
|
+
const child = yield* system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system));
|
|
103
|
+
childrenMap.set(childId, child);
|
|
104
|
+
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
105
|
+
if (Option.isSome(maybeScope)) yield* Scope.addFinalizer(maybeScope.value, Effect.sync(() => {
|
|
106
|
+
childrenMap.delete(childId);
|
|
107
|
+
}));
|
|
108
|
+
return child;
|
|
109
|
+
})
|
|
110
|
+
};
|
|
94
111
|
let resolvedInitial;
|
|
95
112
|
let initialVersion;
|
|
96
113
|
if (Option.isSome(initialSnapshot)) {
|
|
@@ -131,7 +148,8 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
131
148
|
const initCtx = {
|
|
132
149
|
state: resolvedInitial,
|
|
133
150
|
event: initEvent,
|
|
134
|
-
self
|
|
151
|
+
self,
|
|
152
|
+
system
|
|
135
153
|
};
|
|
136
154
|
const { effects: effectSlots } = typedMachine._slots;
|
|
137
155
|
for (const bg of typedMachine.backgroundEffects) {
|
|
@@ -139,12 +157,13 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
139
157
|
state: resolvedInitial,
|
|
140
158
|
event: initEvent,
|
|
141
159
|
self,
|
|
142
|
-
effects: effectSlots
|
|
160
|
+
effects: effectSlots,
|
|
161
|
+
system
|
|
143
162
|
}).pipe(Effect.provideService(typedMachine.Context, initCtx)));
|
|
144
163
|
backgroundFibers.push(fiber);
|
|
145
164
|
}
|
|
146
165
|
const stateScopeRef = { current: yield* Scope.make() };
|
|
147
|
-
yield* runSpawnEffectsWithInspection(typedMachine, resolvedInitial, initEvent, self, stateScopeRef.current, id, inspector);
|
|
166
|
+
yield* runSpawnEffectsWithInspection(typedMachine, resolvedInitial, initEvent, self, stateScopeRef.current, id, inspector, system);
|
|
148
167
|
if (typedMachine.finalStates.has(resolvedInitial._tag)) {
|
|
149
168
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
150
169
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
@@ -157,9 +176,9 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
157
176
|
finalState: resolvedInitial,
|
|
158
177
|
timestamp
|
|
159
178
|
}));
|
|
160
|
-
return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter);
|
|
179
|
+
return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter, system, childrenMap);
|
|
161
180
|
}
|
|
162
|
-
const loopFiber = yield* Effect.forkDaemon(persistentEventLoop(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, self, listeners, adapter, createdAt, stateScopeRef, backgroundFibers, snapshotQueue, snapshotEnabledRef, persistenceQueue, snapshotFiber, persistenceFiber, inspector));
|
|
181
|
+
const loopFiber = yield* Effect.forkDaemon(persistentEventLoop(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, self, listeners, adapter, createdAt, stateScopeRef, backgroundFibers, snapshotQueue, snapshotEnabledRef, persistenceQueue, snapshotFiber, persistenceFiber, inspector, system));
|
|
163
182
|
return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
|
|
164
183
|
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
165
184
|
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
@@ -174,12 +193,12 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
174
193
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
175
194
|
yield* Fiber.interrupt(snapshotFiber);
|
|
176
195
|
yield* Fiber.interrupt(persistenceFiber);
|
|
177
|
-
}).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter);
|
|
196
|
+
}).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter, system, childrenMap);
|
|
178
197
|
});
|
|
179
198
|
/**
|
|
180
199
|
* Main event loop for persistent actor
|
|
181
200
|
*/
|
|
182
|
-
const persistentEventLoop = Effect.fn("effect-machine.persistentActor.eventLoop")(function* (id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, self, listeners, adapter, createdAt, stateScopeRef, backgroundFibers, snapshotQueue, snapshotEnabledRef, persistenceQueue, snapshotFiber, persistenceFiber, inspector) {
|
|
201
|
+
const persistentEventLoop = Effect.fn("effect-machine.persistentActor.eventLoop")(function* (id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, self, listeners, adapter, createdAt, stateScopeRef, backgroundFibers, snapshotQueue, snapshotEnabledRef, persistenceQueue, snapshotFiber, persistenceFiber, inspector, system) {
|
|
183
202
|
const { machine, persistence } = persistentMachine;
|
|
184
203
|
const typedMachine = machine;
|
|
185
204
|
const hooks = inspector === void 0 ? void 0 : {
|
|
@@ -219,7 +238,7 @@ const persistentEventLoop = Effect.fn("effect-machine.persistentActor.eventLoop"
|
|
|
219
238
|
event,
|
|
220
239
|
timestamp
|
|
221
240
|
}));
|
|
222
|
-
const result = yield* processEventCore(typedMachine, currentState, event, self, stateScopeRef, hooks);
|
|
241
|
+
const result = yield* processEventCore(typedMachine, currentState, event, self, stateScopeRef, system, hooks);
|
|
223
242
|
if (!result.transitioned) continue;
|
|
224
243
|
const newVersion = currentVersion + 1;
|
|
225
244
|
yield* Ref.set(versionRef, newVersion);
|
|
@@ -259,7 +278,7 @@ const persistentEventLoop = Effect.fn("effect-machine.persistentActor.eventLoop"
|
|
|
259
278
|
* Run spawn effects with inspection and tracing.
|
|
260
279
|
* @internal
|
|
261
280
|
*/
|
|
262
|
-
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.persistentActor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector) {
|
|
281
|
+
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.persistentActor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector, system) {
|
|
263
282
|
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
264
283
|
type: "@machine.effect",
|
|
265
284
|
actorId,
|
|
@@ -267,7 +286,7 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.persistentActor.
|
|
|
267
286
|
state,
|
|
268
287
|
timestamp
|
|
269
288
|
}));
|
|
270
|
-
yield* runSpawnEffects(machine, state, event, self, stateScope, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
289
|
+
yield* runSpawnEffects(machine, state, event, self, stateScope, system, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
271
290
|
type: "@machine.error",
|
|
272
291
|
actorId,
|
|
273
292
|
phase: info.phase,
|
package/dist/slot.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ActorSystem } from "./actor.js";
|
|
1
2
|
import { Context, Effect, Schema } from "effect";
|
|
2
3
|
|
|
3
4
|
//#region src/slot.d.ts
|
|
@@ -45,6 +46,7 @@ interface MachineContext<State, Event, Self> {
|
|
|
45
46
|
readonly state: State;
|
|
46
47
|
readonly event: Event;
|
|
47
48
|
readonly self: Self;
|
|
49
|
+
readonly system: ActorSystem;
|
|
48
50
|
}
|
|
49
51
|
/**
|
|
50
52
|
* Shared Context tag for all machines.
|
package/dist/testing.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stubSystem } from "./internal/utils.js";
|
|
1
2
|
import { AssertionError } from "./errors.js";
|
|
2
3
|
import { BuiltMachine } from "./machine.js";
|
|
3
4
|
import { executeTransition } from "./internal/transition.js";
|
|
@@ -26,11 +27,14 @@ import { Effect, SubscriptionRef } from "effect";
|
|
|
26
27
|
*/
|
|
27
28
|
const simulate = Effect.fn("effect-machine.simulate")(function* (input, events) {
|
|
28
29
|
const machine = input instanceof BuiltMachine ? input._inner : input;
|
|
29
|
-
const dummySelf = {
|
|
30
|
+
const dummySelf = {
|
|
31
|
+
send: Effect.fn("effect-machine.testing.simulate.send")((_event) => Effect.void),
|
|
32
|
+
spawn: () => Effect.die("spawn not supported in simulation")
|
|
33
|
+
};
|
|
30
34
|
let currentState = machine.initial;
|
|
31
35
|
const states = [currentState];
|
|
32
36
|
for (const event of events) {
|
|
33
|
-
const result = yield* executeTransition(machine, currentState, event, dummySelf);
|
|
37
|
+
const result = yield* executeTransition(machine, currentState, event, dummySelf, stubSystem);
|
|
34
38
|
if (!result.transitioned) continue;
|
|
35
39
|
currentState = result.newState;
|
|
36
40
|
states.push(currentState);
|
|
@@ -110,13 +114,16 @@ const assertNeverReaches = Effect.fn("effect-machine.assertNeverReaches")(functi
|
|
|
110
114
|
*/
|
|
111
115
|
const createTestHarness = Effect.fn("effect-machine.createTestHarness")(function* (input, options) {
|
|
112
116
|
const machine = input instanceof BuiltMachine ? input._inner : input;
|
|
113
|
-
const dummySelf = {
|
|
117
|
+
const dummySelf = {
|
|
118
|
+
send: Effect.fn("effect-machine.testing.harness.send")((_event) => Effect.void),
|
|
119
|
+
spawn: () => Effect.die("spawn not supported in test harness")
|
|
120
|
+
};
|
|
114
121
|
const stateRef = yield* SubscriptionRef.make(machine.initial);
|
|
115
122
|
return {
|
|
116
123
|
state: stateRef,
|
|
117
124
|
send: Effect.fn("effect-machine.testHarness.send")(function* (event) {
|
|
118
125
|
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
119
|
-
const result = yield* executeTransition(machine, currentState, event, dummySelf);
|
|
126
|
+
const result = yield* executeTransition(machine, currentState, event, dummySelf, stubSystem);
|
|
120
127
|
if (!result.transitioned) return currentState;
|
|
121
128
|
const newState = result.newState;
|
|
122
129
|
yield* SubscriptionRef.set(stateRef, newState);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect-machine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/cevr/effect-machine.git"
|
|
@@ -41,21 +41,21 @@
|
|
|
41
41
|
"release": "bun run build && changeset publish"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"effect": "^3.19.
|
|
44
|
+
"effect": "^3.19.16"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@changesets/changelog-github": "^0.5.2",
|
|
48
48
|
"@changesets/cli": "^2.29.8",
|
|
49
|
-
"@effect/cluster": "^0.56.
|
|
49
|
+
"@effect/cluster": "^0.56.2",
|
|
50
50
|
"@effect/experimental": "^0.58.0",
|
|
51
|
-
"@effect/language-service": "^0.
|
|
51
|
+
"@effect/language-service": "^0.73.0",
|
|
52
52
|
"@effect/rpc": "^0.73.0",
|
|
53
|
-
"@types/bun": "
|
|
53
|
+
"@types/bun": "1.3.8",
|
|
54
54
|
"concurrently": "^9.2.1",
|
|
55
55
|
"effect-bun-test": "^0.1.0",
|
|
56
|
-
"lefthook": "^2.0
|
|
57
|
-
"oxfmt": "^0.
|
|
58
|
-
"oxlint": "^1.
|
|
56
|
+
"lefthook": "^2.1.0",
|
|
57
|
+
"oxfmt": "^0.28.0",
|
|
58
|
+
"oxlint": "^1.43.0",
|
|
59
59
|
"tsdown": "^0.20.3",
|
|
60
60
|
"typescript": "^5.9.3"
|
|
61
61
|
},
|
|
@@ -70,5 +70,8 @@
|
|
|
70
70
|
"@effect/rpc": {
|
|
71
71
|
"optional": true
|
|
72
72
|
}
|
|
73
|
+
},
|
|
74
|
+
"overrides": {
|
|
75
|
+
"effect": "^3.19.16"
|
|
73
76
|
}
|
|
74
77
|
}
|