effect-machine 0.19.0 → 0.20.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 +171 -173
- package/dist/actor.d.ts +65 -22
- package/dist/actor.js +172 -78
- package/dist/atom.d.ts +28 -3
- package/dist/atom.js +20 -2
- package/dist/cluster/entity-machine.d.ts +9 -3
- package/dist/cluster/entity-machine.js +8 -8
- package/dist/cluster/index.d.ts +2 -2
- package/dist/cluster/to-entity.d.ts +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/inspection.d.ts +31 -3
- package/dist/inspection.js +21 -0
- package/dist/internal/inspection.d.ts +1 -1
- package/dist/internal/inspection.js +23 -1
- package/dist/internal/machine-definition.d.ts +1 -0
- package/dist/internal/machine-initialization.d.ts +21 -0
- package/dist/internal/machine-initialization.js +27 -0
- package/dist/internal/runtime.d.ts +15 -1
- package/dist/internal/runtime.js +67 -26
- package/dist/internal/transition.d.ts +51 -3
- package/dist/internal/transition.js +216 -38
- package/dist/machine.d.ts +151 -40
- package/dist/machine.js +139 -40
- package/dist/supervision.d.ts +3 -2
- package/dist/supervision.js +3 -2
- package/dist/testing.d.ts +39 -67
- package/dist/testing.js +13 -52
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Type-safe state machines for [Effect](https://effect.website).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Effect Machine gives one actor a schema-first state model, a typed event mailbox, scoped Effect work, typed input and output, supervision, persistence hooks, inspection, and framework-neutral Atom integration.
|
|
6
6
|
|
|
7
|
-
Use it when a feature has
|
|
8
|
-
|
|
9
|
-
- multiple valid and invalid states
|
|
10
|
-
- async work tied to state entry
|
|
11
|
-
- retries, timeouts, cancellation, or backpressure
|
|
12
|
-
- logic you want to reuse in-process, in tests, and in distributed systems
|
|
7
|
+
Use it when a feature has several valid states, invalid transitions, state-owned async work, timeouts, cancellation, actor coordination, or UI views that need precise subscriptions.
|
|
13
8
|
|
|
14
9
|
## Install
|
|
15
10
|
|
|
@@ -17,244 +12,247 @@ Use it when a feature has:
|
|
|
17
12
|
bun add effect-machine effect
|
|
18
13
|
```
|
|
19
14
|
|
|
20
|
-
`effect` is a peer dependency.
|
|
21
|
-
|
|
15
|
+
`effect` is a required peer dependency.
|
|
16
|
+
|
|
17
|
+
## Imports
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { Event, Machine, State } from "effect-machine";
|
|
21
|
+
import * as ActorAtom from "effect-machine/atom";
|
|
22
|
+
import { EntityMachine, toEntity } from "effect-machine/cluster";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Use `effect-machine` for local machines and actors. Use `effect-machine/atom` for React, Solid, or another Effect Atom binding. Use `effect-machine/cluster` for distributed entity machines.
|
|
22
26
|
|
|
23
|
-
##
|
|
27
|
+
## First machine
|
|
24
28
|
|
|
25
|
-
States and events are schemas.
|
|
29
|
+
States and events are Effect schemas.
|
|
26
30
|
|
|
27
31
|
```ts
|
|
28
|
-
import {
|
|
32
|
+
import { Effect, Schema } from "effect";
|
|
29
33
|
import { Event, Machine, State } from "effect-machine";
|
|
30
34
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Failed: {
|
|
35
|
+
const DownloadState = State({
|
|
36
|
+
Idle: {},
|
|
37
|
+
Downloading: { url: Schema.String },
|
|
38
|
+
Done: { url: Schema.String, bytes: Schema.Finite },
|
|
39
|
+
Failed: { url: Schema.String, message: Schema.String },
|
|
36
40
|
});
|
|
37
41
|
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Cancel: {},
|
|
42
|
+
const DownloadEvent = Event({
|
|
43
|
+
Start: { url: Schema.String },
|
|
44
|
+
Completed: { bytes: Schema.Finite },
|
|
45
|
+
Failed: { message: Schema.String },
|
|
43
46
|
});
|
|
44
47
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
cartId: string,
|
|
50
|
-
totalCents: number,
|
|
51
|
-
) => Effect.Effect<{ readonly receiptId: string }>;
|
|
52
|
-
}
|
|
53
|
-
>()("app/PaymentService") {}
|
|
54
|
-
|
|
55
|
-
const checkoutMachine = Machine.make({
|
|
56
|
-
state: CheckoutState,
|
|
57
|
-
event: CheckoutEvent,
|
|
58
|
-
initial: CheckoutState.ReviewingCart({ cartId: "cart_123", totalCents: 4200 }),
|
|
48
|
+
const downloadMachine = Machine.make({
|
|
49
|
+
state: DownloadState,
|
|
50
|
+
event: DownloadEvent,
|
|
51
|
+
initial: DownloadState.Idle,
|
|
59
52
|
})
|
|
60
|
-
.on(
|
|
61
|
-
|
|
62
|
-
)
|
|
63
|
-
.on(CheckoutState.ChargingCard, CheckoutEvent.Charged, ({ state, event }) =>
|
|
64
|
-
CheckoutState.Confirmed.with(state, { receiptId: event.receiptId }),
|
|
53
|
+
.on(DownloadState.Idle, DownloadEvent.Start, ({ event }) =>
|
|
54
|
+
DownloadState.Downloading({ url: event.url }),
|
|
65
55
|
)
|
|
66
|
-
.on(
|
|
67
|
-
|
|
56
|
+
.on(DownloadState.Downloading, DownloadEvent.Completed, ({ state, event }) =>
|
|
57
|
+
DownloadState.Done.with(state, { bytes: event.bytes }),
|
|
68
58
|
)
|
|
69
|
-
.
|
|
70
|
-
|
|
71
|
-
)
|
|
72
|
-
.task(
|
|
73
|
-
CheckoutState.ChargingCard,
|
|
74
|
-
({ state }) =>
|
|
75
|
-
Effect.flatMap(PaymentService, (payment) =>
|
|
76
|
-
payment.chargeCard(state.cartId, state.totalCents),
|
|
77
|
-
),
|
|
78
|
-
{
|
|
79
|
-
onSuccess: ({ receiptId }) => CheckoutEvent.Charged({ receiptId }),
|
|
80
|
-
onFailure: (cause) => CheckoutEvent.Declined({ reason: Cause.pretty(cause) }),
|
|
81
|
-
},
|
|
59
|
+
.on(DownloadState.Downloading, DownloadEvent.Failed, ({ state, event }) =>
|
|
60
|
+
DownloadState.Failed.with(state, { message: event.message }),
|
|
82
61
|
)
|
|
83
|
-
.final(
|
|
84
|
-
.final(
|
|
62
|
+
.final(DownloadState.Done, ({ state }) => state.bytes)
|
|
63
|
+
.final(DownloadState.Failed, () => 0);
|
|
64
|
+
|
|
65
|
+
const program = Effect.scoped(
|
|
66
|
+
Machine.scoped(
|
|
67
|
+
Effect.gen(function* () {
|
|
68
|
+
const actor = yield* Machine.spawn(downloadMachine);
|
|
69
|
+
yield* actor.start;
|
|
70
|
+
yield* actor.send(DownloadEvent.Start({ url: "/report.pdf" }));
|
|
71
|
+
yield* actor.send(DownloadEvent.Completed({ bytes: 1024 }));
|
|
72
|
+
return yield* actor.awaitOutput;
|
|
73
|
+
}),
|
|
74
|
+
),
|
|
75
|
+
);
|
|
85
76
|
```
|
|
86
77
|
|
|
87
|
-
A
|
|
78
|
+
An empty variant is a value such as `DownloadState.Idle`. A non-empty variant is a constructor such as `DownloadState.Downloading({ url })`.
|
|
79
|
+
|
|
80
|
+
`State.with(source, fields)` copies matching fields into the target variant. It prevents manual context spreading across different states.
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
- `State.with(source, overrides)` carries overlapping fields forward without manual copying.
|
|
91
|
-
- `.onAny(...)` is a fallback; a specific `.on(...)` wins.
|
|
92
|
-
- `.spawn(...)` runs work on state entry and cancels it on state exit.
|
|
82
|
+
## Effect is the composition layer
|
|
93
83
|
|
|
94
|
-
|
|
84
|
+
Effect Machine does not add an action queue or a second context system.
|
|
95
85
|
|
|
96
|
-
|
|
86
|
+
| Work | API |
|
|
87
|
+
| ------------------------------------- | -------------------------------------------- |
|
|
88
|
+
| Unconditional state change | `.on`, `.reenter`, or `.immediate` |
|
|
89
|
+
| Conditional state change | `.when`, `.reenterWhen`, or `.immediateWhen` |
|
|
90
|
+
| Work that produces a completion event | `.task` |
|
|
91
|
+
| State-owned stream or resource | `.spawn` |
|
|
92
|
+
| Actor-owned stream or resource | `.background` |
|
|
93
|
+
| Autonomous machine sequence | `Machine.run` with `Effect.flatMap` |
|
|
94
|
+
| Interactive multi-phase flow | Parent machine with child actors |
|
|
97
95
|
|
|
98
|
-
|
|
96
|
+
Effect requirements remain in `R`. A machine cannot start until the application provides every required service. Effectful transition handlers must have `never` in their error channel. Convert expected failures to states or events.
|
|
97
|
+
|
|
98
|
+
Read [the Effect model](./docs/effect-model.md) and [async work ownership](./docs/async-work.md).
|
|
99
|
+
|
|
100
|
+
## Guards and stable state
|
|
101
|
+
|
|
102
|
+
Register ordered candidates for one state and event. The first passing guard wins. An unguarded candidate is the fallback.
|
|
99
103
|
|
|
100
104
|
```ts
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
machine
|
|
106
|
+
.when(
|
|
107
|
+
State.Checking,
|
|
108
|
+
Event.Continue,
|
|
109
|
+
function hasStock({ state }) {
|
|
110
|
+
return state.stock > 0;
|
|
111
|
+
},
|
|
112
|
+
() => State.Accepted,
|
|
113
|
+
)
|
|
114
|
+
.on(State.Checking, Event.Continue, () => State.Rejected)
|
|
115
|
+
.immediate(State.Accepted, ({ state }) => State.Ready.with(state));
|
|
109
116
|
```
|
|
110
117
|
|
|
111
|
-
`
|
|
112
|
-
|
|
113
|
-
Transition handlers in `.on()` and `.reenter()` stay pure. Use services only in `.task()`, `.spawn()`, and `.background()`.
|
|
118
|
+
The predicate can return a Boolean or `Effect<boolean, never, R>`. Its requirements flow into the machine type. `actor.can(event)` evaluates the same predicate with the actor's captured context. `ActorAtom.can(actor, event)` exposes the result to React and Solid. The inspector uses the predicate function name.
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
Immediate transitions run until the state is stable. Subscribers see only the stable state. The runtime stops an accidental eventless loop after 100 edges.
|
|
116
121
|
|
|
117
|
-
|
|
122
|
+
## Effect services and tasks
|
|
118
123
|
|
|
119
124
|
```ts
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
class Api extends Context.Service<
|
|
126
|
+
Api,
|
|
127
|
+
{ readonly load: (id: string) => Effect.Effect<Data, ApiError> }
|
|
128
|
+
>()("app/Api") {}
|
|
129
|
+
|
|
130
|
+
machine.task(State.Loading, ({ state }) => Effect.flatMap(Api, (api) => api.load(state.id)), {
|
|
131
|
+
name: "load-data",
|
|
132
|
+
onSuccess: (data) => Event.Loaded({ data }),
|
|
133
|
+
onFailure: (error) => Event.LoadFailed({ message: String(error) }),
|
|
126
134
|
});
|
|
127
135
|
|
|
128
|
-
Effect.
|
|
129
|
-
|
|
130
|
-
Effect.provideService(PaymentService, {
|
|
131
|
-
chargeCard: (cartId) => Effect.succeed({ receiptId: `rcpt_${cartId}` }),
|
|
132
|
-
}),
|
|
133
|
-
),
|
|
134
|
-
);
|
|
136
|
+
const actor = yield * Machine.spawn(machine).pipe(Effect.provide(ApiLive));
|
|
137
|
+
yield * actor.start;
|
|
135
138
|
```
|
|
136
139
|
|
|
137
|
-
|
|
140
|
+
The actor captures the Effect context during allocation. It keeps those services when it starts later.
|
|
138
141
|
|
|
139
|
-
|
|
140
|
-
- `send(event)` queues and returns immediately
|
|
141
|
-
- `call(event)` returns full transition info
|
|
142
|
-
- `ask(event)` returns a typed domain reply (requires `Event.reply(...)`)
|
|
143
|
-
- `waitFor(...)` / `awaitFinal` for coordination
|
|
144
|
-
- `stop` interrupts now; `drain` processes the remaining queue first
|
|
145
|
-
- `awaitExit` completes when the actor stops
|
|
142
|
+
`onFailure` receives the typed Effect error. A defect does not enter `onFailure`. It stops the actor or starts supervision.
|
|
146
143
|
|
|
147
|
-
|
|
144
|
+
## Input, output, and composition
|
|
148
145
|
|
|
149
146
|
```ts
|
|
150
|
-
|
|
147
|
+
const checkoutMachine = Machine.make({
|
|
148
|
+
state: CheckoutState,
|
|
149
|
+
event: CheckoutEvent,
|
|
150
|
+
initial: (input: CheckoutInput) => CheckoutState.Reviewing(input),
|
|
151
|
+
}).final(CheckoutState.Done, ({ state }) => ({ receiptId: state.receiptId }));
|
|
151
152
|
|
|
152
|
-
const program =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
yield* actor.send(CheckoutEvent.Submit);
|
|
156
|
-
}).pipe(Effect.provide(ActorSystemDefault));
|
|
153
|
+
const program = Machine.run(cartMachine).pipe(
|
|
154
|
+
Effect.flatMap((cart) => Machine.run(checkoutMachine, { input: cart })),
|
|
155
|
+
);
|
|
157
156
|
```
|
|
158
157
|
|
|
159
|
-
|
|
158
|
+
`Machine.run` starts one actor, waits for output, and always stops it. Interruption releases actor resources. The final actor state remains available when you use `Machine.spawn` and retain the actor reference.
|
|
160
159
|
|
|
161
|
-
|
|
160
|
+
Use a parent machine when a UI must route between phases, keep shared values, or support back navigation. Read [Actors and systems](./docs/actors.md).
|
|
162
161
|
|
|
163
|
-
|
|
164
|
-
const CartEvent = Event({
|
|
165
|
-
GetTotal: Event.reply({}, Schema.Number),
|
|
166
|
-
});
|
|
162
|
+
## ActorRef
|
|
167
163
|
|
|
168
|
-
|
|
164
|
+
| Member | Use |
|
|
165
|
+
| --------------------------- | -------------------------------------------------- |
|
|
166
|
+
| `start` | Start a direct actor |
|
|
167
|
+
| `send(event)` | Queue an event |
|
|
168
|
+
| `call(event)` | Process an event and return transition information |
|
|
169
|
+
| `ask(event)` | Return a typed reply from an `Event.reply` event |
|
|
170
|
+
| `waitFor(state)` | Wait for a state constructor or predicate |
|
|
171
|
+
| `sendAndWait(event, state)` | Send and wait for a state |
|
|
172
|
+
| `snapshot` | Read the current state as an Effect |
|
|
173
|
+
| `awaitFinal` | Wait for the final state |
|
|
174
|
+
| `awaitOutput` | Wait for typed output |
|
|
175
|
+
| `awaitExit` | Wait for `Final`, `Stopped`, or `Defect` |
|
|
176
|
+
| `drain` | Process queued events and stop |
|
|
177
|
+
| `subscribe` | Observe state with a host callback |
|
|
178
|
+
| `client` | Use the actor outside Effect |
|
|
179
|
+
| `system` | Access named actors |
|
|
180
|
+
| `children` | Read direct child actors |
|
|
169
181
|
|
|
170
|
-
|
|
171
|
-
```
|
|
182
|
+
`Machine.spawn` returns an unstarted actor. `system.spawn` starts the actor.
|
|
172
183
|
|
|
173
|
-
|
|
184
|
+
Use `actor.client` in a JavaScript callback or application that does not run inside Effect. `client.can(event)` returns a Promise and supports Effect predicates. `client.canSync(event)` supports Boolean predicates only. React and Solid should use Actor Atoms.
|
|
174
185
|
|
|
175
|
-
|
|
186
|
+
## Atom, React, and Solid
|
|
176
187
|
|
|
177
188
|
```ts
|
|
178
|
-
import
|
|
179
|
-
|
|
180
|
-
const result =
|
|
181
|
-
yield *
|
|
182
|
-
simulate(checkoutMachine, [
|
|
183
|
-
CheckoutEvent.Submit,
|
|
184
|
-
CheckoutEvent.Charged({ receiptId: "rcpt_123" }),
|
|
185
|
-
]);
|
|
189
|
+
import * as ActorAtom from "effect-machine/atom";
|
|
186
190
|
|
|
187
|
-
|
|
191
|
+
const stateAtom = ActorAtom.make(actor);
|
|
192
|
+
const countAtom = ActorAtom.select(stateAtom, (state) => state.count);
|
|
188
193
|
```
|
|
189
194
|
|
|
190
|
-
|
|
195
|
+
The selected Atom stays writable. Writes send machine events. A selector publishes only when its selected value changes.
|
|
191
196
|
|
|
192
|
-
|
|
197
|
+
The React example uses `useAtomSuspense` and Motion. The Solid example uses `useAtomResource`, Suspense, and `solid-transition-group`. Both include performance tests. Both retain exit-animation data in the terminal machine state.
|
|
193
198
|
|
|
194
|
-
|
|
199
|
+
Read [Atom and UI integration](./docs/atom-and-ui.md) and browse [all examples](./examples/README.md).
|
|
195
200
|
|
|
196
|
-
|
|
197
|
-
import { useAtomSet, useAtomValue } from "@effect/atom-react";
|
|
198
|
-
import * as ActorAtom from "effect-machine/atom";
|
|
201
|
+
## Persistence, supervision, and inspection
|
|
199
202
|
|
|
200
|
-
|
|
203
|
+
- Recovery resolves state during actor startup.
|
|
204
|
+
- Durability saves committed transitions.
|
|
205
|
+
- Supervision restarts defects within an Effect `Schedule` budget.
|
|
206
|
+
- Inspection reports events, named transition operations, transitions, named guards, tasks, Effects, errors, stops, and actor generations.
|
|
201
207
|
|
|
202
|
-
|
|
203
|
-
const totalCents = useAtomValue(checkoutAtom, (state) =>
|
|
204
|
-
"totalCents" in state ? state.totalCents : 0,
|
|
205
|
-
);
|
|
206
|
-
const send = useAtomSet(checkoutAtom);
|
|
208
|
+
Read [Persistence and supervision](./docs/persistence-and-supervision.md) and [Inspection](./docs/inspection.md).
|
|
207
209
|
|
|
208
|
-
|
|
209
|
-
}
|
|
210
|
-
```
|
|
210
|
+
## Testing
|
|
211
211
|
|
|
212
|
-
`
|
|
212
|
+
Use `simulate` or `createTestHarness` for transition paths. Spawn a real actor for tasks, services, resources, persistence, supervision, inspection, and actor topology.
|
|
213
213
|
|
|
214
214
|
```ts
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
(value, next) => value.cents === next.cents,
|
|
219
|
-
);
|
|
215
|
+
const result = yield * simulate(machine, events, { input });
|
|
216
|
+
yield * assertPath(machine, events, ["Idle", "Loading", "Done"]);
|
|
217
|
+
yield * assertNeverReaches(machine, events, "Failed");
|
|
220
218
|
```
|
|
221
219
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Runnable React and Solid package examples are in `examples/react` and `examples/solid`.
|
|
220
|
+
Read [Testing](./docs/testing.md).
|
|
225
221
|
|
|
226
|
-
|
|
227
|
-
bun run example:react
|
|
228
|
-
bun run example:solid
|
|
229
|
-
```
|
|
222
|
+
## XState migration
|
|
230
223
|
|
|
231
|
-
|
|
224
|
+
The [migration guide](./docs/xstate-migration.md) covers context, assign, actions, invoked promise and callback actors, root routers, actor registries, selectors, inspection, persistence, and exit animation values. Its patterns come from a large XState kiosk application.
|
|
232
225
|
|
|
233
|
-
|
|
226
|
+
## Cluster entities
|
|
234
227
|
|
|
235
|
-
|
|
228
|
+
Use `effect-machine/cluster` to expose a machine through Effect Cluster. It supports typed send, ask, state reads, state watches, input adapters, snapshot persistence, and journal persistence.
|
|
236
229
|
|
|
237
|
-
|
|
230
|
+
Read [Cluster entities](./docs/cluster.md).
|
|
238
231
|
|
|
239
|
-
##
|
|
232
|
+
## Examples
|
|
240
233
|
|
|
241
|
-
|
|
234
|
+
The examples directory is a Bun workspace.
|
|
242
235
|
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const CheckoutEntityLayer = EntityMachine.layer(CheckoutEntity, checkoutMachine, {
|
|
249
|
-
initializeState: (entityId) => CheckoutState.ReviewingCart({ cartId: entityId, totalCents: 0 }),
|
|
250
|
-
persistence: { strategy: "journal" },
|
|
251
|
-
});
|
|
236
|
+
```bash
|
|
237
|
+
bun run examples:gate
|
|
238
|
+
bun run example:react
|
|
239
|
+
bun run example:solid
|
|
252
240
|
```
|
|
253
241
|
|
|
254
|
-
|
|
242
|
+
The [example matrix](./examples/README.md) links every pattern to executable code.
|
|
243
|
+
|
|
244
|
+
## Documentation
|
|
255
245
|
|
|
256
|
-
-
|
|
257
|
-
-
|
|
246
|
+
- [Effect model](./docs/effect-model.md)
|
|
247
|
+
- [Async work](./docs/async-work.md)
|
|
248
|
+
- [Actors and systems](./docs/actors.md)
|
|
249
|
+
- [Atom and UI integration](./docs/atom-and-ui.md)
|
|
250
|
+
- [Persistence and supervision](./docs/persistence-and-supervision.md)
|
|
251
|
+
- [Inspection](./docs/inspection.md)
|
|
252
|
+
- [Testing](./docs/testing.md)
|
|
253
|
+
- [Migration from XState](./docs/xstate-migration.md)
|
|
254
|
+
- [Cluster entities](./docs/cluster.md)
|
|
255
|
+
- [AI agent reference](./SKILL.md)
|
|
258
256
|
|
|
259
257
|
## License
|
|
260
258
|
|
package/dist/actor.d.ts
CHANGED
|
@@ -5,17 +5,31 @@ import { Lifecycle, Machine } from "./machine.js";
|
|
|
5
5
|
import { ProcessEventResult } from "./internal/transition.js";
|
|
6
6
|
import { Context, Effect, Layer, Option, Scope, Stream, SubscriptionRef } from "effect";
|
|
7
7
|
//#region src/actor.d.ts
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
/** JavaScript client for code that does not run inside Effect. */
|
|
9
|
+
interface ActorClient<State extends {
|
|
10
|
+
readonly _tag: string;
|
|
11
|
+
}, Event, Output = State> {
|
|
12
|
+
readonly send: (event: Event) => void;
|
|
13
|
+
readonly stop: () => void;
|
|
14
|
+
readonly getSnapshot: () => State;
|
|
15
|
+
readonly matches: (tag: State["_tag"]) => boolean;
|
|
16
|
+
readonly canSync: (event: Event) => boolean;
|
|
17
|
+
readonly can: (event: Event) => Promise<boolean>;
|
|
18
|
+
readonly getLifecycle: () => ActorLifecycle<State, Output>;
|
|
19
|
+
readonly getLatestTransition: () => TransitionInfo<State, Event> | undefined;
|
|
20
|
+
readonly subscribe: (listener: (state: State) => void) => () => void;
|
|
21
|
+
}
|
|
22
|
+
/** @deprecated Use `ActorClient`. */
|
|
11
23
|
interface ActorRefSync<State extends {
|
|
12
24
|
readonly _tag: string;
|
|
13
|
-
}, Event> {
|
|
25
|
+
}, Event, Output = State> {
|
|
14
26
|
readonly send: (event: Event) => void;
|
|
15
27
|
readonly stop: () => void;
|
|
16
28
|
readonly snapshot: () => State;
|
|
17
29
|
readonly matches: (tag: State["_tag"]) => boolean;
|
|
18
30
|
readonly can: (event: Event) => boolean;
|
|
31
|
+
readonly lifecycle: () => ActorLifecycle<State, Output>;
|
|
32
|
+
readonly latestTransition: () => TransitionInfo<State, Event> | undefined;
|
|
19
33
|
}
|
|
20
34
|
/**
|
|
21
35
|
* Information about a successful transition.
|
|
@@ -26,9 +40,19 @@ interface TransitionInfo<State, Event> {
|
|
|
26
40
|
readonly toState: State;
|
|
27
41
|
readonly event: Event;
|
|
28
42
|
}
|
|
43
|
+
/** Observable actor lifecycle. Domain state remains available through `actor.state`. */
|
|
44
|
+
type ActorLifecycle<State, Output = State> = {
|
|
45
|
+
readonly _tag: "Created";
|
|
46
|
+
} | {
|
|
47
|
+
readonly _tag: "Starting";
|
|
48
|
+
readonly generation: number;
|
|
49
|
+
} | {
|
|
50
|
+
readonly _tag: "Active";
|
|
51
|
+
readonly generation: number;
|
|
52
|
+
} | ActorExit<State, Output>;
|
|
29
53
|
interface ActorRef<State extends {
|
|
30
54
|
readonly _tag: string;
|
|
31
|
-
}, Event> {
|
|
55
|
+
}, Event, Output = State> {
|
|
32
56
|
readonly id: string;
|
|
33
57
|
/** Send an event (fire-and-forget). */
|
|
34
58
|
readonly send: (event: Event) => Effect.Effect<void>;
|
|
@@ -36,7 +60,7 @@ interface ActorRef<State extends {
|
|
|
36
60
|
* Serialized request-reply (OTP gen_server:call).
|
|
37
61
|
* Event is processed through the queue; caller gets ProcessEventResult back.
|
|
38
62
|
*/
|
|
39
|
-
readonly call: (event: Event) => Effect.Effect<ProcessEventResult<State>>;
|
|
63
|
+
readonly call: (event: Event) => Effect.Effect<ProcessEventResult<State, Event>>;
|
|
40
64
|
/**
|
|
41
65
|
* Typed request-reply. Accepts only events with a reply schema
|
|
42
66
|
* (defined via `Event.reply()`). Return type is inferred from the schema.
|
|
@@ -45,6 +69,10 @@ interface ActorRef<State extends {
|
|
|
45
69
|
readonly ask: <E extends Event & ReplyTypeBrand<unknown>>(event: E) => Effect.Effect<ExtractReply<E>, NoReplyError | ActorStoppedError>;
|
|
46
70
|
/** Observable state. */
|
|
47
71
|
readonly state: SubscriptionRef.SubscriptionRef<State>;
|
|
72
|
+
/** Observable actor lifecycle. */
|
|
73
|
+
readonly lifecycle: SubscriptionRef.SubscriptionRef<ActorLifecycle<State, Output>>;
|
|
74
|
+
/** The latest accepted edge. This value remains available after actor exit. */
|
|
75
|
+
readonly latestTransition: SubscriptionRef.SubscriptionRef<TransitionInfo<State, Event> | undefined>;
|
|
48
76
|
/** Stop the actor gracefully. */
|
|
49
77
|
readonly stop: Effect.Effect<void>;
|
|
50
78
|
/**
|
|
@@ -60,7 +88,7 @@ interface ActorRef<State extends {
|
|
|
60
88
|
readonly snapshot: Effect.Effect<State>;
|
|
61
89
|
/** Check if current state matches tag. */
|
|
62
90
|
readonly matches: (tag: State["_tag"]) => Effect.Effect<boolean>;
|
|
63
|
-
/** Check if event
|
|
91
|
+
/** Check if an event has an enabled transition. Supports Boolean and Effect predicates. */
|
|
64
92
|
readonly can: (event: Event) => Effect.Effect<boolean>;
|
|
65
93
|
/** Stream of state changes. */
|
|
66
94
|
readonly changes: Stream.Stream<State>;
|
|
@@ -81,6 +109,8 @@ interface ActorRef<State extends {
|
|
|
81
109
|
};
|
|
82
110
|
/** Wait for a final state (includes current snapshot). */
|
|
83
111
|
readonly awaitFinal: Effect.Effect<State>;
|
|
112
|
+
/** Wait for the domain output of a final state. */
|
|
113
|
+
readonly awaitOutput: Effect.Effect<Output, ActorStoppedError>;
|
|
84
114
|
/** Send event and wait for predicate, state variant, or final state. */
|
|
85
115
|
readonly sendAndWait: {
|
|
86
116
|
(event: Event, predicate: (state: State) => boolean): Effect.Effect<State>;
|
|
@@ -91,18 +121,20 @@ interface ActorRef<State extends {
|
|
|
91
121
|
};
|
|
92
122
|
/** Subscribe to state changes (sync callback). Returns unsubscribe function. */
|
|
93
123
|
readonly subscribe: (fn: (state: State) => void) => () => void;
|
|
124
|
+
/** JavaScript client for callbacks and applications outside Effect. */
|
|
125
|
+
readonly client: ActorClient<State, Event, Output>;
|
|
94
126
|
/**
|
|
95
127
|
* Wait for this actor's terminal exit. Resolves with the exit reason.
|
|
96
128
|
* Set exactly once when the actor terminates (final, stop, drain, or defect).
|
|
97
129
|
*/
|
|
98
|
-
readonly awaitExit: Effect.Effect<ActorExit<State>>;
|
|
130
|
+
readonly awaitExit: Effect.Effect<ActorExit<State, Output>>;
|
|
99
131
|
/**
|
|
100
132
|
* Drain: process all remaining events in the queue, then stop.
|
|
101
133
|
* Unlike `stop` (which interrupts immediately), `drain` lets the actor finish its work.
|
|
102
134
|
*/
|
|
103
135
|
readonly drain: Effect.Effect<void>;
|
|
104
|
-
/**
|
|
105
|
-
readonly sync: ActorRefSync<State, Event>;
|
|
136
|
+
/** @deprecated Use `client`. */
|
|
137
|
+
readonly sync: ActorRefSync<State, Event, Output>;
|
|
106
138
|
/** The actor system this actor belongs to. */
|
|
107
139
|
readonly system: ActorSystemService;
|
|
108
140
|
/** Child actors spawned via `self.spawn` in this actor's handlers. */
|
|
@@ -147,14 +179,14 @@ interface ActorSystemService {
|
|
|
147
179
|
* const actor = yield* system.spawn("my-actor", machine);
|
|
148
180
|
* ```
|
|
149
181
|
*/
|
|
150
|
-
readonly spawn:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
182
|
+
readonly spawn: {
|
|
183
|
+
<S extends AnyState, E extends {
|
|
184
|
+
readonly _tag: string;
|
|
185
|
+
}, R, Output>(id: string, machine: Machine<S, E, R, any, any, void, Output>, options?: SystemSpawnOptions<S, E, void>): Effect.Effect<ActorRef<S, E, Output>, DuplicateActorError, R>;
|
|
186
|
+
<S extends AnyState, E extends {
|
|
187
|
+
readonly _tag: string;
|
|
188
|
+
}, R, Input, Output>(id: string, machine: Machine<S, E, R, any, any, Input, Output>, options: SystemSpawnOptions<S, E, Input>): Effect.Effect<ActorRef<S, E, Output>, DuplicateActorError, R>;
|
|
189
|
+
};
|
|
158
190
|
/**
|
|
159
191
|
* Get an existing actor by ID
|
|
160
192
|
*/
|
|
@@ -179,6 +211,15 @@ interface ActorSystemService {
|
|
|
179
211
|
*/
|
|
180
212
|
readonly subscribe: (fn: SystemEventListener) => () => void;
|
|
181
213
|
}
|
|
214
|
+
type SystemSpawnOptions<S, E, Input> = {
|
|
215
|
+
readonly supervision?: Supervision.Policy;
|
|
216
|
+
readonly lifecycle?: Lifecycle<S, E>;
|
|
217
|
+
readonly hydrate?: S;
|
|
218
|
+
} & ([Input] extends [void] ? {
|
|
219
|
+
readonly input?: never;
|
|
220
|
+
} : {
|
|
221
|
+
readonly input: Input;
|
|
222
|
+
});
|
|
182
223
|
declare const ActorSystem_base: Context.ServiceClass<ActorSystem, "effect-machine/actor/ActorSystem", ActorSystemService>;
|
|
183
224
|
/**
|
|
184
225
|
* ActorSystem service tag
|
|
@@ -203,16 +244,18 @@ declare const createActor: <S extends {
|
|
|
203
244
|
readonly _tag: string;
|
|
204
245
|
}, E extends {
|
|
205
246
|
readonly _tag: string;
|
|
206
|
-
}, R>(id: string, machine: Machine<S, E, R, any, any>, options
|
|
207
|
-
initialState
|
|
247
|
+
}, R, O>(id: string, machine: Machine<S, E, R, any, any, any, O>, options: {
|
|
248
|
+
initialState: S;
|
|
249
|
+
machineInitial: S;
|
|
250
|
+
hydrated?: boolean;
|
|
208
251
|
supervision?: Supervision.Policy;
|
|
209
252
|
lifecycle?: Lifecycle<S, E>;
|
|
210
253
|
/** @internal Called by system after each restart — emits ActorRestarted system event */
|
|
211
254
|
onRestart?: (generation: number, exit: ActorExit<unknown>) => Effect.Effect<void>;
|
|
212
|
-
}
|
|
255
|
+
}) => Effect.Effect<ActorRef<S, E, O>, never, R>;
|
|
213
256
|
/**
|
|
214
257
|
* Default ActorSystem layer
|
|
215
258
|
*/
|
|
216
259
|
declare const Default: Layer.Layer<ActorSystem, never, never>;
|
|
217
260
|
//#endregion
|
|
218
|
-
export { ActorRef, ActorRefSync, ActorScope, ActorSystem, ActorSystemService, Default, type ProcessEventResult, SystemEvent, SystemEventListener, TransitionInfo, createActor };
|
|
261
|
+
export { ActorClient, ActorLifecycle, ActorRef, ActorRefSync, ActorScope, ActorSystem, ActorSystemService, Default, type ProcessEventResult, SystemEvent, SystemEventListener, SystemSpawnOptions, TransitionInfo, createActor };
|