effect-machine 0.3.2 → 0.4.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 +24 -0
- package/dist/actor.d.ts +7 -2
- package/dist/actor.js +35 -18
- package/dist/cluster/entity-machine.js +16 -10
- package/dist/index.d.ts +1 -1
- 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 +16 -1
- package/dist/machine.d.ts +8 -1
- package/dist/persistence/adapter.d.ts +1 -1
- package/dist/persistence/persistent-actor.js +31 -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,29 @@ 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
|
+
|
|
186
209
|
### Testing
|
|
187
210
|
|
|
188
211
|
Test transitions without actors:
|
|
@@ -273,6 +296,7 @@ See the [primer](./primer/) for comprehensive documentation:
|
|
|
273
296
|
| `actor.awaitFinal` | Wait final state |
|
|
274
297
|
| `actor.sendAndWait(ev, State.X)` | Send + wait for state |
|
|
275
298
|
| `actor.subscribe(fn)` | Sync callback |
|
|
299
|
+
| `actor.system` | Access the actor's `ActorSystem` |
|
|
276
300
|
|
|
277
301
|
## License
|
|
278
302
|
|
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,6 +101,11 @@ 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;
|
|
104
109
|
}
|
|
105
110
|
/** Base type for stored actors (internal) */
|
|
106
111
|
type AnyState = {
|
|
@@ -234,7 +239,7 @@ declare const buildActorRefCore: <S extends {
|
|
|
234
239
|
readonly _tag: string;
|
|
235
240
|
}, E extends {
|
|
236
241
|
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
|
|
242
|
+
}, 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) => ActorRef<S, E>;
|
|
238
243
|
/**
|
|
239
244
|
* Create and start an actor for a machine
|
|
240
245
|
*/
|
package/dist/actor.js
CHANGED
|
@@ -24,7 +24,7 @@ const notifyListeners = (listeners, state) => {
|
|
|
24
24
|
/**
|
|
25
25
|
* Build core ActorRef methods shared between regular and persistent actors.
|
|
26
26
|
*/
|
|
27
|
-
const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop) => {
|
|
27
|
+
const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listeners, stop, system) => {
|
|
28
28
|
const send = Effect.fn("effect-machine.actor.send")(function* (event) {
|
|
29
29
|
if (yield* Ref.get(stoppedRef)) return;
|
|
30
30
|
yield* Queue.offer(eventQueue, event);
|
|
@@ -88,7 +88,8 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
88
88
|
return () => {
|
|
89
89
|
listeners.delete(fn);
|
|
90
90
|
};
|
|
91
|
-
}
|
|
91
|
+
},
|
|
92
|
+
system
|
|
92
93
|
};
|
|
93
94
|
};
|
|
94
95
|
/**
|
|
@@ -96,13 +97,25 @@ const buildActorRefCore = (id, machine, stateRef, eventQueue, stoppedRef, listen
|
|
|
96
97
|
*/
|
|
97
98
|
const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machine) {
|
|
98
99
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
|
|
100
|
+
const existingSystem = yield* Effect.serviceOption(ActorSystem);
|
|
101
|
+
let system;
|
|
102
|
+
let implicitSystemScope;
|
|
103
|
+
if (Option.isSome(existingSystem)) system = existingSystem.value;
|
|
104
|
+
else {
|
|
105
|
+
const scope = yield* Scope.make();
|
|
106
|
+
system = yield* make().pipe(Effect.provideService(Scope.Scope, scope));
|
|
107
|
+
implicitSystemScope = scope;
|
|
108
|
+
}
|
|
99
109
|
const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(Inspector));
|
|
100
110
|
const eventQueue = yield* Queue.unbounded();
|
|
101
111
|
const stoppedRef = yield* Ref.make(false);
|
|
102
|
-
const self = {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
const self = {
|
|
113
|
+
send: Effect.fn("effect-machine.actor.self.send")(function* (event) {
|
|
114
|
+
if (yield* Ref.get(stoppedRef)) return;
|
|
115
|
+
yield* Queue.offer(eventQueue, event);
|
|
116
|
+
}),
|
|
117
|
+
spawn: (childId, childMachine) => system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system))
|
|
118
|
+
};
|
|
106
119
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.initial_state", machine.initial._tag);
|
|
107
120
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
108
121
|
type: "@machine.spawn",
|
|
@@ -117,7 +130,8 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
117
130
|
const ctx = {
|
|
118
131
|
state: machine.initial,
|
|
119
132
|
event: initEvent,
|
|
120
|
-
self
|
|
133
|
+
self,
|
|
134
|
+
system
|
|
121
135
|
};
|
|
122
136
|
const { effects: effectSlots } = machine._slots;
|
|
123
137
|
for (const bg of machine.backgroundEffects) {
|
|
@@ -125,12 +139,13 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
125
139
|
state: machine.initial,
|
|
126
140
|
event: initEvent,
|
|
127
141
|
self,
|
|
128
|
-
effects: effectSlots
|
|
142
|
+
effects: effectSlots,
|
|
143
|
+
system
|
|
129
144
|
}).pipe(Effect.provideService(machine.Context, ctx)));
|
|
130
145
|
backgroundFibers.push(fiber);
|
|
131
146
|
}
|
|
132
147
|
const stateScopeRef = { current: yield* Scope.make() };
|
|
133
|
-
yield* runSpawnEffectsWithInspection(machine, machine.initial, initEvent, self, stateScopeRef.current, id, inspectorValue);
|
|
148
|
+
yield* runSpawnEffectsWithInspection(machine, machine.initial, initEvent, self, stateScopeRef.current, id, inspectorValue, system);
|
|
134
149
|
if (machine.finalStates.has(machine.initial._tag)) {
|
|
135
150
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
136
151
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
@@ -141,9 +156,10 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
141
156
|
timestamp
|
|
142
157
|
}));
|
|
143
158
|
yield* Ref.set(stoppedRef, true);
|
|
144
|
-
|
|
159
|
+
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
160
|
+
return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system);
|
|
145
161
|
}
|
|
146
|
-
const loopFiber = yield* Effect.forkDaemon(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue));
|
|
162
|
+
const loopFiber = yield* Effect.forkDaemon(eventLoop(machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, id, inspectorValue, system));
|
|
147
163
|
return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
|
|
148
164
|
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
149
165
|
yield* emitWithTimestamp(inspectorValue, (timestamp) => ({
|
|
@@ -156,12 +172,13 @@ const createActor = Effect.fn("effect-machine.actor.spawn")(function* (id, machi
|
|
|
156
172
|
yield* Fiber.interrupt(loopFiber);
|
|
157
173
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
158
174
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
159
|
-
|
|
175
|
+
if (implicitSystemScope !== void 0) yield* Scope.close(implicitSystemScope, Exit.void);
|
|
176
|
+
}).pipe(Effect.withSpan("effect-machine.actor.stop"), Effect.asVoid), system);
|
|
160
177
|
});
|
|
161
178
|
/**
|
|
162
179
|
* Main event loop for the actor
|
|
163
180
|
*/
|
|
164
|
-
const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, actorId, inspector) {
|
|
181
|
+
const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine, stateRef, eventQueue, stoppedRef, self, listeners, backgroundFibers, stateScopeRef, actorId, inspector, system) {
|
|
165
182
|
while (true) {
|
|
166
183
|
const event = yield* Queue.take(eventQueue);
|
|
167
184
|
const currentState = yield* SubscriptionRef.get(stateRef);
|
|
@@ -169,7 +186,7 @@ const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine
|
|
|
169
186
|
"effect_machine.actor.id": actorId,
|
|
170
187
|
"effect_machine.state.current": currentState._tag,
|
|
171
188
|
"effect_machine.event.type": event._tag
|
|
172
|
-
} })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector))) {
|
|
189
|
+
} })(processEvent(machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system))) {
|
|
173
190
|
yield* Ref.set(stoppedRef, true);
|
|
174
191
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
175
192
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
@@ -181,7 +198,7 @@ const eventLoop = Effect.fn("effect-machine.actor.eventLoop")(function* (machine
|
|
|
181
198
|
* Process a single event, returning true if the actor should stop.
|
|
182
199
|
* Wraps processEventCore with actor-specific concerns (inspection, listeners, state ref).
|
|
183
200
|
*/
|
|
184
|
-
const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector) {
|
|
201
|
+
const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (machine, currentState, event, stateRef, self, listeners, stateScopeRef, actorId, inspector, system) {
|
|
185
202
|
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
186
203
|
type: "@machine.event",
|
|
187
204
|
actorId,
|
|
@@ -189,7 +206,7 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
|
|
|
189
206
|
event,
|
|
190
207
|
timestamp
|
|
191
208
|
}));
|
|
192
|
-
const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, inspector === void 0 ? void 0 : {
|
|
209
|
+
const result = yield* processEventCore(machine, currentState, event, self, stateScopeRef, system, inspector === void 0 ? void 0 : {
|
|
193
210
|
onSpawnEffect: (state) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
194
211
|
type: "@machine.effect",
|
|
195
212
|
actorId,
|
|
@@ -242,7 +259,7 @@ const processEvent = Effect.fn("effect-machine.actor.processEvent")(function* (m
|
|
|
242
259
|
* Wraps the core runSpawnEffects with inspection events and spans.
|
|
243
260
|
* @internal
|
|
244
261
|
*/
|
|
245
|
-
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector) {
|
|
262
|
+
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector, system) {
|
|
246
263
|
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
247
264
|
type: "@machine.effect",
|
|
248
265
|
actorId,
|
|
@@ -250,7 +267,7 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.actor.spawnEffec
|
|
|
250
267
|
state,
|
|
251
268
|
timestamp
|
|
252
269
|
}));
|
|
253
|
-
yield* runSpawnEffects(machine, state, event, self, stateScope, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
270
|
+
yield* runSpawnEffects(machine, state, event, self, stateScope, system, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
254
271
|
type: "@machine.error",
|
|
255
272
|
actorId,
|
|
256
273
|
phase: info.phase,
|
|
@@ -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,8 +1,8 @@
|
|
|
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";
|
|
@@ -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
|
@@ -26,6 +26,21 @@ 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
|
+
listPersisted: () => Effect.die("listPersisted not supported in stub system"),
|
|
41
|
+
restoreMany: () => Effect.die("restoreMany not supported in stub system"),
|
|
42
|
+
restoreAll: () => Effect.die("restoreAll not supported in stub system")
|
|
43
|
+
};
|
|
29
44
|
|
|
30
45
|
//#endregion
|
|
31
|
-
export { INTERNAL_ENTER_EVENT, INTERNAL_INIT_EVENT, getTag, isEffect };
|
|
46
|
+
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) => {
|
|
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),
|
|
73
75
|
persist,
|
|
74
76
|
version,
|
|
75
77
|
replayTo
|
|
@@ -84,13 +86,19 @@ 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 self = {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
const self = {
|
|
96
|
+
send: Effect.fn("effect-machine.persistentActor.self.send")(function* (event) {
|
|
97
|
+
if (yield* Ref.get(stoppedRef)) return;
|
|
98
|
+
yield* Queue.offer(eventQueue, event);
|
|
99
|
+
}),
|
|
100
|
+
spawn: (childId, childMachine) => system.spawn(childId, childMachine).pipe(Effect.provideService(ActorSystem, system))
|
|
101
|
+
};
|
|
94
102
|
let resolvedInitial;
|
|
95
103
|
let initialVersion;
|
|
96
104
|
if (Option.isSome(initialSnapshot)) {
|
|
@@ -131,7 +139,8 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
131
139
|
const initCtx = {
|
|
132
140
|
state: resolvedInitial,
|
|
133
141
|
event: initEvent,
|
|
134
|
-
self
|
|
142
|
+
self,
|
|
143
|
+
system
|
|
135
144
|
};
|
|
136
145
|
const { effects: effectSlots } = typedMachine._slots;
|
|
137
146
|
for (const bg of typedMachine.backgroundEffects) {
|
|
@@ -139,12 +148,13 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
139
148
|
state: resolvedInitial,
|
|
140
149
|
event: initEvent,
|
|
141
150
|
self,
|
|
142
|
-
effects: effectSlots
|
|
151
|
+
effects: effectSlots,
|
|
152
|
+
system
|
|
143
153
|
}).pipe(Effect.provideService(typedMachine.Context, initCtx)));
|
|
144
154
|
backgroundFibers.push(fiber);
|
|
145
155
|
}
|
|
146
156
|
const stateScopeRef = { current: yield* Scope.make() };
|
|
147
|
-
yield* runSpawnEffectsWithInspection(typedMachine, resolvedInitial, initEvent, self, stateScopeRef.current, id, inspector);
|
|
157
|
+
yield* runSpawnEffectsWithInspection(typedMachine, resolvedInitial, initEvent, self, stateScopeRef.current, id, inspector, system);
|
|
148
158
|
if (typedMachine.finalStates.has(resolvedInitial._tag)) {
|
|
149
159
|
yield* Scope.close(stateScopeRef.current, Exit.void);
|
|
150
160
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
@@ -157,9 +167,9 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
157
167
|
finalState: resolvedInitial,
|
|
158
168
|
timestamp
|
|
159
169
|
}));
|
|
160
|
-
return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter);
|
|
170
|
+
return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Ref.set(stoppedRef, true).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter, system);
|
|
161
171
|
}
|
|
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));
|
|
172
|
+
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
173
|
return buildPersistentActorRef(id, persistentMachine, stateRef, versionRef, eventQueue, stoppedRef, listeners, Effect.gen(function* () {
|
|
164
174
|
const finalState = yield* SubscriptionRef.get(stateRef);
|
|
165
175
|
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
@@ -174,12 +184,12 @@ const createPersistentActor = Effect.fn("effect-machine.persistentActor.spawn")(
|
|
|
174
184
|
yield* Effect.all(backgroundFibers.map(Fiber.interrupt), { concurrency: "unbounded" });
|
|
175
185
|
yield* Fiber.interrupt(snapshotFiber);
|
|
176
186
|
yield* Fiber.interrupt(persistenceFiber);
|
|
177
|
-
}).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter);
|
|
187
|
+
}).pipe(Effect.withSpan("effect-machine.persistentActor.stop"), Effect.asVoid), adapter, system);
|
|
178
188
|
});
|
|
179
189
|
/**
|
|
180
190
|
* Main event loop for persistent actor
|
|
181
191
|
*/
|
|
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) {
|
|
192
|
+
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
193
|
const { machine, persistence } = persistentMachine;
|
|
184
194
|
const typedMachine = machine;
|
|
185
195
|
const hooks = inspector === void 0 ? void 0 : {
|
|
@@ -219,7 +229,7 @@ const persistentEventLoop = Effect.fn("effect-machine.persistentActor.eventLoop"
|
|
|
219
229
|
event,
|
|
220
230
|
timestamp
|
|
221
231
|
}));
|
|
222
|
-
const result = yield* processEventCore(typedMachine, currentState, event, self, stateScopeRef, hooks);
|
|
232
|
+
const result = yield* processEventCore(typedMachine, currentState, event, self, stateScopeRef, system, hooks);
|
|
223
233
|
if (!result.transitioned) continue;
|
|
224
234
|
const newVersion = currentVersion + 1;
|
|
225
235
|
yield* Ref.set(versionRef, newVersion);
|
|
@@ -259,7 +269,7 @@ const persistentEventLoop = Effect.fn("effect-machine.persistentActor.eventLoop"
|
|
|
259
269
|
* Run spawn effects with inspection and tracing.
|
|
260
270
|
* @internal
|
|
261
271
|
*/
|
|
262
|
-
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.persistentActor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector) {
|
|
272
|
+
const runSpawnEffectsWithInspection = Effect.fn("effect-machine.persistentActor.spawnEffects")(function* (machine, state, event, self, stateScope, actorId, inspector, system) {
|
|
263
273
|
yield* emitWithTimestamp(inspector, (timestamp) => ({
|
|
264
274
|
type: "@machine.effect",
|
|
265
275
|
actorId,
|
|
@@ -267,7 +277,7 @@ const runSpawnEffectsWithInspection = Effect.fn("effect-machine.persistentActor.
|
|
|
267
277
|
state,
|
|
268
278
|
timestamp
|
|
269
279
|
}));
|
|
270
|
-
yield* runSpawnEffects(machine, state, event, self, stateScope, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
280
|
+
yield* runSpawnEffects(machine, state, event, self, stateScope, system, inspector === void 0 ? void 0 : (info) => emitWithTimestamp(inspector, (timestamp) => ({
|
|
271
281
|
type: "@machine.error",
|
|
272
282
|
actorId,
|
|
273
283
|
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.4.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
|
}
|