@typeonce/effect-machine 0.8.0 → 0.10.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 +180 -40
- package/dist/Machine.d.ts +659 -481
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +86 -222
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +5 -12
- package/dist/internal/machine/activities.d.ts.map +1 -1
- package/dist/internal/machine/activities.js +47 -17
- package/dist/internal/machine/activities.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +6 -2
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +5 -0
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/cluster.d.ts.map +1 -1
- package/dist/internal/machine/cluster.js +2 -1
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/command.js +2 -2
- package/dist/internal/machine/command.js.map +1 -1
- package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
- package/dist/internal/machine/commandRuntime.js +6 -3
- package/dist/internal/machine/commandRuntime.js.map +1 -1
- package/dist/internal/machine/configuration.d.ts +8 -1
- package/dist/internal/machine/configuration.d.ts.map +1 -1
- package/dist/internal/machine/configuration.js +9 -4
- package/dist/internal/machine/configuration.js.map +1 -1
- package/dist/internal/machine/errors.d.ts +1 -1
- package/dist/internal/machine/errors.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts +3 -3
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +74 -24
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts +10 -2
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +94 -34
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationEvent.d.ts +39 -0
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -0
- package/dist/internal/machine/invocationEvent.js +43 -0
- package/dist/internal/machine/invocationEvent.js.map +1 -0
- package/dist/internal/machine/machine.d.ts +16 -65
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +18 -73
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +4 -2
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +104 -24
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/process.d.ts.map +1 -1
- package/dist/internal/machine/process.js +8 -8
- package/dist/internal/machine/process.js.map +1 -1
- package/dist/internal/machine/protocol.d.ts +8 -5
- package/dist/internal/machine/protocol.d.ts.map +1 -1
- package/dist/internal/machine/protocol.js +207 -15
- package/dist/internal/machine/protocol.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +8 -2
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +86 -8
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/symbols.d.ts +2 -0
- package/dist/internal/machine/symbols.d.ts.map +1 -1
- package/dist/internal/machine/symbols.js +2 -0
- package/dist/internal/machine/symbols.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +7 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +29 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +1 -1
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/runtime.d.ts.map +1 -1
- package/dist/internal/testing/machine/runtime.js +2 -1
- package/dist/internal/testing/machine/runtime.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +5 -3
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +7 -7
- package/dist/testing/MachineTest.js +7 -7
- package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +25 -8
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +18 -3
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +242 -122
- package/package.json +2 -2
- package/src/Machine.ts +1453 -812
- package/src/internal/machine/activities.ts +48 -33
- package/src/internal/machine/atom.ts +38 -13
- package/src/internal/machine/cluster.ts +2 -1
- package/src/internal/machine/command.ts +2 -2
- package/src/internal/machine/commandRuntime.ts +9 -4
- package/src/internal/machine/configuration.ts +23 -5
- package/src/internal/machine/errors.ts +1 -1
- package/src/internal/machine/executionPlan.ts +102 -28
- package/src/internal/machine/invocation.ts +182 -50
- package/src/internal/machine/invocationEvent.ts +72 -0
- package/src/internal/machine/machine.ts +80 -387
- package/src/internal/machine/planner.ts +127 -32
- package/src/internal/machine/process.ts +18 -8
- package/src/internal/machine/protocol.ts +313 -25
- package/src/internal/machine/runtime.ts +135 -9
- package/src/internal/machine/symbols.ts +3 -0
- package/src/internal/machine/topology.ts +38 -0
- package/src/internal/testing/machine/finiteModel.ts +1 -1
- package/src/internal/testing/machine/runtime.ts +2 -1
- package/src/internal/testing/machine/verification.ts +5 -3
- package/src/testing/MachineTest.ts +7 -7
- package/src/unstable/cluster/ClusterMachine.ts +1 -1
- package/src/unstable/reactivity/AtomMachine.ts +45 -13
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Define schemas first, derive the state topology, then add behavior:
|
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
27
|
import { Machine } from "@typeonce/effect-machine"
|
|
28
|
-
import { Effect, Schema } from "effect"
|
|
28
|
+
import { Effect, Schema, Stream } from "effect"
|
|
29
29
|
|
|
30
30
|
const State = Schema.TaggedUnion({
|
|
31
31
|
Idle: {},
|
|
@@ -39,13 +39,16 @@ const Event = Schema.TaggedUnion({
|
|
|
39
39
|
})
|
|
40
40
|
|
|
41
41
|
const States = Machine.defineStates(State.cases)
|
|
42
|
+
const CounterEvent = Machine.events(Event)
|
|
42
43
|
|
|
43
|
-
const
|
|
44
|
+
const CounterDefinition = Machine.make({
|
|
44
45
|
id: "Counter",
|
|
45
46
|
states: States.states,
|
|
46
|
-
events:
|
|
47
|
+
events: CounterEvent,
|
|
47
48
|
initial: () => States.initial.Idle.from()
|
|
48
|
-
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
const Counter = CounterDefinition.handle({
|
|
49
52
|
Idle: {
|
|
50
53
|
on: {
|
|
51
54
|
Start: ({ target }) => target.full.Running.from({ count: 0 })
|
|
@@ -61,14 +64,14 @@ const Counter = Machine.make({
|
|
|
61
64
|
|
|
62
65
|
const program = Effect.gen(function*() {
|
|
63
66
|
const ref = yield* Machine.start(Counter)
|
|
64
|
-
yield* ref.send(
|
|
65
|
-
yield* ref.send(
|
|
67
|
+
yield* ref.send(CounterEvent.Start())
|
|
68
|
+
yield* ref.send(CounterEvent.Increment())
|
|
66
69
|
})
|
|
67
70
|
```
|
|
68
71
|
|
|
69
72
|
`Machine.start` returns a `MachineRef` with `send`, `state`, `snapshot`,
|
|
70
|
-
`changes`, `join`, and `stop`. Sending enqueues an event; observe
|
|
71
|
-
use the testing probe when work must be causally acknowledged.
|
|
73
|
+
`changes`, `emissions`, `join`, and `stop`. Sending enqueues an event; observe
|
|
74
|
+
`changes` or use the testing probe when work must be causally acknowledged.
|
|
72
75
|
|
|
73
76
|
## Modeling workflow
|
|
74
77
|
|
|
@@ -76,9 +79,11 @@ Use this order to preserve inference and keep boundaries explicit:
|
|
|
76
79
|
|
|
77
80
|
1. Define domain, state, public-event, internal-event, and emitted-event schemas.
|
|
78
81
|
2. Declare topology with `Machine.defineStates`.
|
|
79
|
-
3. Create
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
3. Create public and internal event descriptors with `Machine.events` and
|
|
83
|
+
`Machine.internalEvents`.
|
|
84
|
+
4. Create the machine protocol and initializer with `Machine.make`.
|
|
85
|
+
5. Implement every active state with `.handle(...)`.
|
|
86
|
+
6. Add runtime, Atom, testing, or cluster adapters at the application boundary.
|
|
82
87
|
|
|
83
88
|
### Construct state through builders
|
|
84
89
|
|
|
@@ -92,7 +97,18 @@ States.initial.Form.from({ draft: "" }, (form) => form.Editing.from())
|
|
|
92
97
|
The machine runs these inputs through the state schema while planning. Schema
|
|
93
98
|
defaults, refinements, and tagged-class identity are therefore preserved, and
|
|
94
99
|
decode failures remain typed machine failures. Pass a value directly only when
|
|
95
|
-
it is already decoded
|
|
100
|
+
it is already decoded.
|
|
101
|
+
|
|
102
|
+
When sibling states share fields, remove the source discriminator and pass the
|
|
103
|
+
remaining fields through the target schema:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
Submit: ;
|
|
107
|
+
;(({ state, target }) => {
|
|
108
|
+
const { _tag: _, ...fields } = state
|
|
109
|
+
return target.local.Saving.from({ ...fields, attempt: 1 })
|
|
110
|
+
})
|
|
111
|
+
```
|
|
96
112
|
|
|
97
113
|
Omit `schema` when a state represents control flow but owns no data:
|
|
98
114
|
|
|
@@ -118,10 +134,11 @@ schema-backed paths. Add a schema later if the state starts owning data.
|
|
|
118
134
|
Put data on the narrowest state where it is valid. If sibling phases share
|
|
119
135
|
data, put it on their compound parent.
|
|
120
136
|
|
|
121
|
-
### Separate
|
|
137
|
+
### Separate inputs, raised events, and emissions
|
|
122
138
|
|
|
123
|
-
`events` is the public
|
|
124
|
-
|
|
139
|
+
`events` is the public actor-input protocol. Events raised to the same machine
|
|
140
|
+
belong in `internalEvents`. Ephemeral outward notifications have their own
|
|
141
|
+
`emittedEvents` protocol:
|
|
125
142
|
|
|
126
143
|
```ts
|
|
127
144
|
const Command = Schema.TaggedUnion({ Save: {} })
|
|
@@ -129,11 +146,20 @@ const Internal = Schema.TaggedUnion({
|
|
|
129
146
|
Saved: { id: Schema.String },
|
|
130
147
|
SaveFailed: { message: Schema.String }
|
|
131
148
|
})
|
|
149
|
+
const Emitted = Schema.TaggedUnion({
|
|
150
|
+
SaveObserved: { id: Schema.String }
|
|
151
|
+
})
|
|
132
152
|
|
|
133
|
-
const
|
|
153
|
+
export const CommandEvent = Machine.events(Command)
|
|
154
|
+
export type PublicCommandEvent = Machine.EventOf<typeof CommandEvent>
|
|
155
|
+
const InternalEvent = Machine.internalEvents(Internal)
|
|
156
|
+
const Emissions = Machine.emittedEvents(Emitted)
|
|
157
|
+
|
|
158
|
+
const definition = Machine.make({
|
|
134
159
|
states: States.states,
|
|
135
|
-
events:
|
|
136
|
-
internalEvents:
|
|
160
|
+
events: CommandEvent,
|
|
161
|
+
internalEvents: InternalEvent,
|
|
162
|
+
emittedEvents: Emissions,
|
|
137
163
|
initial: () => States.initial.Idle.from()
|
|
138
164
|
})
|
|
139
165
|
```
|
|
@@ -141,22 +167,101 @@ const machine = Machine.make({
|
|
|
141
167
|
Handlers see both protocols. Typed `send` and `Machine.plan` accept only public
|
|
142
168
|
events. Event tags must be unique and public/internal tags must be disjoint.
|
|
143
169
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
170
|
+
Export the descriptor returned by `Machine.events` instead of exporting its
|
|
171
|
+
schemas. This keeps the deferred constructors as the standard way to create
|
|
172
|
+
events without exposing schema `.make` methods:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
ref.send(CommandEvent.Save())
|
|
176
|
+
enqueue.raise(InternalEvent.Saved({ id: "entry-1" }))
|
|
177
|
+
enqueue.emit(Emissions.SaveObserved({ id: "entry-1" }))
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The returned constructors preserve each schema's make input, including required
|
|
181
|
+
fields and constructor defaults. They defer schema construction until delivery,
|
|
182
|
+
so invalid values fail planning or the running machine with
|
|
183
|
+
`MachineSchemaDecodeError` instead of throwing at the call site.
|
|
184
|
+
Schemas with an open discriminator such as `_tag: Schema.String` remain valid
|
|
185
|
+
protocols but cannot expose a finite constructor set; pass a complete event
|
|
186
|
+
object to `send` or `Machine.plan` for those events.
|
|
187
|
+
|
|
188
|
+
`ref.emissions` is a hot `Stream`: it publishes only notifications produced
|
|
189
|
+
after subscription, replays nothing, and completes when the actor terminates.
|
|
190
|
+
Snapshots remain separate and stateful: `ref.changes` begins with the current
|
|
191
|
+
lifecycle snapshot and then follows later changes. Because `Machine.start`
|
|
192
|
+
returns only after initialization, startup emissions are not observable from
|
|
193
|
+
the returned ref; represent startup facts in state when they must be retained.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const next = ref.emissions.pipe(Stream.take(1), Stream.runHead)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Invalid event and emission constructions fail the machine with a typed
|
|
200
|
+
`MachineSchemaDecodeError`; they do not throw from the constructor call.
|
|
201
|
+
|
|
202
|
+
### Send explicitly between actors
|
|
203
|
+
|
|
204
|
+
`raise` targets the current machine in the same macrostep. `sendTo` targets an
|
|
205
|
+
actor mailbox and is processed later. A child declares the subset of parent
|
|
206
|
+
inputs it may send with `parentEvents`:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const ParentEvents = Machine.events(ChildFinished)
|
|
210
|
+
|
|
211
|
+
const child = Machine.make({
|
|
212
|
+
states: ChildStates.states,
|
|
213
|
+
events: ChildEvents,
|
|
214
|
+
parentEvents: ParentEvents,
|
|
215
|
+
initial: () => ChildStates.initial.Working.from()
|
|
216
|
+
}).handle({
|
|
217
|
+
Working: {
|
|
218
|
+
on: {
|
|
219
|
+
Finish: ({ parent, target }, enqueue) => {
|
|
220
|
+
if (parent !== undefined) {
|
|
221
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
222
|
+
}
|
|
223
|
+
return target.full.Done.from()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
Done: {}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
const Child = Machine.child("worker", child)
|
|
231
|
+
const ParentInputs = Machine.events(Start, ParentEvents)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The same child remains isolated and may be started as a root, where `parent` is
|
|
235
|
+
`undefined`. When `Child` is invoked, the parent definition must accept every
|
|
236
|
+
event in `parentEvents`; otherwise `.handle(...)` is a compile-time error.
|
|
237
|
+
Inside the child, the parent reference accepts only those declared events.
|
|
238
|
+
`emit` never sends to the parent: it only publishes on the emitting actor's
|
|
239
|
+
`emissions` stream.
|
|
240
|
+
|
|
241
|
+
Every handler also receives `self`, which can be targeted with `sendTo` when a
|
|
242
|
+
later mailbox turn is required. Use `raise` instead for same-macrostep work.
|
|
243
|
+
Structural state values use distinct names: `containingState` is the immediate
|
|
244
|
+
valued state in the same statechart, while `ancestors` maps valued ancestor
|
|
245
|
+
paths. `parent` always means the owning actor reference.
|
|
147
246
|
|
|
148
247
|
### Choose the target by scope
|
|
149
248
|
|
|
150
249
|
| Builder | Use when | Preserves |
|
|
151
250
|
| ---------------- | ---------------------------------------- | ------------------------------------------------- |
|
|
251
|
+
| `target.none()` | Handling without selecting a destination | The complete current configuration |
|
|
152
252
|
| `target.local` | Moving inside the nearest compound scope | Ancestors and unrelated parallel regions |
|
|
153
253
|
| `target.branch` | Moving elsewhere under the active root | Omitted active ancestors and parallel regions |
|
|
154
254
|
| `target.full` | Replacing or selecting a complete root | Nothing implicit for a newly selected root |
|
|
155
255
|
| `target.history` | Restoring a declared history node | The remembered configuration or its typed default |
|
|
156
256
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
257
|
+
Every installed transition handler returns either a concrete target or
|
|
258
|
+
`target.none()`. An absent handler ignores the trigger; `target.none()` handles
|
|
259
|
+
it and retains queued commands, raised events, and emitted events without
|
|
260
|
+
selecting a destination. Declared `targets` constrain only concrete
|
|
261
|
+
destinations, so `target.none()` is always permitted. Builders describe the
|
|
262
|
+
next logical configuration. Shared states exit and enter only when paths
|
|
263
|
+
change; use `{ reenter: true, transition }` when the source must restart. With
|
|
264
|
+
`target.none()`, reentry restarts the source while retaining its configuration.
|
|
160
265
|
|
|
161
266
|
## Statechart capabilities
|
|
162
267
|
|
|
@@ -185,29 +290,48 @@ State-scoped work starts on entry and is interrupted on exit:
|
|
|
185
290
|
|
|
186
291
|
```ts
|
|
187
292
|
Loading: {
|
|
188
|
-
invoke: Machine.
|
|
293
|
+
invoke: Machine.invoke({
|
|
189
294
|
id: "save-document",
|
|
190
295
|
effect: saveDocument,
|
|
191
|
-
|
|
192
|
-
onFailure: (error) =>
|
|
296
|
+
onDone: ({ output, target }) => target.full.Saved({ id: output.id }),
|
|
297
|
+
onFailure: ({ error, target }) => target.full.Failed({ message: String(error) })
|
|
193
298
|
})
|
|
194
299
|
}
|
|
195
300
|
|
|
196
301
|
Waiting: {
|
|
197
|
-
invoke: Machine.
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
302
|
+
invoke: Machine.invoke({
|
|
303
|
+
id: "save-timeout",
|
|
304
|
+
after: "3 seconds",
|
|
305
|
+
onDone: ({ target }) => target.full.Failed({ message: "Timed out" })
|
|
306
|
+
})
|
|
201
307
|
}
|
|
202
308
|
```
|
|
203
309
|
|
|
204
|
-
Use `
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
310
|
+
Use `effect` for one Effect, `after` for a cancellable delay, `logic` for a
|
|
311
|
+
reusable process, and `child` for a complete child statechart—all through
|
|
312
|
+
`Machine.invoke({...})`. The helper is an identity at runtime and preserves
|
|
313
|
+
owner-context and source-channel inference across lifecycle handlers, including
|
|
314
|
+
for state-dependent Effects:
|
|
208
315
|
|
|
209
|
-
|
|
210
|
-
|
|
316
|
+
```ts
|
|
317
|
+
invoke: Machine.invoke({
|
|
318
|
+
id: "load-document",
|
|
319
|
+
effect: ({ state }) => loadDocument(state.documentId),
|
|
320
|
+
onDone: ({ output, target }) => target.full.Ready({ document: output }),
|
|
321
|
+
onFailure: ({ error, target }) => target.full.Failed({ message: error.message })
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
A direct `invoke: { ... }` object is also supported when its lifecycle handlers
|
|
326
|
+
do not need source-derived context. Reuse one exported
|
|
327
|
+
`Machine.child(id, machine)` descriptor for invocation, `sendTo`, and child
|
|
328
|
+
lookup.
|
|
329
|
+
|
|
330
|
+
`onDone` is required for a non-`never` output, and `onFailure` is required for a
|
|
331
|
+
non-`never` typed error; each handler is omitted when its channel is `never`.
|
|
332
|
+
Defects, interruption, and source-construction failures terminate the owning
|
|
333
|
+
runtime. `effect: Effect.sleep(...)` is valid, but `after` keeps timers explicit
|
|
334
|
+
and makes static durations visible through activity inspection.
|
|
211
335
|
|
|
212
336
|
## Reactivity
|
|
213
337
|
|
|
@@ -230,6 +354,17 @@ The bridge exposes `ref`, `snapshot`, `state`, fail-aware `result`, writable
|
|
|
230
354
|
equality-aware derivations. React applications using `@effect/atom-react` need
|
|
231
355
|
a `RegistryProvider`.
|
|
232
356
|
|
|
357
|
+
Emissions stay streams rather than becoming retained atom state:
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
const rootEmissions = AtomMachine.emissions(counterAtom)
|
|
361
|
+
const childEmissions = AtomMachine.childEmissions(counterAtom.child(Worker))
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
These streams require the same `AtomRegistry`, follow the currently mounted
|
|
365
|
+
actor instance, and do not replay notifications from an earlier subscription
|
|
366
|
+
or child instance.
|
|
367
|
+
|
|
233
368
|
## Persistence
|
|
234
369
|
|
|
235
370
|
Logical snapshots can be validated for storage or transport:
|
|
@@ -260,14 +395,19 @@ The testing entrypoint provides complementary layers:
|
|
|
260
395
|
import { MachineTest } from "@typeonce/effect-machine/testing"
|
|
261
396
|
|
|
262
397
|
const trace = yield* MachineTest.run(Counter, {
|
|
263
|
-
events: [
|
|
398
|
+
events: [
|
|
399
|
+
{ _tag: "Start" },
|
|
400
|
+
{ _tag: "Increment" }
|
|
401
|
+
]
|
|
264
402
|
})
|
|
265
403
|
|
|
266
404
|
yield* MachineTest.verify(Counter, trace)
|
|
267
405
|
```
|
|
268
406
|
|
|
269
|
-
|
|
270
|
-
|
|
407
|
+
`MachineTest` scenarios retain decoded event values for model inspection, so
|
|
408
|
+
pass complete decoded objects when defining scenarios manually. Pure planner
|
|
409
|
+
tests do not execute invokes or time. Use a started machine and a probe when
|
|
410
|
+
those semantics matter.
|
|
271
411
|
|
|
272
412
|
## Entrypoints
|
|
273
413
|
|