effect-machine 0.9.0 → 0.11.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.
Files changed (73) hide show
  1. package/README.md +118 -55
  2. package/dist/actor.d.ts +77 -179
  3. package/dist/actor.js +161 -113
  4. package/dist/cluster/entity-machine.js +5 -3
  5. package/dist/errors.d.ts +12 -1
  6. package/dist/errors.js +8 -1
  7. package/dist/index.d.ts +4 -8
  8. package/dist/index.js +2 -7
  9. package/dist/internal/transition.d.ts +27 -3
  10. package/dist/internal/transition.js +38 -9
  11. package/dist/internal/utils.d.ts +7 -2
  12. package/dist/internal/utils.js +1 -5
  13. package/dist/machine.d.ts +94 -35
  14. package/dist/machine.js +128 -13
  15. package/dist/testing.js +57 -3
  16. package/package.json +10 -9
  17. package/v3/dist/actor.d.ts +210 -0
  18. package/{dist-v3 → v3/dist}/actor.js +198 -117
  19. package/{dist-v3 → v3/dist}/cluster/entity-machine.d.ts +1 -1
  20. package/{dist-v3 → v3/dist}/cluster/entity-machine.js +8 -6
  21. package/{dist-v3 → v3/dist}/cluster/to-entity.d.ts +1 -1
  22. package/{dist-v3 → v3/dist}/cluster/to-entity.js +1 -1
  23. package/v3/dist/errors.d.ts +76 -0
  24. package/{dist-v3 → v3/dist}/errors.js +9 -2
  25. package/v3/dist/index.d.ts +9 -0
  26. package/v3/dist/index.js +8 -0
  27. package/{dist-v3 → v3/dist}/inspection.d.ts +53 -8
  28. package/v3/dist/inspection.js +156 -0
  29. package/{dist-v3 → v3/dist}/internal/brands.d.ts +1 -1
  30. package/{dist-v3 → v3/dist}/internal/inspection.d.ts +1 -1
  31. package/v3/dist/internal/inspection.js +20 -0
  32. package/{dist-v3 → v3/dist}/internal/transition.d.ts +35 -11
  33. package/{dist-v3 → v3/dist}/internal/transition.js +47 -15
  34. package/{dist-v3 → v3/dist}/internal/utils.d.ts +9 -4
  35. package/{dist-v3 → v3/dist}/internal/utils.js +2 -6
  36. package/{dist-v3 → v3/dist}/machine.d.ts +113 -40
  37. package/{dist-v3 → v3/dist}/machine.js +191 -15
  38. package/{dist-v3 → v3/dist}/schema.d.ts +1 -1
  39. package/{dist-v3 → v3/dist}/schema.js +5 -2
  40. package/{dist-v3 → v3/dist}/slot.d.ts +4 -3
  41. package/{dist-v3 → v3/dist}/slot.js +1 -1
  42. package/{dist-v3 → v3/dist}/testing.d.ts +14 -8
  43. package/{dist-v3 → v3/dist}/testing.js +60 -6
  44. package/dist/persistence/adapter.d.ts +0 -135
  45. package/dist/persistence/adapter.js +0 -25
  46. package/dist/persistence/adapters/in-memory.d.ts +0 -32
  47. package/dist/persistence/adapters/in-memory.js +0 -174
  48. package/dist/persistence/index.d.ts +0 -5
  49. package/dist/persistence/index.js +0 -5
  50. package/dist/persistence/persistent-actor.d.ts +0 -50
  51. package/dist/persistence/persistent-actor.js +0 -368
  52. package/dist/persistence/persistent-machine.d.ts +0 -105
  53. package/dist/persistence/persistent-machine.js +0 -22
  54. package/dist-v3/actor.d.ts +0 -291
  55. package/dist-v3/errors.d.ts +0 -27
  56. package/dist-v3/index.d.ts +0 -12
  57. package/dist-v3/index.js +0 -13
  58. package/dist-v3/inspection.js +0 -48
  59. package/dist-v3/internal/inspection.js +0 -13
  60. package/dist-v3/persistence/adapter.d.ts +0 -125
  61. package/dist-v3/persistence/adapter.js +0 -25
  62. package/dist-v3/persistence/adapters/in-memory.d.ts +0 -32
  63. package/dist-v3/persistence/adapters/in-memory.js +0 -174
  64. package/dist-v3/persistence/index.d.ts +0 -5
  65. package/dist-v3/persistence/index.js +0 -5
  66. package/dist-v3/persistence/persistent-actor.d.ts +0 -49
  67. package/dist-v3/persistence/persistent-actor.js +0 -365
  68. package/dist-v3/persistence/persistent-machine.d.ts +0 -105
  69. package/dist-v3/persistence/persistent-machine.js +0 -22
  70. /package/{dist-v3 → v3/dist}/_virtual/_rolldown/runtime.js +0 -0
  71. /package/{dist-v3 → v3/dist}/cluster/index.d.ts +0 -0
  72. /package/{dist-v3 → v3/dist}/cluster/index.js +0 -0
  73. /package/{dist-v3 → v3/dist}/internal/brands.js +0 -0
package/README.md CHANGED
@@ -74,8 +74,12 @@ const orderMachine = Machine.make({
74
74
  const program = Effect.gen(function* () {
75
75
  const actor = yield* Machine.spawn(orderMachine);
76
76
 
77
+ // fire-and-forget
77
78
  yield* actor.send(OrderEvent.Process);
78
- yield* actor.send(OrderEvent.Ship({ trackingId: "TRACK-123" }));
79
+
80
+ // request-reply — get ProcessEventResult back
81
+ const result = yield* actor.call(OrderEvent.Ship({ trackingId: "TRACK-123" }));
82
+ console.log(result.transitioned); // true
79
83
 
80
84
  const state = yield* actor.waitFor(OrderState.Shipped);
81
85
  console.log(state); // Shipped { orderId: "order-1", trackingId: "TRACK-123" }
@@ -183,6 +187,53 @@ machine.task(State.Loading, ({ effects, state }) => effects.fetchData({ url: sta
183
187
  });
184
188
  ```
185
189
 
190
+ ### State Timeouts
191
+
192
+ `.timeout()` — gen_statem-style state timeouts. Timer starts on state entry, cancels on exit:
193
+
194
+ ```ts
195
+ machine
196
+ .timeout(State.Loading, {
197
+ duration: Duration.seconds(30),
198
+ event: Event.Timeout,
199
+ })
200
+ // Dynamic duration from state
201
+ .timeout(State.Retrying, {
202
+ duration: (state) => Duration.seconds(state.backoff),
203
+ event: Event.GiveUp,
204
+ });
205
+ ```
206
+
207
+ `.reenter()` restarts the timer with fresh state values.
208
+
209
+ ### Event Postpone
210
+
211
+ `.postpone()` — gen_statem-style event postpone. When a matching event arrives in the given state, it is buffered. After the next state transition (tag change), buffered events drain in FIFO order, looping until stable:
212
+
213
+ ```ts
214
+ machine
215
+ .postpone(State.Connecting, Event.Data) // single event
216
+ .postpone(State.Connecting, [Event.Data, Event.Cmd]); // multiple events
217
+ ```
218
+
219
+ Reply-bearing events (`call`/`ask`) in the postpone buffer are settled with `ActorStoppedError` on stop/interrupt/final-state.
220
+
221
+ ### ask / reply
222
+
223
+ Handlers can return a domain reply via `{ state, reply }`:
224
+
225
+ ```ts
226
+ .on(State.Active, Event.GetCount, ({ state }) => ({
227
+ state, // stay in same state
228
+ reply: state.count, // domain value returned to caller
229
+ }))
230
+
231
+ // Caller side:
232
+ const count = yield* actor.ask<number>(Event.GetCount);
233
+ ```
234
+
235
+ `ask` fails with `NoReplyError` if the handler doesn't provide a reply, and `ActorStoppedError` if the actor stops while the request is pending.
236
+
186
237
  ### Child Actors
187
238
 
188
239
  Spawn children from `.spawn()` handlers with `self.spawn`. Children are state-scoped — auto-stopped on state exit:
@@ -206,6 +257,31 @@ const child = yield * parent.system.get("worker-1"); // Option<ActorRef>
206
257
 
207
258
  Every actor always has a system — `Machine.spawn` creates an implicit one if no `ActorSystem` is in context.
208
259
 
260
+ ### Persistence
261
+
262
+ Persistence is composed from primitives — no built-in adapter or framework:
263
+
264
+ ```ts
265
+ // Snapshot persistence — observe state changes, save externally
266
+ yield * actor.changes.pipe(Stream.runForEach((state) => saveSnapshot(actor.id, state)));
267
+
268
+ // Event journal — observe transitions
269
+ yield * actor.transitions.pipe(Stream.runForEach(({ event }) => appendEvent(actor.id, event)));
270
+
271
+ // Restore from snapshot
272
+ const savedState = yield * loadSnapshot(id);
273
+ const actor = yield * Machine.spawn(machine, { hydrate: savedState });
274
+
275
+ // Restore from event log
276
+ const events = yield * loadEvents(id);
277
+ const state = yield * Machine.replay(machine, events);
278
+ const actor = yield * Machine.spawn(machine, { hydrate: state });
279
+
280
+ // Restore from snapshot + tail events
281
+ const state = yield * Machine.replay(machine, tailEvents, { from: snapshot });
282
+ const actor = yield * Machine.spawn(machine, { hydrate: state });
283
+ ```
284
+
209
285
  ### System Observation
210
286
 
211
287
  React to actors joining and leaving the system:
@@ -245,21 +321,6 @@ expect(result.states.map((s) => s._tag)).toEqual(["Idle", "Loading", "Done"]);
245
321
  yield * assertPath(machine, events, ["Idle", "Loading", "Done"]);
246
322
  ```
247
323
 
248
- ## Documentation
249
-
250
- See the [primer](./primer/) for comprehensive documentation:
251
-
252
- | Topic | File | Description |
253
- | ----------- | ----------------------------------------- | ------------------------------ |
254
- | Overview | [index.md](./primer/index.md) | Navigation and quick reference |
255
- | Basics | [basics.md](./primer/basics.md) | Core concepts |
256
- | Handlers | [handlers.md](./primer/handlers.md) | Transitions and guards |
257
- | Effects | [effects.md](./primer/effects.md) | spawn, background, timeouts |
258
- | Testing | [testing.md](./primer/testing.md) | simulate, harness, assertions |
259
- | Actors | [actors.md](./primer/actors.md) | ActorSystem, ActorRef |
260
- | Persistence | [persistence.md](./primer/persistence.md) | Snapshots, event sourcing |
261
- | Gotchas | [gotchas.md](./primer/gotchas.md) | Common mistakes |
262
-
263
324
  ## API Quick Reference
264
325
 
265
326
  ### Building
@@ -273,55 +334,47 @@ See the [primer](./primer/) for comprehensive documentation:
273
334
  | `.reenter(State.X, Event.Y, handler)` | Force re-entry on same state |
274
335
  | `.spawn(State.X, handler)` | State-scoped effect |
275
336
  | `.task(State.X, run, { onSuccess })` | State-scoped task |
337
+ | `.timeout(State.X, { duration, event })` | State timeout (gen_statem) |
338
+ | `.postpone(State.X, Event.Y)` | Postpone event in state (gen_statem) |
276
339
  | `.background(handler)` | Machine-lifetime effect |
277
340
  | `.final(State.X)` | Mark final state |
278
341
  | `.build({ slot: impl })` | Provide implementations, returns `BuiltMachine` (terminal) |
279
342
  | `.build()` | Finalize no-slot machine, returns `BuiltMachine` (terminal) |
280
- | `.persist(config)` | Enable persistence |
281
-
282
- ### State Constructors
283
-
284
- | Method | Purpose |
285
- | -------------------------------------- | ------------------------------ |
286
- | `State.X.derive(source)` | Pick target fields from source |
287
- | `State.X.derive(source, { field: v })` | Pick fields + apply overrides |
288
- | `State.$is("X")(value)` | Type guard |
289
- | `State.$match(value, { X: fn, ... })` | Pattern matching |
290
343
 
291
344
  ### Running
292
345
 
293
- | Method | Purpose |
294
- | ---------------------------- | ------------------------------------------------------------------------------------------------------- |
295
- | `Machine.spawn(machine)` | Single actor, no registry. Caller manages lifetime via `actor.stop`. Auto-cleans up if `Scope` present. |
296
- | `Machine.spawn(machine, id)` | Same as above with custom ID |
297
- | `system.spawn(id, machine)` | Registry, lookup by ID, bulk ops, persistence. Cleans up on system teardown. |
298
-
299
- ### Testing
300
-
301
- | Function | Description |
302
- | ------------------------------------------ | ---------------------------------------------------------------- |
303
- | `simulate(machine, events)` | Run events, get all states (accepts `Machine` or `BuiltMachine`) |
304
- | `createTestHarness(machine)` | Step-by-step testing (accepts `Machine` or `BuiltMachine`) |
305
- | `assertPath(machine, events, path)` | Assert exact path |
306
- | `assertReaches(machine, events, tag)` | Assert final state |
307
- | `assertNeverReaches(machine, events, tag)` | Assert state never visited |
346
+ | Method | Purpose |
347
+ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------- |
348
+ | `Machine.spawn(machine)` | Single actor, no registry. Caller manages lifetime via `actor.stop`. Auto-cleans up if `Scope` present. |
349
+ | `Machine.spawn(machine, id)` | Same as above with custom ID |
350
+ | `Machine.spawn(machine, { hydrate: s })` | Restore from saved state re-runs spawn effects for that state |
351
+ | `Machine.replay(machine, events)` | Fold events through handlers to compute state (for event sourcing restore) |
352
+ | `system.spawn(id, machine)` | Registry, lookup by ID, bulk ops. Cleans up on system teardown. |
308
353
 
309
354
  ### Actor
310
355
 
311
- | Method | Description |
312
- | -------------------------------- | ---------------------------------- |
313
- | `actor.send(event)` | Queue event |
314
- | `actor.sendSync(event)` | Fire-and-forget (sync, for UI) |
315
- | `actor.snapshot` | Get current state |
316
- | `actor.matches(tag)` | Check state tag |
317
- | `actor.can(event)` | Can handle event? |
318
- | `actor.changes` | Stream of changes |
319
- | `actor.waitFor(State.X)` | Wait for state (constructor or fn) |
320
- | `actor.awaitFinal` | Wait final state |
321
- | `actor.sendAndWait(ev, State.X)` | Send + wait for state |
322
- | `actor.subscribe(fn)` | Sync callback |
323
- | `actor.system` | Access the actor's `ActorSystem` |
324
- | `actor.children` | Child actors (`ReadonlyMap`) |
356
+ | Method | Description |
357
+ | -------------------------------- | ----------------------------------------------- |
358
+ | `actor.send(event)` | Fire-and-forget (queue event) |
359
+ | `actor.cast(event)` | Alias for send (OTP gen_server:cast) |
360
+ | `actor.call(event)` | Request-reply, returns `ProcessEventResult` |
361
+ | `actor.ask<R>(event)` | Typed domain reply from handler |
362
+ | `actor.snapshot` | Get current state |
363
+ | `actor.matches(tag)` | Check state tag |
364
+ | `actor.can(event)` | Can handle event? |
365
+ | `actor.changes` | Stream of state changes |
366
+ | `actor.transitions` | Stream of `{ fromState, toState, event }` edges |
367
+ | `actor.waitFor(State.X)` | Wait for state (constructor or fn) |
368
+ | `actor.awaitFinal` | Wait final state |
369
+ | `actor.sendAndWait(ev, State.X)` | Send + wait for state |
370
+ | `actor.subscribe(fn)` | Sync callback |
371
+ | `actor.sync.send(event)` | Sync fire-and-forget (for UI) |
372
+ | `actor.sync.stop()` | Sync stop |
373
+ | `actor.sync.snapshot()` | Sync get state |
374
+ | `actor.sync.matches(tag)` | Sync check state tag |
375
+ | `actor.sync.can(event)` | Sync can handle event? |
376
+ | `actor.system` | Access the actor's `ActorSystem` |
377
+ | `actor.children` | Child actors (`ReadonlyMap`) |
325
378
 
326
379
  ### ActorSystem
327
380
 
@@ -334,6 +387,16 @@ See the [primer](./primer/) for comprehensive documentation:
334
387
  | `system.subscribe(fn)` | Sync callback for spawn/stop events |
335
388
  | `system.events` | Async `Stream<SystemEvent>` for spawn/stop |
336
389
 
390
+ ### Testing
391
+
392
+ | Function | Description |
393
+ | ------------------------------------------ | ---------------------------------------------------------------- |
394
+ | `simulate(machine, events)` | Run events, get all states (accepts `Machine` or `BuiltMachine`) |
395
+ | `createTestHarness(machine)` | Step-by-step testing (accepts `Machine` or `BuiltMachine`) |
396
+ | `assertPath(machine, events, path)` | Assert exact path |
397
+ | `assertReaches(machine, events, tag)` | Assert final state |
398
+ | `assertNeverReaches(machine, events, tag)` | Assert state never visited |
399
+
337
400
  ## License
338
401
 
339
402
  MIT
package/dist/actor.d.ts CHANGED
@@ -1,95 +1,99 @@
1
1
  import { EffectsDef, GuardsDef, MachineContext } from "./slot.js";
2
- import { PersistentMachine } from "./persistence/persistent-machine.js";
3
- import { DuplicateActorError } from "./errors.js";
2
+ import { ActorStoppedError, DuplicateActorError, NoReplyError } from "./errors.js";
4
3
  import { ProcessEventError, ProcessEventHooks, ProcessEventResult, processEventCore, resolveTransition, runSpawnEffects } from "./internal/transition.js";
5
- import { PersistentActorRef } from "./persistence/persistent-actor.js";
6
- import { ActorMetadata, PersistenceAdapterTag, PersistenceError, RestoreResult, VersionConflictError } from "./persistence/adapter.js";
7
4
  import { BuiltMachine, Machine, MachineRef } from "./machine.js";
8
- import { Deferred, Effect, Layer, Option, Queue, Ref, Scope, ServiceMap, Stream, SubscriptionRef } from "effect";
5
+ import { Deferred, Effect, Layer, Option, PubSub, Queue, Ref, Scope, ServiceMap, Stream, SubscriptionRef } from "effect";
9
6
  import * as effect_Tracer0 from "effect/Tracer";
10
7
 
11
8
  //#region src/actor.d.ts
12
- /** Queued event with optional reply channel */
13
- interface QueuedEvent<E> {
9
+ /** Discriminated mailbox request */
10
+ type QueuedEvent<E> = {
11
+ readonly _tag: "send";
14
12
  readonly event: E;
15
- readonly reply?: Deferred.Deferred<ProcessEventResult<{
13
+ } | {
14
+ readonly _tag: "call";
15
+ readonly event: E;
16
+ readonly reply: Deferred.Deferred<ProcessEventResult<{
16
17
  readonly _tag: string;
17
- }>>;
18
- }
18
+ }>, ActorStoppedError>;
19
+ } | {
20
+ readonly _tag: "ask";
21
+ readonly event: E;
22
+ readonly reply: Deferred.Deferred<unknown, NoReplyError | ActorStoppedError>;
23
+ };
19
24
  /**
20
25
  * Reference to a running actor.
21
26
  */
27
+ /**
28
+ * Sync projection of ActorRef for non-Effect boundaries (React hooks, framework callbacks).
29
+ */
30
+ interface ActorRefSync<State extends {
31
+ readonly _tag: string;
32
+ }, Event> {
33
+ readonly send: (event: Event) => void;
34
+ readonly stop: () => void;
35
+ readonly snapshot: () => State;
36
+ readonly matches: (tag: State["_tag"]) => boolean;
37
+ readonly can: (event: Event) => boolean;
38
+ }
39
+ /**
40
+ * Information about a successful transition.
41
+ * Emitted on the `transitions` stream after each accepted event.
42
+ */
43
+ interface TransitionInfo<State, Event> {
44
+ readonly fromState: State;
45
+ readonly toState: State;
46
+ readonly event: Event;
47
+ }
22
48
  interface ActorRef<State extends {
23
49
  readonly _tag: string;
24
50
  }, Event> {
25
- /**
26
- * Unique identifier for this actor
27
- */
28
51
  readonly id: string;
29
- /**
30
- * Send an event to the actor
31
- */
52
+ /** Send an event (fire-and-forget). */
32
53
  readonly send: (event: Event) => Effect.Effect<void>;
54
+ /** Fire-and-forget alias for send (OTP gen_server:cast). */
55
+ readonly cast: (event: Event) => Effect.Effect<void>;
33
56
  /**
34
- * Observable state of the actor
57
+ * Serialized request-reply (OTP gen_server:call).
58
+ * Event is processed through the queue; caller gets ProcessEventResult back.
35
59
  */
36
- readonly state: SubscriptionRef.SubscriptionRef<State>;
60
+ readonly call: (event: Event) => Effect.Effect<ProcessEventResult<State>>;
37
61
  /**
38
- * Stop the actor gracefully
62
+ * Typed request-reply. Event is processed through the queue; caller gets
63
+ * the domain value returned by the handler's `reply` field.
64
+ * Fails with NoReplyError if the handler doesn't provide a reply.
39
65
  */
66
+ readonly ask: <R>(event: Event) => Effect.Effect<R, NoReplyError | ActorStoppedError>;
67
+ /** Observable state. */
68
+ readonly state: SubscriptionRef.SubscriptionRef<State>;
69
+ /** Stop the actor gracefully. */
40
70
  readonly stop: Effect.Effect<void>;
41
- /**
42
- * Stop the actor (fire-and-forget).
43
- * Signals graceful shutdown without waiting for completion.
44
- * Use when stopping from sync contexts (e.g. framework cleanup hooks).
45
- */
46
- readonly stopSync: () => void;
47
- /**
48
- * Get current state snapshot (Effect)
49
- */
71
+ /** Get current state snapshot. */
50
72
  readonly snapshot: Effect.Effect<State>;
51
- /**
52
- * Get current state snapshot (sync)
53
- */
54
- readonly snapshotSync: () => State;
55
- /**
56
- * Check if current state matches tag (Effect)
57
- */
73
+ /** Check if current state matches tag. */
58
74
  readonly matches: (tag: State["_tag"]) => Effect.Effect<boolean>;
59
- /**
60
- * Check if current state matches tag (sync)
61
- */
62
- readonly matchesSync: (tag: State["_tag"]) => boolean;
63
- /**
64
- * Check if event can be handled in current state (Effect)
65
- */
75
+ /** Check if event can be handled in current state. */
66
76
  readonly can: (event: Event) => Effect.Effect<boolean>;
67
- /**
68
- * Check if event can be handled in current state (sync)
69
- */
70
- readonly canSync: (event: Event) => boolean;
71
- /**
72
- * Stream of state changes
73
- */
77
+ /** Stream of state changes. */
74
78
  readonly changes: Stream.Stream<State>;
75
79
  /**
76
- * Wait for a state that matches predicate or state variant (includes current snapshot).
77
- * Accepts a predicate function or a state constructor/value (e.g. `State.Active`).
80
+ * Stream of accepted transitions (edge stream).
81
+ *
82
+ * Emits `{ fromState, toState, event }` on every successful transition,
83
+ * including same-state reenters. PubSub-backed — late subscribers miss
84
+ * past edges. This is observational, not a durability guarantee.
78
85
  */
86
+ readonly transitions: Stream.Stream<TransitionInfo<State, Event>>;
87
+ /** Wait for a state matching predicate or variant (includes current snapshot). */
79
88
  readonly waitFor: {
80
89
  (predicate: (state: State) => boolean): Effect.Effect<State>;
81
90
  (state: {
82
91
  readonly _tag: State["_tag"];
83
92
  }): Effect.Effect<State>;
84
93
  };
85
- /**
86
- * Wait for a final state (includes current snapshot)
87
- */
94
+ /** Wait for a final state (includes current snapshot). */
88
95
  readonly awaitFinal: Effect.Effect<State>;
89
- /**
90
- * Send event and wait for predicate, state variant, or final state.
91
- * Accepts a predicate function or a state constructor/value (e.g. `State.Active`).
92
- */
96
+ /** Send event and wait for predicate, state variant, or final state. */
93
97
  readonly sendAndWait: {
94
98
  (event: Event, predicate: (state: State) => boolean): Effect.Effect<State>;
95
99
  (event: Event, state: {
@@ -97,39 +101,13 @@ interface ActorRef<State extends {
97
101
  }): Effect.Effect<State>;
98
102
  (event: Event): Effect.Effect<State>;
99
103
  };
100
- /**
101
- * Send event synchronously (fire-and-forget).
102
- * No-op on stopped actors. Use when you need to send from sync contexts
103
- * (e.g. framework hooks, event handlers).
104
- */
105
- readonly sendSync: (event: Event) => void;
106
- /**
107
- * Send event and wait for the transition result (synchronous processing).
108
- * The event is processed through the queue (preserving serialization)
109
- * but the caller gets back the ProcessEventResult.
110
- *
111
- * OTP gen_server:call equivalent — use when you need to know what happened.
112
- */
113
- readonly dispatch: (event: Event) => Effect.Effect<ProcessEventResult<State>>;
114
- /**
115
- * Promise-based dispatch — send event and get back the transition result.
116
- * Use at non-Effect boundaries (React event handlers, framework hooks, tests).
117
- */
118
- readonly dispatchPromise: (event: Event) => Promise<ProcessEventResult<State>>;
119
- /**
120
- * Subscribe to state changes (sync callback)
121
- * Returns unsubscribe function
122
- */
104
+ /** Subscribe to state changes (sync callback). Returns unsubscribe function. */
123
105
  readonly subscribe: (fn: (state: State) => void) => () => void;
124
- /**
125
- * The actor system this actor belongs to.
126
- * Every actor always has a system either inherited from context or implicitly created.
127
- */
106
+ /** Sync helpers for non-Effect boundaries. */
107
+ readonly sync: ActorRefSync<State, Event>;
108
+ /** The actor system this actor belongs to. */
128
109
  readonly system: ActorSystem;
129
- /**
130
- * Child actors spawned via `self.spawn` in this actor's handlers.
131
- * State-scoped children are auto-removed on state exit.
132
- */
110
+ /** Child actors spawned via `self.spawn` in this actor's handlers. */
133
111
  readonly children: ReadonlyMap<string, ActorRef<AnyState, unknown>>;
134
112
  }
135
113
  /** Base type for stored actors (internal) */
@@ -159,54 +137,17 @@ interface ActorSystem {
159
137
  /**
160
138
  * Spawn a new actor with the given machine.
161
139
  *
162
- * For regular machines, returns ActorRef.
163
- * For persistent machines (created with Machine.persist), returns PersistentActorRef.
164
- *
165
- * All effect slots must be provided via `.build()` before spawning.
166
- *
167
140
  * @example
168
141
  * ```ts
169
- * // Regular machine (built)
170
142
  * const built = machine.build({ fetchData: ... })
171
143
  * const actor = yield* system.spawn("my-actor", built);
172
- *
173
- * // Persistent machine (auto-detected)
174
- * const persistentActor = yield* system.spawn("my-actor", persistentMachine);
175
- * persistentActor.persist; // available
176
- * persistentActor.version; // available
177
144
  * ```
178
145
  */
179
- readonly spawn: {
180
- <S extends {
181
- readonly _tag: string;
182
- }, E extends {
183
- readonly _tag: string;
184
- }, R>(id: string, machine: BuiltMachine<S, E, R>): Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>;
185
- <S extends {
186
- readonly _tag: string;
187
- }, E extends {
188
- readonly _tag: string;
189
- }, R>(id: string, machine: PersistentMachine<S, E, R>): Effect.Effect<PersistentActorRef<S, E, R>, PersistenceError | VersionConflictError | DuplicateActorError, R | PersistenceAdapterTag>;
190
- };
191
- /**
192
- * Restore an actor from persistence.
193
- * Returns None if no persisted state exists for the given ID.
194
- *
195
- * @example
196
- * ```ts
197
- * const maybeActor = yield* system.restore("order-1", persistentMachine);
198
- * if (Option.isSome(maybeActor)) {
199
- * const actor = maybeActor.value;
200
- * const state = yield* actor.snapshot;
201
- * console.log(`Restored to state: ${state._tag}`);
202
- * }
203
- * ```
204
- */
205
- readonly restore: <S extends {
146
+ readonly spawn: <S extends {
206
147
  readonly _tag: string;
207
148
  }, E extends {
208
149
  readonly _tag: string;
209
- }, R>(id: string, machine: PersistentMachine<S, E, R>) => Effect.Effect<Option.Option<PersistentActorRef<S, E, R>>, PersistenceError | DuplicateActorError, R | PersistenceAdapterTag>;
150
+ }, R>(id: string, machine: BuiltMachine<S, E, R>) => Effect.Effect<ActorRef<S, E>, DuplicateActorError, R>;
210
151
  /**
211
152
  * Get an existing actor by ID
212
153
  */
@@ -230,53 +171,6 @@ interface ActorSystem {
230
171
  * Returns an unsubscribe function.
231
172
  */
232
173
  readonly subscribe: (fn: SystemEventListener) => () => void;
233
- /**
234
- * List all persisted actor metadata.
235
- * Returns empty array if adapter doesn't support registry.
236
- *
237
- * @example
238
- * ```ts
239
- * const actors = yield* system.listPersisted();
240
- * for (const meta of actors) {
241
- * console.log(`${meta.id}: ${meta.stateTag} (v${meta.version})`);
242
- * }
243
- * ```
244
- */
245
- readonly listPersisted: () => Effect.Effect<ReadonlyArray<ActorMetadata>, PersistenceError, PersistenceAdapterTag>;
246
- /**
247
- * Restore multiple actors by ID.
248
- * Returns both successfully restored actors and failures.
249
- *
250
- * @example
251
- * ```ts
252
- * const result = yield* system.restoreMany(["order-1", "order-2"], orderMachine);
253
- * console.log(`Restored: ${result.restored.length}, Failed: ${result.failed.length}`);
254
- * ```
255
- */
256
- readonly restoreMany: <S extends {
257
- readonly _tag: string;
258
- }, E extends {
259
- readonly _tag: string;
260
- }, R>(ids: ReadonlyArray<string>, machine: PersistentMachine<S, E, R>) => Effect.Effect<RestoreResult<S, E, R>, never, R | PersistenceAdapterTag>;
261
- /**
262
- * Restore all persisted actors for a machine type.
263
- * Uses adapter registry if available, otherwise returns empty result.
264
- *
265
- * @example
266
- * ```ts
267
- * const result = yield* system.restoreAll(orderMachine, {
268
- * filter: (meta) => meta.stateTag !== "Done"
269
- * });
270
- * console.log(`Restored ${result.restored.length} active orders`);
271
- * ```
272
- */
273
- readonly restoreAll: <S extends {
274
- readonly _tag: string;
275
- }, E extends {
276
- readonly _tag: string;
277
- }, R>(machine: PersistentMachine<S, E, R>, options?: {
278
- filter?: (meta: ActorMetadata) => boolean;
279
- }) => Effect.Effect<RestoreResult<S, E, R>, PersistenceError, R | PersistenceAdapterTag>;
280
174
  }
281
175
  /**
282
176
  * ActorSystem service tag
@@ -289,13 +183,13 @@ type Listeners<S> = Set<(state: S) => void>;
289
183
  */
290
184
  declare const notifyListeners: <S>(listeners: Listeners<S>, state: S) => void;
291
185
  /**
292
- * Build core ActorRef methods shared between regular and persistent actors.
186
+ * Build core ActorRef methods.
293
187
  */
294
188
  declare const buildActorRefCore: <S extends {
295
189
  readonly _tag: string;
296
190
  }, E extends {
297
191
  readonly _tag: string;
298
- }, 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<QueuedEvent<E>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>) => ActorRef<S, E>;
192
+ }, 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<QueuedEvent<E>>, stoppedRef: Ref.Ref<boolean>, listeners: Listeners<S>, stop: Effect.Effect<void>, system: ActorSystem, childrenMap: ReadonlyMap<string, ActorRef<AnyState, unknown>>, pendingReplies: Set<Deferred.Deferred<unknown, unknown>>, transitionsPubSub?: PubSub.PubSub<TransitionInfo<S, E>>) => ActorRef<S, E>;
299
193
  /**
300
194
  * Create and start an actor for a machine
301
195
  */
@@ -303,10 +197,14 @@ declare const createActor: <S extends {
303
197
  readonly _tag: string;
304
198
  }, E extends {
305
199
  readonly _tag: string;
306
- }, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>) => Effect.Effect<ActorRef<S, E>, never, Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, effect_Tracer0.ParentSpan> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope> | Exclude<Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>, effect_Tracer0.ParentSpan>>;
200
+ }, R, GD extends GuardsDef, EFD extends EffectsDef>(id: string, machine: Machine<S, E, R, Record<string, never>, Record<string, never>, GD, EFD>, options?: {
201
+ initialState?: S;
202
+ } | undefined) => Effect.Effect<ActorRef<S, E>, never, Exclude<R, MachineContext<S, E, MachineRef<E>>> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, effect_Tracer0.ParentSpan> | Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope> | Exclude<Exclude<Exclude<R, MachineContext<S, E, MachineRef<E>>>, Scope.Scope>, effect_Tracer0.ParentSpan>>;
203
+ /** Fail all pending call/ask Deferreds with ActorStoppedError. Safe to call multiple times. */
204
+ declare const settlePendingReplies: (pendingReplies: Set<Deferred.Deferred<unknown, unknown>>, actorId: string) => Effect.Effect<void, never, never>;
307
205
  /**
308
206
  * Default ActorSystem layer
309
207
  */
310
208
  declare const Default: Layer.Layer<ActorSystem, never, never>;
311
209
  //#endregion
312
- export { ActorRef, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects };
210
+ export { ActorRef, ActorRefSync, ActorSystem, Default, Listeners, type ProcessEventError, type ProcessEventHooks, type ProcessEventResult, QueuedEvent, SystemEvent, SystemEventListener, TransitionInfo, buildActorRefCore, createActor, notifyListeners, processEventCore, resolveTransition, runSpawnEffects, settlePendingReplies };