effect-machine 0.2.4 → 0.3.1
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 +93 -52
- package/package.json +2 -2
- package/src/actor.ts +134 -111
- package/src/errors.ts +1 -1
- package/src/index.ts +1 -0
- package/src/internal/transition.ts +8 -2
- package/src/machine.ts +228 -145
- package/src/persistence/adapter.ts +2 -2
- package/src/persistence/persistent-actor.ts +4 -10
- package/src/schema.ts +52 -6
- package/src/testing.ts +35 -27
package/src/actor.ts
CHANGED
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* - ActorSystem service (spawn/stop/get actors)
|
|
7
7
|
* - Actor creation and event loop
|
|
8
8
|
*/
|
|
9
|
+
import type { Stream } from "effect";
|
|
9
10
|
import {
|
|
10
11
|
Cause,
|
|
11
12
|
Context,
|
|
13
|
+
Deferred,
|
|
12
14
|
Effect,
|
|
13
15
|
Exit,
|
|
14
16
|
Fiber,
|
|
@@ -17,12 +19,12 @@ import {
|
|
|
17
19
|
Option,
|
|
18
20
|
Queue,
|
|
19
21
|
Ref,
|
|
22
|
+
Runtime,
|
|
20
23
|
Scope,
|
|
21
|
-
Stream,
|
|
22
24
|
SubscriptionRef,
|
|
23
25
|
} from "effect";
|
|
24
26
|
|
|
25
|
-
import type { Machine, MachineRef } from "./machine.js";
|
|
27
|
+
import type { Machine, MachineRef, BuiltMachine } from "./machine.js";
|
|
26
28
|
import type { Inspector } from "./inspection.js";
|
|
27
29
|
import { Inspector as InspectorTag } from "./inspection.js";
|
|
28
30
|
import { processEventCore, runSpawnEffects, resolveTransition } from "./internal/transition.js";
|
|
@@ -37,7 +39,7 @@ export type {
|
|
|
37
39
|
ProcessEventResult,
|
|
38
40
|
} from "./internal/transition.js";
|
|
39
41
|
import type { GuardsDef, EffectsDef } from "./slot.js";
|
|
40
|
-
import { DuplicateActorError
|
|
42
|
+
import { DuplicateActorError } from "./errors.js";
|
|
41
43
|
import { INTERNAL_INIT_EVENT } from "./internal/utils.js";
|
|
42
44
|
import type {
|
|
43
45
|
ActorMetadata,
|
|
@@ -82,6 +84,13 @@ export interface ActorRef<State extends { readonly _tag: string }, Event> {
|
|
|
82
84
|
*/
|
|
83
85
|
readonly stop: Effect.Effect<void>;
|
|
84
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Stop the actor (fire-and-forget).
|
|
89
|
+
* Signals graceful shutdown without waiting for completion.
|
|
90
|
+
* Use when stopping from sync contexts (e.g. framework cleanup hooks).
|
|
91
|
+
*/
|
|
92
|
+
readonly stopSync: () => void;
|
|
93
|
+
|
|
85
94
|
/**
|
|
86
95
|
* Get current state snapshot (Effect)
|
|
87
96
|
*/
|
|
@@ -118,9 +127,13 @@ export interface ActorRef<State extends { readonly _tag: string }, Event> {
|
|
|
118
127
|
readonly changes: Stream.Stream<State>;
|
|
119
128
|
|
|
120
129
|
/**
|
|
121
|
-
* Wait for a state that matches predicate (includes current snapshot)
|
|
130
|
+
* Wait for a state that matches predicate or state variant (includes current snapshot).
|
|
131
|
+
* Accepts a predicate function or a state constructor/value (e.g. `State.Active`).
|
|
122
132
|
*/
|
|
123
|
-
readonly waitFor:
|
|
133
|
+
readonly waitFor: {
|
|
134
|
+
(predicate: (state: State) => boolean): Effect.Effect<State>;
|
|
135
|
+
(state: { readonly _tag: State["_tag"] }): Effect.Effect<State>;
|
|
136
|
+
};
|
|
124
137
|
|
|
125
138
|
/**
|
|
126
139
|
* Wait for a final state (includes current snapshot)
|
|
@@ -128,12 +141,21 @@ export interface ActorRef<State extends { readonly _tag: string }, Event> {
|
|
|
128
141
|
readonly awaitFinal: Effect.Effect<State>;
|
|
129
142
|
|
|
130
143
|
/**
|
|
131
|
-
* Send event and wait for predicate or final state
|
|
144
|
+
* Send event and wait for predicate, state variant, or final state.
|
|
145
|
+
* Accepts a predicate function or a state constructor/value (e.g. `State.Active`).
|
|
146
|
+
*/
|
|
147
|
+
readonly sendAndWait: {
|
|
148
|
+
(event: Event, predicate: (state: State) => boolean): Effect.Effect<State>;
|
|
149
|
+
(event: Event, state: { readonly _tag: State["_tag"] }): Effect.Effect<State>;
|
|
150
|
+
(event: Event): Effect.Effect<State>;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Send event synchronously (fire-and-forget).
|
|
155
|
+
* No-op on stopped actors. Use when you need to send from sync contexts
|
|
156
|
+
* (e.g. framework hooks, event handlers).
|
|
132
157
|
*/
|
|
133
|
-
readonly
|
|
134
|
-
event: Event,
|
|
135
|
-
predicate?: (state: State) => boolean,
|
|
136
|
-
) => Effect.Effect<State>;
|
|
158
|
+
readonly sendSync: (event: Event) => void;
|
|
137
159
|
|
|
138
160
|
/**
|
|
139
161
|
* Subscribe to state changes (sync callback)
|
|
@@ -159,14 +181,13 @@ export interface ActorSystem {
|
|
|
159
181
|
* For regular machines, returns ActorRef.
|
|
160
182
|
* For persistent machines (created with Machine.persist), returns PersistentActorRef.
|
|
161
183
|
*
|
|
162
|
-
*
|
|
163
|
-
* Attempting to spawn a machine with unprovided effect slots will fail.
|
|
184
|
+
* All effect slots must be provided via `.build()` before spawning.
|
|
164
185
|
*
|
|
165
186
|
* @example
|
|
166
187
|
* ```ts
|
|
167
|
-
* // Regular machine (
|
|
168
|
-
* const
|
|
169
|
-
* const actor = yield* system.spawn("my-actor",
|
|
188
|
+
* // Regular machine (built)
|
|
189
|
+
* const built = machine.build({ fetchData: ... })
|
|
190
|
+
* const actor = yield* system.spawn("my-actor", built);
|
|
170
191
|
*
|
|
171
192
|
* // Persistent machine (auto-detected)
|
|
172
193
|
* const persistentActor = yield* system.spawn("my-actor", persistentMachine);
|
|
@@ -175,18 +196,11 @@ export interface ActorSystem {
|
|
|
175
196
|
* ```
|
|
176
197
|
*/
|
|
177
198
|
readonly spawn: {
|
|
178
|
-
// Regular machine overload
|
|
179
|
-
<
|
|
180
|
-
S extends { readonly _tag: string },
|
|
181
|
-
E extends { readonly _tag: string },
|
|
182
|
-
R,
|
|
183
|
-
GD extends GuardsDef = Record<string, never>,
|
|
184
|
-
EFD extends EffectsDef = Record<string, never>,
|
|
185
|
-
>(
|
|
199
|
+
// Regular machine overload (BuiltMachine)
|
|
200
|
+
<S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
|
|
186
201
|
id: string,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
): Effect.Effect<ActorRef<S, E>, DuplicateActorError | UnprovidedSlotsError, R | Scope.Scope>;
|
|
202
|
+
machine: BuiltMachine<S, E, R>,
|
|
203
|
+
): Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>;
|
|
190
204
|
|
|
191
205
|
// Persistent machine overload
|
|
192
206
|
<S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
|
|
@@ -194,8 +208,8 @@ export interface ActorSystem {
|
|
|
194
208
|
machine: PersistentMachine<S, E, R>,
|
|
195
209
|
): Effect.Effect<
|
|
196
210
|
PersistentActorRef<S, E, R>,
|
|
197
|
-
PersistenceError | VersionConflictError | DuplicateActorError
|
|
198
|
-
R |
|
|
211
|
+
PersistenceError | VersionConflictError | DuplicateActorError,
|
|
212
|
+
R | PersistenceAdapterTag
|
|
199
213
|
>;
|
|
200
214
|
};
|
|
201
215
|
|
|
@@ -218,8 +232,8 @@ export interface ActorSystem {
|
|
|
218
232
|
machine: PersistentMachine<S, E, R>,
|
|
219
233
|
) => Effect.Effect<
|
|
220
234
|
Option.Option<PersistentActorRef<S, E, R>>,
|
|
221
|
-
PersistenceError | DuplicateActorError
|
|
222
|
-
R |
|
|
235
|
+
PersistenceError | DuplicateActorError,
|
|
236
|
+
R | PersistenceAdapterTag
|
|
223
237
|
>;
|
|
224
238
|
|
|
225
239
|
/**
|
|
@@ -267,7 +281,7 @@ export interface ActorSystem {
|
|
|
267
281
|
>(
|
|
268
282
|
ids: ReadonlyArray<string>,
|
|
269
283
|
machine: PersistentMachine<S, E, R>,
|
|
270
|
-
) => Effect.Effect<RestoreResult<S, E, R>, never, R |
|
|
284
|
+
) => Effect.Effect<RestoreResult<S, E, R>, never, R | PersistenceAdapterTag>;
|
|
271
285
|
|
|
272
286
|
/**
|
|
273
287
|
* Restore all persisted actors for a machine type.
|
|
@@ -288,11 +302,7 @@ export interface ActorSystem {
|
|
|
288
302
|
>(
|
|
289
303
|
machine: PersistentMachine<S, E, R>,
|
|
290
304
|
options?: { filter?: (meta: ActorMetadata) => boolean },
|
|
291
|
-
) => Effect.Effect<
|
|
292
|
-
RestoreResult<S, E, R>,
|
|
293
|
-
PersistenceError,
|
|
294
|
-
R | Scope.Scope | PersistenceAdapterTag
|
|
295
|
-
>;
|
|
305
|
+
) => Effect.Effect<RestoreResult<S, E, R>, PersistenceError, R | PersistenceAdapterTag>;
|
|
296
306
|
}
|
|
297
307
|
|
|
298
308
|
/**
|
|
@@ -362,16 +372,41 @@ export const buildActorRefCore = <
|
|
|
362
372
|
});
|
|
363
373
|
|
|
364
374
|
const waitFor = Effect.fn("effect-machine.actor.waitFor")(function* (
|
|
365
|
-
|
|
375
|
+
predicateOrState: ((state: S) => boolean) | { readonly _tag: S["_tag"] },
|
|
366
376
|
) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
|
|
377
|
+
const predicate =
|
|
378
|
+
typeof predicateOrState === "function" && !("_tag" in predicateOrState)
|
|
379
|
+
? predicateOrState
|
|
380
|
+
: (s: S) => s._tag === (predicateOrState as { readonly _tag: string })._tag;
|
|
381
|
+
|
|
382
|
+
// Check current state first — SubscriptionRef.get acquires/releases
|
|
383
|
+
// the semaphore quickly (read-only), no deadlock risk.
|
|
384
|
+
const current = yield* SubscriptionRef.get(stateRef);
|
|
385
|
+
if (predicate(current)) return current;
|
|
386
|
+
|
|
387
|
+
// Use sync listener + Deferred to avoid holding the SubscriptionRef
|
|
388
|
+
// semaphore for the duration of a stream (which causes deadlock when
|
|
389
|
+
// send triggers SubscriptionRef.set concurrently).
|
|
390
|
+
const done = yield* Deferred.make<S>();
|
|
391
|
+
const rt = yield* Effect.runtime<never>();
|
|
392
|
+
const runFork = Runtime.runFork(rt);
|
|
393
|
+
const listener = (state: S) => {
|
|
394
|
+
if (predicate(state)) {
|
|
395
|
+
runFork(Deferred.succeed(done, state));
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
listeners.add(listener);
|
|
399
|
+
|
|
400
|
+
// Re-check after subscribing to close the race window
|
|
401
|
+
const afterSubscribe = yield* SubscriptionRef.get(stateRef);
|
|
402
|
+
if (predicate(afterSubscribe)) {
|
|
403
|
+
listeners.delete(listener);
|
|
404
|
+
return afterSubscribe;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const result = yield* Deferred.await(done);
|
|
408
|
+
listeners.delete(listener);
|
|
409
|
+
return result;
|
|
375
410
|
});
|
|
376
411
|
|
|
377
412
|
const awaitFinal = waitFor((state) => machine.finalStates.has(state._tag)).pipe(
|
|
@@ -380,11 +415,11 @@ export const buildActorRefCore = <
|
|
|
380
415
|
|
|
381
416
|
const sendAndWait = Effect.fn("effect-machine.actor.sendAndWait")(function* (
|
|
382
417
|
event: E,
|
|
383
|
-
|
|
418
|
+
predicateOrState?: ((state: S) => boolean) | { readonly _tag: S["_tag"] },
|
|
384
419
|
) {
|
|
385
420
|
yield* send(event);
|
|
386
|
-
if (
|
|
387
|
-
return yield* waitFor(
|
|
421
|
+
if (predicateOrState !== undefined) {
|
|
422
|
+
return yield* waitFor(predicateOrState);
|
|
388
423
|
}
|
|
389
424
|
return yield* awaitFinal;
|
|
390
425
|
});
|
|
@@ -394,6 +429,7 @@ export const buildActorRefCore = <
|
|
|
394
429
|
send,
|
|
395
430
|
state: stateRef,
|
|
396
431
|
stop,
|
|
432
|
+
stopSync: () => Effect.runFork(stop),
|
|
397
433
|
snapshot,
|
|
398
434
|
snapshotSync: () => Effect.runSync(SubscriptionRef.get(stateRef)),
|
|
399
435
|
matches,
|
|
@@ -407,6 +443,12 @@ export const buildActorRefCore = <
|
|
|
407
443
|
waitFor,
|
|
408
444
|
awaitFinal,
|
|
409
445
|
sendAndWait,
|
|
446
|
+
sendSync: (event) => {
|
|
447
|
+
const stopped = Effect.runSync(Ref.get(stoppedRef));
|
|
448
|
+
if (!stopped) {
|
|
449
|
+
Effect.runSync(Queue.offer(eventQueue, event));
|
|
450
|
+
}
|
|
451
|
+
},
|
|
410
452
|
subscribe: (fn) => {
|
|
411
453
|
listeners.add(fn);
|
|
412
454
|
return () => {
|
|
@@ -432,11 +474,6 @@ export const createActor = Effect.fn("effect-machine.actor.spawn")(function* <
|
|
|
432
474
|
>(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>) {
|
|
433
475
|
yield* Effect.annotateCurrentSpan("effect_machine.actor.id", id);
|
|
434
476
|
|
|
435
|
-
const missing = machine._missingSlots();
|
|
436
|
-
if (missing.length > 0) {
|
|
437
|
-
return yield* new UnprovidedSlotsError({ slots: missing });
|
|
438
|
-
}
|
|
439
|
-
|
|
440
477
|
// Get optional inspector from context
|
|
441
478
|
const inspectorValue = Option.getOrUndefined(yield* Effect.serviceOption(InspectorTag)) as
|
|
442
479
|
| Inspector<S, E>
|
|
@@ -477,7 +514,7 @@ export const createActor = Effect.fn("effect-machine.actor.spawn")(function* <
|
|
|
477
514
|
const { effects: effectSlots } = machine._slots;
|
|
478
515
|
|
|
479
516
|
for (const bg of machine.backgroundEffects) {
|
|
480
|
-
const fiber = yield* Effect.
|
|
517
|
+
const fiber = yield* Effect.forkDaemon(
|
|
481
518
|
bg
|
|
482
519
|
.handler({ state: machine.initial, event: initEvent, self, effects: effectSlots })
|
|
483
520
|
.pipe(Effect.provideService(machine.Context, ctx)),
|
|
@@ -520,10 +557,9 @@ export const createActor = Effect.fn("effect-machine.actor.spawn")(function* <
|
|
|
520
557
|
return buildActorRefCore(id, machine, stateRef, eventQueue, stoppedRef, listeners, stop);
|
|
521
558
|
}
|
|
522
559
|
|
|
523
|
-
// Start the event loop — use
|
|
524
|
-
// is
|
|
525
|
-
|
|
526
|
-
const loopFiber = yield* Effect.forkScoped(
|
|
560
|
+
// Start the event loop — use forkDaemon so the event loop fiber's lifetime
|
|
561
|
+
// is detached from any parent scope/fiber. actor.stop handles cleanup.
|
|
562
|
+
const loopFiber = yield* Effect.forkDaemon(
|
|
527
563
|
eventLoop(
|
|
528
564
|
machine,
|
|
529
565
|
stateRef,
|
|
@@ -774,7 +810,16 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
774
810
|
const spawnGate = yield* Effect.makeSemaphore(1);
|
|
775
811
|
const withSpawnGate = spawnGate.withPermits(1);
|
|
776
812
|
|
|
777
|
-
|
|
813
|
+
// Stop all actors on system teardown
|
|
814
|
+
yield* Effect.addFinalizer(() => {
|
|
815
|
+
const stops: Effect.Effect<void>[] = [];
|
|
816
|
+
MutableHashMap.forEach(actors, (actor) => {
|
|
817
|
+
stops.push(actor.stop);
|
|
818
|
+
});
|
|
819
|
+
return Effect.all(stops, { concurrency: "unbounded" }).pipe(Effect.asVoid);
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
/** Check for duplicate ID, register actor, attach scope cleanup if available */
|
|
778
823
|
const registerActor = Effect.fn("effect-machine.actorSystem.register")(function* <
|
|
779
824
|
T extends { stop: Effect.Effect<void> },
|
|
780
825
|
>(id: string, actor: T) {
|
|
@@ -788,13 +833,17 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
788
833
|
// Register it - O(1)
|
|
789
834
|
MutableHashMap.set(actors, id, actor as unknown as ActorRef<AnyState, unknown>);
|
|
790
835
|
|
|
791
|
-
//
|
|
792
|
-
yield* Effect.
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
836
|
+
// If scope available, attach per-actor cleanup
|
|
837
|
+
const maybeScope = yield* Effect.serviceOption(Scope.Scope);
|
|
838
|
+
if (Option.isSome(maybeScope)) {
|
|
839
|
+
yield* Scope.addFinalizer(
|
|
840
|
+
maybeScope.value,
|
|
841
|
+
Effect.gen(function* () {
|
|
842
|
+
yield* actor.stop;
|
|
843
|
+
MutableHashMap.remove(actors, id);
|
|
844
|
+
}),
|
|
845
|
+
);
|
|
846
|
+
}
|
|
798
847
|
|
|
799
848
|
return actor;
|
|
800
849
|
});
|
|
@@ -803,14 +852,12 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
803
852
|
S extends { readonly _tag: string },
|
|
804
853
|
E extends { readonly _tag: string },
|
|
805
854
|
R,
|
|
806
|
-
|
|
807
|
-
EFD extends EffectsDef = Record<string, never>,
|
|
808
|
-
>(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>) {
|
|
855
|
+
>(id: string, built: BuiltMachine<S, E, R>) {
|
|
809
856
|
if (MutableHashMap.has(actors, id)) {
|
|
810
857
|
return yield* new DuplicateActorError({ actorId: id });
|
|
811
858
|
}
|
|
812
859
|
// Create and register the actor
|
|
813
|
-
const actor = yield* createActor(id,
|
|
860
|
+
const actor = yield* createActor(id, built._inner);
|
|
814
861
|
return yield* registerActor(id, actor);
|
|
815
862
|
});
|
|
816
863
|
|
|
@@ -846,68 +893,44 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
846
893
|
S extends { readonly _tag: string },
|
|
847
894
|
E extends { readonly _tag: string },
|
|
848
895
|
R,
|
|
849
|
-
|
|
850
|
-
EFD extends EffectsDef = Record<string, never>,
|
|
851
|
-
>(
|
|
852
|
-
id: string,
|
|
853
|
-
machine:
|
|
854
|
-
| Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>
|
|
855
|
-
| PersistentMachine<S, E, R>,
|
|
856
|
-
) {
|
|
896
|
+
>(id: string, machine: BuiltMachine<S, E, R> | PersistentMachine<S, E, R>) {
|
|
857
897
|
if (isPersistentMachine(machine)) {
|
|
858
898
|
// TypeScript can't narrow union with invariant generic params
|
|
859
899
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
860
900
|
return yield* spawnPersistent(id, machine as PersistentMachine<S, E, R>);
|
|
861
901
|
}
|
|
862
|
-
return yield* spawnRegular(
|
|
863
|
-
id,
|
|
864
|
-
machine as Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>,
|
|
865
|
-
);
|
|
902
|
+
return yield* spawnRegular(id, machine as BuiltMachine<S, E, R>);
|
|
866
903
|
});
|
|
867
904
|
|
|
868
905
|
// Type-safe overloaded spawn implementation
|
|
869
|
-
function spawn<
|
|
870
|
-
S extends { readonly _tag: string },
|
|
871
|
-
E extends { readonly _tag: string },
|
|
872
|
-
R,
|
|
873
|
-
GD extends GuardsDef = Record<string, never>,
|
|
874
|
-
EFD extends EffectsDef = Record<string, never>,
|
|
875
|
-
>(
|
|
906
|
+
function spawn<S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
|
|
876
907
|
id: string,
|
|
877
|
-
machine:
|
|
878
|
-
): Effect.Effect<ActorRef<S, E>, DuplicateActorError
|
|
908
|
+
machine: BuiltMachine<S, E, R>,
|
|
909
|
+
): Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>;
|
|
879
910
|
function spawn<S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
|
|
880
911
|
id: string,
|
|
881
912
|
machine: PersistentMachine<S, E, R>,
|
|
882
913
|
): Effect.Effect<
|
|
883
914
|
PersistentActorRef<S, E, R>,
|
|
884
|
-
PersistenceError | VersionConflictError | DuplicateActorError
|
|
885
|
-
R |
|
|
915
|
+
PersistenceError | VersionConflictError | DuplicateActorError,
|
|
916
|
+
R | PersistenceAdapterTag
|
|
886
917
|
>;
|
|
887
|
-
function spawn<
|
|
888
|
-
S extends { readonly _tag: string },
|
|
889
|
-
E extends { readonly _tag: string },
|
|
890
|
-
R,
|
|
891
|
-
GD extends GuardsDef = Record<string, never>,
|
|
892
|
-
EFD extends EffectsDef = Record<string, never>,
|
|
893
|
-
>(
|
|
918
|
+
function spawn<S extends { readonly _tag: string }, E extends { readonly _tag: string }, R>(
|
|
894
919
|
id: string,
|
|
895
|
-
machine:
|
|
896
|
-
| Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>
|
|
897
|
-
| PersistentMachine<S, E, R>,
|
|
920
|
+
machine: BuiltMachine<S, E, R> | PersistentMachine<S, E, R>,
|
|
898
921
|
):
|
|
899
|
-
| Effect.Effect<ActorRef<S, E>, DuplicateActorError
|
|
922
|
+
| Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>
|
|
900
923
|
| Effect.Effect<
|
|
901
924
|
PersistentActorRef<S, E, R>,
|
|
902
|
-
PersistenceError | VersionConflictError | DuplicateActorError
|
|
903
|
-
R |
|
|
925
|
+
PersistenceError | VersionConflictError | DuplicateActorError,
|
|
926
|
+
R | PersistenceAdapterTag
|
|
904
927
|
> {
|
|
905
928
|
return withSpawnGate(spawnImpl(id, machine)) as
|
|
906
|
-
| Effect.Effect<ActorRef<S, E>, DuplicateActorError
|
|
929
|
+
| Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>
|
|
907
930
|
| Effect.Effect<
|
|
908
931
|
PersistentActorRef<S, E, R>,
|
|
909
|
-
PersistenceError | VersionConflictError | DuplicateActorError
|
|
910
|
-
R |
|
|
932
|
+
PersistenceError | VersionConflictError | DuplicateActorError,
|
|
933
|
+
R | PersistenceAdapterTag
|
|
911
934
|
>;
|
|
912
935
|
}
|
|
913
936
|
|
|
@@ -961,7 +984,7 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
961
984
|
const restored: PersistentActorRef<S, E, R>[] = [];
|
|
962
985
|
const failed: {
|
|
963
986
|
id: string;
|
|
964
|
-
error: PersistenceError | DuplicateActorError
|
|
987
|
+
error: PersistenceError | DuplicateActorError;
|
|
965
988
|
}[] = [];
|
|
966
989
|
|
|
967
990
|
for (const id of ids) {
|
|
@@ -1032,4 +1055,4 @@ const make = Effect.fn("effect-machine.actorSystem.make")(function* () {
|
|
|
1032
1055
|
/**
|
|
1033
1056
|
* Default ActorSystem layer
|
|
1034
1057
|
*/
|
|
1035
|
-
export const Default = Layer.
|
|
1058
|
+
export const Default = Layer.scoped(ActorSystem, make());
|
package/src/errors.ts
CHANGED
|
@@ -49,7 +49,7 @@ export class SlotProvisionError extends Schema.TaggedError<SlotProvisionError>()
|
|
|
49
49
|
},
|
|
50
50
|
) {}
|
|
51
51
|
|
|
52
|
-
/** Machine.
|
|
52
|
+
/** Machine.build() validation failed - missing or extra handlers */
|
|
53
53
|
export class ProvisionValidationError extends Schema.TaggedError<ProvisionValidationError>()(
|
|
54
54
|
"ProvisionValidationError",
|
|
55
55
|
{
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { Cause, Effect, Exit, Scope } from "effect";
|
|
12
12
|
|
|
13
13
|
import type { Machine, MachineRef, Transition, SpawnEffect, HandlerContext } from "../machine.js";
|
|
14
|
+
import { BuiltMachine } from "../machine.js";
|
|
14
15
|
import type { GuardsDef, EffectsDef, MachineContext } from "../slot.js";
|
|
15
16
|
import { isEffect, INTERNAL_ENTER_EVENT } from "./utils.js";
|
|
16
17
|
|
|
@@ -443,6 +444,7 @@ const getIndex = <
|
|
|
443
444
|
* Find all transitions matching a state/event pair.
|
|
444
445
|
* Returns empty array if no matches.
|
|
445
446
|
*
|
|
447
|
+
* Accepts both `Machine` and `BuiltMachine`.
|
|
446
448
|
* O(1) lookup after first access (index is lazily built).
|
|
447
449
|
*/
|
|
448
450
|
export const findTransitions = <
|
|
@@ -453,12 +455,16 @@ export const findTransitions = <
|
|
|
453
455
|
EFD extends EffectsDef = Record<string, never>,
|
|
454
456
|
>(
|
|
455
457
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Schema fields need wide acceptance
|
|
456
|
-
|
|
458
|
+
input: Machine<S, E, R, any, any, GD, EFD> | BuiltMachine<S, E, R>,
|
|
457
459
|
stateTag: string,
|
|
458
460
|
eventTag: string,
|
|
459
461
|
): ReadonlyArray<Transition<S, E, GD, EFD, R>> => {
|
|
462
|
+
const machine = input instanceof BuiltMachine ? input._inner : input;
|
|
460
463
|
const index = getIndex(machine);
|
|
461
|
-
|
|
464
|
+
const specific = index.transitions.get(stateTag)?.get(eventTag) ?? [];
|
|
465
|
+
if (specific.length > 0) return specific;
|
|
466
|
+
// Fallback to wildcard transitions
|
|
467
|
+
return index.transitions.get("*")?.get(eventTag) ?? [];
|
|
462
468
|
};
|
|
463
469
|
|
|
464
470
|
/**
|