@typeonce/effect-machine 0.31.2 → 0.33.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 +168 -162
- package/dist/Machine.d.ts +495 -284
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +91 -111
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +3 -4
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +0 -2
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/childRegistry.d.ts +52 -0
- package/dist/internal/machine/childRegistry.d.ts.map +1 -0
- package/dist/internal/machine/childRegistry.js +74 -0
- package/dist/internal/machine/childRegistry.js.map +1 -0
- package/dist/internal/machine/cluster.d.ts +2 -3
- package/dist/internal/machine/cluster.d.ts.map +1 -1
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/configuration.d.ts.map +1 -1
- package/dist/internal/machine/configuration.js +14 -4
- package/dist/internal/machine/configuration.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts +1 -0
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +97 -53
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +4 -2
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationDefinition.d.ts +35 -0
- package/dist/internal/machine/invocationDefinition.d.ts.map +1 -0
- package/dist/internal/machine/invocationDefinition.js +21 -0
- package/dist/internal/machine/invocationDefinition.js.map +1 -0
- package/dist/internal/machine/invocationEvent.d.ts +0 -2
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
- package/dist/internal/machine/invocationEvent.js +0 -8
- package/dist/internal/machine/invocationEvent.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +21 -43
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +134 -403
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +13 -19
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +63 -35
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/process.d.ts +6 -8
- package/dist/internal/machine/process.d.ts.map +1 -1
- package/dist/internal/machine/process.js +1 -0
- package/dist/internal/machine/process.js.map +1 -1
- package/dist/internal/machine/protocol.d.ts +2 -2
- package/dist/internal/machine/protocol.d.ts.map +1 -1
- package/dist/internal/machine/protocol.js +4 -18
- package/dist/internal/machine/protocol.js.map +1 -1
- package/dist/internal/machine/readiness.d.ts +1 -1
- package/dist/internal/machine/readiness.d.ts.map +1 -1
- package/dist/internal/machine/requirements.d.ts +4 -0
- package/dist/internal/machine/requirements.d.ts.map +1 -0
- package/dist/internal/machine/requirements.js +2 -0
- package/dist/internal/machine/requirements.js.map +1 -0
- package/dist/internal/machine/runtime.d.ts +4 -58
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +1 -69
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/serialization.d.ts.map +1 -1
- package/dist/internal/machine/serialization.js +16 -0
- package/dist/internal/machine/serialization.js.map +1 -1
- package/dist/internal/machine/stateDefinition.d.ts +3 -1
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
- package/dist/internal/machine/stateDefinition.js +36 -2
- package/dist/internal/machine/stateDefinition.js.map +1 -1
- package/dist/internal/machine/targetBuilder.d.ts +16 -0
- package/dist/internal/machine/targetBuilder.d.ts.map +1 -0
- package/dist/internal/machine/targetBuilder.js +333 -0
- package/dist/internal/machine/targetBuilder.js.map +1 -0
- package/dist/internal/machine/topology.js +3 -3
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/machine/transition.d.ts +16 -0
- package/dist/internal/machine/transition.d.ts.map +1 -0
- package/dist/internal/machine/transition.js +2 -0
- package/dist/internal/machine/transition.js.map +1 -0
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +11 -13
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/format.d.ts +7 -0
- package/dist/internal/testing/machine/format.d.ts.map +1 -0
- package/dist/internal/testing/machine/format.js +104 -0
- package/dist/internal/testing/machine/format.js.map +1 -0
- package/dist/internal/testing/machine/probe.d.ts +1 -1
- package/dist/internal/testing/machine/probe.d.ts.map +1 -1
- package/dist/internal/testing/machine/probe.js +1 -1
- package/dist/internal/testing/machine/probe.js.map +1 -1
- package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/referenceModel.js +15 -19
- package/dist/internal/testing/machine/referenceModel.js.map +1 -1
- package/dist/internal/testing/machine/trace.d.ts +1 -1
- package/dist/internal/testing/machine/trace.d.ts.map +1 -1
- package/dist/internal/testing/machine/trace.js +3 -3
- package/dist/internal/testing/machine/trace.js.map +1 -1
- package/dist/internal/testing/machine/value.d.ts +8 -0
- package/dist/internal/testing/machine/value.d.ts.map +1 -0
- package/dist/internal/testing/machine/value.js +82 -0
- package/dist/internal/testing/machine/value.js.map +1 -0
- package/dist/internal/testing/machine/verification.d.ts +2 -13
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +21 -113
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +89 -104
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +70 -84
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +10 -13
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +5 -8
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +26 -61
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +17 -26
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +45 -45
- package/docs/effect-atom-react.md +35 -63
- package/docs/machine-review.md +7 -7
- package/docs/root-api.md +259 -0
- package/package.json +1 -1
- package/src/Machine.ts +1053 -538
- package/src/internal/machine/atom.ts +3 -13
- package/src/internal/machine/childRegistry.ts +141 -0
- package/src/internal/machine/cluster.ts +5 -14
- package/src/internal/machine/configuration.ts +20 -4
- package/src/internal/machine/executionPlan.ts +109 -52
- package/src/internal/machine/invocation.ts +10 -6
- package/src/internal/machine/invocationDefinition.ts +52 -0
- package/src/internal/machine/invocationEvent.ts +0 -8
- package/src/internal/machine/machine.ts +222 -716
- package/src/internal/machine/planner.ts +74 -62
- package/src/internal/machine/process.ts +14 -21
- package/src/internal/machine/protocol.ts +9 -32
- package/src/internal/machine/readiness.ts +1 -0
- package/src/internal/machine/requirements.ts +11 -0
- package/src/internal/machine/runtime.ts +17 -186
- package/src/internal/machine/serialization.ts +22 -0
- package/src/internal/machine/stateDefinition.ts +36 -3
- package/src/internal/machine/targetBuilder.ts +525 -0
- package/src/internal/machine/topology.ts +3 -3
- package/src/internal/machine/transition.ts +26 -0
- package/src/internal/testing/machine/finiteModel.ts +23 -24
- package/src/internal/testing/machine/format.ts +120 -0
- package/src/internal/testing/machine/probe.ts +4 -4
- package/src/internal/testing/machine/referenceModel.ts +25 -17
- package/src/internal/testing/machine/trace.ts +4 -4
- package/src/internal/testing/machine/value.ts +77 -0
- package/src/internal/testing/machine/verification.ts +904 -1109
- package/src/testing/MachineTest.ts +103 -114
- package/src/unstable/cluster/ClusterMachine.ts +10 -20
- package/src/unstable/reactivity/AtomMachine.ts +42 -79
package/README.md
CHANGED
|
@@ -42,57 +42,42 @@ in lockstep with this package.
|
|
|
42
42
|
|
|
43
43
|
## Quick start
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Start with events and root-owned data. A data-only machine needs no child states:
|
|
46
46
|
|
|
47
47
|
```ts
|
|
48
48
|
import { Machine } from "@typeonce/effect-machine"
|
|
49
|
-
import { Effect, Schema
|
|
50
|
-
|
|
51
|
-
const State = Schema.TaggedUnion({
|
|
52
|
-
Running: { count: Schema.Number }
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
const States = Machine.states({
|
|
56
|
-
Idle: {},
|
|
57
|
-
Running: State.cases.Running
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const CounterEvent = Machine.events(
|
|
61
|
-
Schema.TaggedUnion({
|
|
62
|
-
Start: {},
|
|
63
|
-
Increment: {},
|
|
64
|
-
Stop: {}
|
|
65
|
-
})
|
|
66
|
-
)
|
|
49
|
+
import { Effect, Schema } from "effect"
|
|
67
50
|
|
|
51
|
+
const Root = Machine.state({ fields: { count: Schema.Number } })
|
|
52
|
+
const Events = Machine.events({ Increment: { by: Schema.Number } })
|
|
68
53
|
const CounterDefinition = Machine.make({
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
initial: (to) => to.Idle()
|
|
54
|
+
root: Root,
|
|
55
|
+
events: Events,
|
|
56
|
+
initial: (root) => root.from(() => ({ count: 0 }))
|
|
73
57
|
})
|
|
74
|
-
|
|
75
58
|
const Counter = CounterDefinition.handle({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Running: {
|
|
82
|
-
on: {
|
|
83
|
-
Increment: (to) => to.full.Running().resolve(({ state, target }) => target.from({ count: state.count + 1 })),
|
|
84
|
-
Stop: (to) => to.full.Idle()
|
|
85
|
-
}
|
|
59
|
+
on: {
|
|
60
|
+
Increment: (to) =>
|
|
61
|
+
to.self.update.from(({ current, event }) => ({
|
|
62
|
+
count: current.count + event.by
|
|
63
|
+
}))
|
|
86
64
|
}
|
|
87
65
|
})
|
|
88
66
|
|
|
89
|
-
const program = Effect.gen(function*() {
|
|
67
|
+
const program = Effect.scoped(Effect.gen(function*() {
|
|
90
68
|
const ref = yield* Machine.start(Counter)
|
|
91
|
-
yield* ref.send(
|
|
92
|
-
|
|
93
|
-
})
|
|
69
|
+
yield* ref.send(Events.Increment({ by: 1 }))
|
|
70
|
+
}))
|
|
94
71
|
```
|
|
95
72
|
|
|
73
|
+
`self.update` replaces the owner's complete value without exiting and reentering
|
|
74
|
+
it. Its children and scoped work remain active. Add `initial` and `states` to
|
|
75
|
+
the same root descriptor when the workflow needs distinct modes; the root data
|
|
76
|
+
survives transitions between those children.
|
|
77
|
+
|
|
78
|
+
See [the root API guide](./docs/root-api.md) for topology-only machines,
|
|
79
|
+
initialization, reusable schemas, guards, migration, and observation contracts.
|
|
80
|
+
|
|
96
81
|
`handle` creates a complete implementation boundary. Its result does not
|
|
97
82
|
expose `handle`, so all behavior for one machine belongs in the same handler
|
|
98
83
|
tree. Reuse the definition when multiple independent implementations are
|
|
@@ -109,26 +94,25 @@ const TestingCounter = CounterDefinition.handle(testingHandlers)
|
|
|
109
94
|
|
|
110
95
|
## Modeling workflow
|
|
111
96
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
`
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
`Machine.
|
|
131
|
-
`Machine.SnapshotAt<typeof States, Path>`.
|
|
97
|
+
1. Define reusable domain schemas where they express a domain boundary.
|
|
98
|
+
2. Declare one root with `Machine.state`. Use inline children or mount reusable
|
|
99
|
+
descriptors under `states`. `fields` builds a tagged schema; `schema` keeps
|
|
100
|
+
an existing schema and its construction behavior.
|
|
101
|
+
3. Declare field records with `Machine.events`, `Machine.internalEvents`, and
|
|
102
|
+
`Machine.emittedEvents`. Import existing tagged unions or tagged classes with
|
|
103
|
+
the corresponding `eventsFromSchemas`, `internalEventsFromSchemas`, or
|
|
104
|
+
`emittedEventsFromSchemas` constructor.
|
|
105
|
+
4. Pass the root descriptor to `Machine.make({ root, events })`. Supply root
|
|
106
|
+
values with `initial`; declared child defaults determine startup topology.
|
|
107
|
+
5. Implement root behavior directly in `handle`, with child behavior in its
|
|
108
|
+
`states` property. Add runtime, Atom, testing, or Cluster adapters at the
|
|
109
|
+
application boundary.
|
|
110
|
+
|
|
111
|
+
The root has path `""`; children retain paths such as `Editing` and
|
|
112
|
+
`Editing.Form`. Use `Root.path(...)` to validate path literals. Type full
|
|
113
|
+
snapshots as `Machine.Snapshot<typeof Root>` or `Machine.Snapshot<typeof machine>`,
|
|
114
|
+
state values as `Machine.Value<typeof Root, Path>`, and selected snapshots as
|
|
115
|
+
`Machine.SnapshotAt<typeof Root, Path>`.
|
|
132
116
|
|
|
133
117
|
### Make invalid states unrepresentable
|
|
134
118
|
|
|
@@ -187,20 +171,24 @@ Omit `schema` when a state represents control flow but owns no data. Use `{}`
|
|
|
187
171
|
instead of defining an empty tagged schema:
|
|
188
172
|
|
|
189
173
|
```ts
|
|
190
|
-
const States = Machine.
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
174
|
+
const States = Machine.state({
|
|
175
|
+
initial: "Form",
|
|
176
|
+
states: {
|
|
177
|
+
Form: {
|
|
178
|
+
initial: "Editing",
|
|
179
|
+
states: {
|
|
180
|
+
Editing: {},
|
|
181
|
+
Saving
|
|
182
|
+
}
|
|
196
183
|
}
|
|
197
184
|
}
|
|
198
185
|
})
|
|
199
186
|
|
|
200
187
|
const definition = Machine.make({
|
|
201
|
-
|
|
202
|
-
events: Machine.
|
|
203
|
-
|
|
188
|
+
root: States,
|
|
189
|
+
events: Machine.eventsFromSchemas(),
|
|
190
|
+
initialConfiguration: (root) =>
|
|
191
|
+
root.resolve(({ target }) => target.from((to) => to.Form.from((form) => form.Editing.from())))
|
|
204
192
|
})
|
|
205
193
|
```
|
|
206
194
|
|
|
@@ -212,7 +200,7 @@ schema-backed paths. Add a schema later if the state starts owning data.
|
|
|
212
200
|
Keep data-bearing state schemas together in a named `Schema.TaggedUnion` and
|
|
213
201
|
reference its cases from the topology. For a standalone state schema whose
|
|
214
202
|
class identity is useful, declare a named `Schema.TaggedClass`. Do not bury
|
|
215
|
-
|
|
203
|
+
inline `fields` in a `Machine.state` descriptor.
|
|
216
204
|
|
|
217
205
|
Put data on the narrowest state where it is valid. If sibling phases share
|
|
218
206
|
data, put it on their compound parent.
|
|
@@ -224,24 +212,24 @@ belong in `internalEvents`. Ephemeral outward notifications have their own
|
|
|
224
212
|
`emittedEvents` protocol:
|
|
225
213
|
|
|
226
214
|
```ts
|
|
227
|
-
export const CommandEvent = Machine.
|
|
215
|
+
export const CommandEvent = Machine.eventsFromSchemas(
|
|
228
216
|
Schema.TaggedUnion({ Save: {} })
|
|
229
217
|
)
|
|
230
218
|
export type PublicCommandEvent = Machine.EventOf<typeof CommandEvent>
|
|
231
|
-
const InternalEvent = Machine.
|
|
219
|
+
const InternalEvent = Machine.internalEventsFromSchemas(
|
|
232
220
|
Schema.TaggedUnion({
|
|
233
221
|
Saved: { id: Schema.String },
|
|
234
222
|
SaveFailed: { message: Schema.String }
|
|
235
223
|
})
|
|
236
224
|
)
|
|
237
|
-
const Emissions = Machine.
|
|
225
|
+
const Emissions = Machine.emittedEventsFromSchemas(
|
|
238
226
|
Schema.TaggedUnion({
|
|
239
227
|
SaveObserved: { id: Schema.String }
|
|
240
228
|
})
|
|
241
229
|
)
|
|
242
230
|
|
|
243
231
|
const definition = Machine.make({
|
|
244
|
-
|
|
232
|
+
root: States,
|
|
245
233
|
events: CommandEvent,
|
|
246
234
|
internalEvents: InternalEvent,
|
|
247
235
|
emittedEvents: Emissions,
|
|
@@ -352,28 +340,30 @@ machine mailbox and is processed later. A machine that requires an owner
|
|
|
352
340
|
declares the subset of parent inputs it may send with `Machine.parent`:
|
|
353
341
|
|
|
354
342
|
```ts
|
|
355
|
-
const ParentEvents = Machine.
|
|
343
|
+
const ParentEvents = Machine.eventsFromSchemas(ChildFinished)
|
|
356
344
|
|
|
357
345
|
const child = Machine.make({
|
|
358
|
-
|
|
346
|
+
root: ChildStates,
|
|
359
347
|
events: ChildEvents,
|
|
360
348
|
parent: Machine.parent(ParentEvents),
|
|
361
349
|
initial: (to) => to.Working()
|
|
362
350
|
}).handle({
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
to
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
351
|
+
states: {
|
|
352
|
+
Working: {
|
|
353
|
+
on: {
|
|
354
|
+
Finish: (to) =>
|
|
355
|
+
to.branch.Done().resolve(({ parent, target }, enqueue) => {
|
|
356
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
357
|
+
return target.from()
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
Done: {}
|
|
362
|
+
}
|
|
373
363
|
})
|
|
374
364
|
|
|
375
365
|
const Child = Machine.child("worker", child)
|
|
376
|
-
const ParentInputs = Machine.
|
|
366
|
+
const ParentInputs = Machine.eventsFromSchemas(Start, ParentEvents)
|
|
377
367
|
```
|
|
378
368
|
|
|
379
369
|
`parent` is statically present in every child callback, and root APIs such as
|
|
@@ -409,7 +399,7 @@ paths. `parent` always means the owning machine target.
|
|
|
409
399
|
| `target.history` | Restoring a declared history node | The remembered configuration or its typed default |
|
|
410
400
|
|
|
411
401
|
Every required transition handler selects a target from its inline `to`
|
|
412
|
-
builder. Return a bare selection such as `to.
|
|
402
|
+
builder. Return a bare selection such as `to.local.Idle()` when the selected
|
|
413
403
|
builder supports zero-argument construction; the machine applies the same
|
|
414
404
|
default construction as `target.from()`. This includes empty schemas and
|
|
415
405
|
schemas whose constructor fields are all optional or defaulted. TypeScript
|
|
@@ -421,13 +411,14 @@ selecting a destination. Concrete destinations stay narrowed inside their
|
|
|
421
411
|
resolver, and `to.branches({...})` gives the resolver only the declared named
|
|
422
412
|
`select` builders. Builders describe the
|
|
423
413
|
next logical configuration. Shared states exit and enter only when paths
|
|
424
|
-
change; call `.reenter()`
|
|
425
|
-
|
|
414
|
+
change; call `.reenter()` before `.from(...)`, `.decoded(...)`, or `.resolve(...)`
|
|
415
|
+
when the source must restart. Default-constructible targets can finish at
|
|
416
|
+
`.reenter()`. With `to.none`, reentry
|
|
426
417
|
restarts the source while retaining its configuration.
|
|
427
418
|
|
|
428
419
|
Topology-only definition instructions are values: `to.none`, declared
|
|
429
420
|
`.initial` and history selections, and `to.local.with`. Concrete state and
|
|
430
|
-
choice destinations remain calls such as `to.
|
|
421
|
+
choice destinations remain calls such as `to.local.Running()`. Runtime named
|
|
431
422
|
branch builders remain callable, including `select.unchanged()`, because their
|
|
432
423
|
result carries the selected branch evidence.
|
|
433
424
|
|
|
@@ -440,7 +431,7 @@ handler source:
|
|
|
440
431
|
|
|
441
432
|
```ts
|
|
442
433
|
const handlers = {
|
|
443
|
-
Increment: (to) => to.branch.root.session.update(({ current
|
|
434
|
+
Increment: (to) => to.branch.root.session.update.from(({ current }) => ({ count: current.count + 1 }))
|
|
444
435
|
}
|
|
445
436
|
```
|
|
446
437
|
|
|
@@ -473,30 +464,28 @@ const handlers = {
|
|
|
473
464
|
CreatePlan: (to) =>
|
|
474
465
|
to.local.SavingPlan()
|
|
475
466
|
.updating(to.branch.Ready)
|
|
476
|
-
.
|
|
477
|
-
target.
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
owner.decoded(new Ready({ ...current, notice: null }))
|
|
481
|
-
)
|
|
482
|
-
)
|
|
467
|
+
.from(({ current, event }) => ({
|
|
468
|
+
target: { request: { _tag: "Create", input: event.input } },
|
|
469
|
+
update: { ...current, notice: null }
|
|
470
|
+
}))
|
|
483
471
|
}
|
|
484
472
|
```
|
|
485
473
|
|
|
486
474
|
`to.local.SavingPlan()` selects topology. `.updating(to.branch.Ready)` names
|
|
487
|
-
the retained valued owner and makes its replacement mandatory
|
|
488
|
-
|
|
489
|
-
`.
|
|
490
|
-
pre-transition snapshot.
|
|
491
|
-
|
|
475
|
+
the retained valued owner and makes its replacement mandatory. `.from(...)`
|
|
476
|
+
returns `{ target, update }` with constructor inputs for both values;
|
|
477
|
+
`.decoded(...)` returns already decoded values for both. `current` is that
|
|
478
|
+
owner's decoded value from the pre-transition snapshot. Use `.resolve(...)`
|
|
479
|
+
when constructing explicit children, mixing construction methods, or queuing
|
|
480
|
+
commands; its `target` and `owner` builders construct the two values.
|
|
492
481
|
|
|
493
482
|
The topology change and owner replacement apply atomically in one microstep.
|
|
494
483
|
The owner does not exit or reenter, its work is not restarted, and destination
|
|
495
484
|
entry actions observe the new owner value. Eventless stabilization follows.
|
|
496
485
|
Only one retained owner may be replaced by a combined target. A `full` target,
|
|
497
486
|
or any target that exits the selected owner, does not expose `.updating`.
|
|
498
|
-
|
|
499
|
-
|
|
487
|
+
Named branches support value-only updates; a combined update declares its
|
|
488
|
+
destination directly.
|
|
500
489
|
|
|
501
490
|
For a schema-less destination, construction remains explicit:
|
|
502
491
|
|
|
@@ -521,11 +510,13 @@ before lifecycle actions run. Competing transitions that write the same owner
|
|
|
521
510
|
conflict; document order and hierarchy select one writer rather than applying
|
|
522
511
|
last-write-wins behavior.
|
|
523
512
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
513
|
+
Use `.guard(predicate)` before construction to decline an update without
|
|
514
|
+
constructing values or queuing commands. It is available on standalone and
|
|
515
|
+
combined updates. A false guard allows ancestor fallback. A resolver may also
|
|
516
|
+
return `decline()` with `{ declinable: true }` for decisions during resolution.
|
|
517
|
+
Call `.reenter()` before construction on event or invocation transitions when
|
|
518
|
+
the handler source should exit and enter again. Reentry applies to that source,
|
|
519
|
+
not to the retained ancestor whose value changed.
|
|
529
520
|
|
|
530
521
|
The selector omits `update` for schema-less scopes, atomic and final states,
|
|
531
522
|
inactive branches, parallel sibling regions, and choice resolvers. Updating a
|
|
@@ -574,7 +565,7 @@ lifecycle do not run.
|
|
|
574
565
|
|
|
575
566
|
## Statechart capabilities
|
|
576
567
|
|
|
577
|
-
`Machine.
|
|
568
|
+
`Machine.state` supports:
|
|
578
569
|
|
|
579
570
|
- atomic states;
|
|
580
571
|
- compound states with one active child;
|
|
@@ -599,16 +590,18 @@ State-scoped work starts on entry and is interrupted on exit:
|
|
|
599
590
|
|
|
600
591
|
```ts
|
|
601
592
|
machine.handle({
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
.
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
from
|
|
611
|
-
.
|
|
593
|
+
states: {
|
|
594
|
+
Loading: {
|
|
595
|
+
invoke: (from) =>
|
|
596
|
+
from.effect("save-document", () => saveDocument)
|
|
597
|
+
.onDone((to) => to.branch.Saved().resolve(({ output, target }) => target.from({ id: output.id })))
|
|
598
|
+
.onFailure((to) => to.branch.Failed().resolve(({ error, target }) => target.from({ message: String(error) })))
|
|
599
|
+
},
|
|
600
|
+
Waiting: {
|
|
601
|
+
invoke: (from) =>
|
|
602
|
+
from.timer("save-timeout", "3 seconds")
|
|
603
|
+
.onDone((to) => to.branch.Failed().resolve(({ target }) => target.from({ message: "Timed out" })))
|
|
604
|
+
}
|
|
612
605
|
}
|
|
613
606
|
})
|
|
614
607
|
```
|
|
@@ -621,11 +614,13 @@ the completed chain is the value returned by the callback:
|
|
|
621
614
|
|
|
622
615
|
```ts
|
|
623
616
|
machine.handle({
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
.
|
|
628
|
-
|
|
617
|
+
states: {
|
|
618
|
+
Loading: {
|
|
619
|
+
invoke: (from) =>
|
|
620
|
+
from.effect("load-document", ({ state }) => loadDocument(state.documentId))
|
|
621
|
+
.onDone((to) => to.branch.Ready().resolve(({ output, target }) => target.from({ document: output })))
|
|
622
|
+
.onFailure((to) => to.branch.Failed().resolve(({ error, target }) => target.from({ message: error.message })))
|
|
623
|
+
}
|
|
629
624
|
}
|
|
630
625
|
})
|
|
631
626
|
```
|
|
@@ -636,16 +631,18 @@ macrostep commits:
|
|
|
636
631
|
|
|
637
632
|
```ts
|
|
638
633
|
machine.handle({
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
634
|
+
states: {
|
|
635
|
+
Listening: {
|
|
636
|
+
invoke: (from) =>
|
|
637
|
+
from.stream("channel", () => channelMessages)
|
|
638
|
+
.onElement((to) =>
|
|
639
|
+
to.none.resolve(({ element }, enqueue) => {
|
|
640
|
+
enqueue.raise(Events.MessageReceived({ message: element }))
|
|
641
|
+
})
|
|
642
|
+
)
|
|
643
|
+
.onDone((to) => to.none)
|
|
644
|
+
.onFailure((to) => to.branch.Failed().resolve(({ error, target }) => target.from({ error })))
|
|
645
|
+
}
|
|
649
646
|
}
|
|
650
647
|
})
|
|
651
648
|
```
|
|
@@ -667,16 +664,18 @@ const machine = Machine.make({
|
|
|
667
664
|
parent: Machine.parent(ParentEvents)
|
|
668
665
|
// ...
|
|
669
666
|
}).handle({
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
.
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
667
|
+
states: {
|
|
668
|
+
Saving: {
|
|
669
|
+
invoke: (from) =>
|
|
670
|
+
from.effect("notify-parent", () => saveDocument)
|
|
671
|
+
.onDone((to) =>
|
|
672
|
+
to.none.resolve(({ parent, self }, enqueue) => {
|
|
673
|
+
enqueue.sendTo(self, Commands.Save())
|
|
674
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
675
|
+
})
|
|
676
|
+
)
|
|
677
|
+
.onFailure((to) => to.none)
|
|
678
|
+
}
|
|
680
679
|
}
|
|
681
680
|
})
|
|
682
681
|
```
|
|
@@ -690,11 +689,13 @@ transitions retain the exact owning state and machine protocols.
|
|
|
690
689
|
const refreshCache = Cache.refresh
|
|
691
690
|
|
|
692
691
|
machine.handle({
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
692
|
+
states: {
|
|
693
|
+
Active: {
|
|
694
|
+
invoke: (from) => [
|
|
695
|
+
from.effect("refresh-cache", () => refreshCache).onDone((to) => to.none).onFailure((to) => to.none),
|
|
696
|
+
from.timer("expire-session", "5 minutes").onDone((to) => to.branch.Expired())
|
|
697
|
+
]
|
|
698
|
+
}
|
|
698
699
|
}
|
|
699
700
|
})
|
|
700
701
|
```
|
|
@@ -720,19 +721,21 @@ open set of children that must survive state changes:
|
|
|
720
721
|
const Plant = Machine.childFamily(plantMachine)
|
|
721
722
|
|
|
722
723
|
const central = Machine.make({
|
|
723
|
-
events: Machine.
|
|
724
|
+
events: Machine.eventsFromSchemas(ResourcesOffered, PlantBroken)
|
|
724
725
|
// ...
|
|
725
726
|
}).handle({
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
727
|
+
states: {
|
|
728
|
+
Commissioning: {
|
|
729
|
+
invoke: (from) =>
|
|
730
|
+
from.effect("commission-wave", ({ children, state }) =>
|
|
731
|
+
Effect.forEach(
|
|
732
|
+
state.plants,
|
|
733
|
+
(input) => children.spawn(Plant(input.id), { input }),
|
|
734
|
+
{ discard: true }
|
|
735
|
+
))
|
|
736
|
+
.onDone((to) => to.branch.Operating())
|
|
737
|
+
.onFailure((to) => to.branch.CommissioningFailed())
|
|
738
|
+
}
|
|
736
739
|
}
|
|
737
740
|
})
|
|
738
741
|
```
|
|
@@ -857,7 +860,10 @@ or child instance.
|
|
|
857
860
|
|
|
858
861
|
## Persistence
|
|
859
862
|
|
|
860
|
-
Logical snapshots
|
|
863
|
+
Logical snapshots use codec version 2, including the root at path `""`. Earlier
|
|
864
|
+
encoded snapshots are rejected; migrate persisted data explicitly before decoding.
|
|
865
|
+
|
|
866
|
+
Snapshots can be validated for storage or transport:
|
|
861
867
|
|
|
862
868
|
```ts
|
|
863
869
|
const encoded = yield * Machine.encodeSnapshot(machine, snapshot)
|