@typeonce/effect-machine 0.31.2 → 0.32.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 (151) hide show
  1. package/README.md +145 -140
  2. package/dist/Machine.d.ts +428 -254
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +91 -111
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +3 -4
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js +0 -2
  9. package/dist/internal/machine/atom.js.map +1 -1
  10. package/dist/internal/machine/childRegistry.d.ts +52 -0
  11. package/dist/internal/machine/childRegistry.d.ts.map +1 -0
  12. package/dist/internal/machine/childRegistry.js +74 -0
  13. package/dist/internal/machine/childRegistry.js.map +1 -0
  14. package/dist/internal/machine/cluster.d.ts +2 -3
  15. package/dist/internal/machine/cluster.d.ts.map +1 -1
  16. package/dist/internal/machine/cluster.js.map +1 -1
  17. package/dist/internal/machine/configuration.d.ts.map +1 -1
  18. package/dist/internal/machine/configuration.js +14 -4
  19. package/dist/internal/machine/configuration.js.map +1 -1
  20. package/dist/internal/machine/executionPlan.d.ts +1 -0
  21. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  22. package/dist/internal/machine/executionPlan.js +97 -53
  23. package/dist/internal/machine/executionPlan.js.map +1 -1
  24. package/dist/internal/machine/invocation.d.ts.map +1 -1
  25. package/dist/internal/machine/invocation.js +4 -2
  26. package/dist/internal/machine/invocation.js.map +1 -1
  27. package/dist/internal/machine/invocationDefinition.d.ts +35 -0
  28. package/dist/internal/machine/invocationDefinition.d.ts.map +1 -0
  29. package/dist/internal/machine/invocationDefinition.js +21 -0
  30. package/dist/internal/machine/invocationDefinition.js.map +1 -0
  31. package/dist/internal/machine/invocationEvent.d.ts +0 -2
  32. package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
  33. package/dist/internal/machine/invocationEvent.js +0 -8
  34. package/dist/internal/machine/invocationEvent.js.map +1 -1
  35. package/dist/internal/machine/machine.d.ts +21 -43
  36. package/dist/internal/machine/machine.d.ts.map +1 -1
  37. package/dist/internal/machine/machine.js +98 -374
  38. package/dist/internal/machine/machine.js.map +1 -1
  39. package/dist/internal/machine/planner.d.ts +13 -19
  40. package/dist/internal/machine/planner.d.ts.map +1 -1
  41. package/dist/internal/machine/planner.js +63 -35
  42. package/dist/internal/machine/planner.js.map +1 -1
  43. package/dist/internal/machine/process.d.ts +6 -8
  44. package/dist/internal/machine/process.d.ts.map +1 -1
  45. package/dist/internal/machine/process.js +1 -0
  46. package/dist/internal/machine/process.js.map +1 -1
  47. package/dist/internal/machine/protocol.d.ts +2 -2
  48. package/dist/internal/machine/protocol.d.ts.map +1 -1
  49. package/dist/internal/machine/protocol.js +4 -18
  50. package/dist/internal/machine/protocol.js.map +1 -1
  51. package/dist/internal/machine/readiness.d.ts +1 -1
  52. package/dist/internal/machine/readiness.d.ts.map +1 -1
  53. package/dist/internal/machine/requirements.d.ts +4 -0
  54. package/dist/internal/machine/requirements.d.ts.map +1 -0
  55. package/dist/internal/machine/requirements.js +2 -0
  56. package/dist/internal/machine/requirements.js.map +1 -0
  57. package/dist/internal/machine/runtime.d.ts +4 -58
  58. package/dist/internal/machine/runtime.d.ts.map +1 -1
  59. package/dist/internal/machine/runtime.js +1 -69
  60. package/dist/internal/machine/runtime.js.map +1 -1
  61. package/dist/internal/machine/serialization.d.ts.map +1 -1
  62. package/dist/internal/machine/serialization.js +16 -0
  63. package/dist/internal/machine/serialization.js.map +1 -1
  64. package/dist/internal/machine/stateDefinition.d.ts +3 -1
  65. package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
  66. package/dist/internal/machine/stateDefinition.js +36 -2
  67. package/dist/internal/machine/stateDefinition.js.map +1 -1
  68. package/dist/internal/machine/targetBuilder.d.ts +16 -0
  69. package/dist/internal/machine/targetBuilder.d.ts.map +1 -0
  70. package/dist/internal/machine/targetBuilder.js +333 -0
  71. package/dist/internal/machine/targetBuilder.js.map +1 -0
  72. package/dist/internal/machine/topology.js +3 -3
  73. package/dist/internal/machine/topology.js.map +1 -1
  74. package/dist/internal/machine/transition.d.ts +16 -0
  75. package/dist/internal/machine/transition.d.ts.map +1 -0
  76. package/dist/internal/machine/transition.js +2 -0
  77. package/dist/internal/machine/transition.js.map +1 -0
  78. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  79. package/dist/internal/testing/machine/finiteModel.js +10 -12
  80. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  81. package/dist/internal/testing/machine/format.d.ts +7 -0
  82. package/dist/internal/testing/machine/format.d.ts.map +1 -0
  83. package/dist/internal/testing/machine/format.js +104 -0
  84. package/dist/internal/testing/machine/format.js.map +1 -0
  85. package/dist/internal/testing/machine/probe.d.ts +1 -1
  86. package/dist/internal/testing/machine/probe.d.ts.map +1 -1
  87. package/dist/internal/testing/machine/probe.js +1 -1
  88. package/dist/internal/testing/machine/probe.js.map +1 -1
  89. package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -1
  90. package/dist/internal/testing/machine/referenceModel.js +15 -19
  91. package/dist/internal/testing/machine/referenceModel.js.map +1 -1
  92. package/dist/internal/testing/machine/trace.d.ts +1 -1
  93. package/dist/internal/testing/machine/trace.d.ts.map +1 -1
  94. package/dist/internal/testing/machine/trace.js +3 -3
  95. package/dist/internal/testing/machine/trace.js.map +1 -1
  96. package/dist/internal/testing/machine/value.d.ts +8 -0
  97. package/dist/internal/testing/machine/value.d.ts.map +1 -0
  98. package/dist/internal/testing/machine/value.js +82 -0
  99. package/dist/internal/testing/machine/value.js.map +1 -0
  100. package/dist/internal/testing/machine/verification.d.ts +2 -13
  101. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  102. package/dist/internal/testing/machine/verification.js +21 -113
  103. package/dist/internal/testing/machine/verification.js.map +1 -1
  104. package/dist/testing/MachineTest.d.ts +89 -104
  105. package/dist/testing/MachineTest.d.ts.map +1 -1
  106. package/dist/testing/MachineTest.js +70 -84
  107. package/dist/testing/MachineTest.js.map +1 -1
  108. package/dist/unstable/cluster/ClusterMachine.d.ts +10 -13
  109. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
  110. package/dist/unstable/cluster/ClusterMachine.js +5 -8
  111. package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
  112. package/dist/unstable/reactivity/AtomMachine.d.ts +26 -61
  113. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  114. package/dist/unstable/reactivity/AtomMachine.js +17 -26
  115. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  116. package/docs/agent-guide.md +45 -45
  117. package/docs/effect-atom-react.md +35 -63
  118. package/docs/machine-review.md +7 -7
  119. package/docs/root-api.md +214 -0
  120. package/package.json +1 -1
  121. package/src/Machine.ts +778 -437
  122. package/src/internal/machine/atom.ts +3 -13
  123. package/src/internal/machine/childRegistry.ts +141 -0
  124. package/src/internal/machine/cluster.ts +5 -14
  125. package/src/internal/machine/configuration.ts +20 -4
  126. package/src/internal/machine/executionPlan.ts +109 -52
  127. package/src/internal/machine/invocation.ts +10 -6
  128. package/src/internal/machine/invocationDefinition.ts +52 -0
  129. package/src/internal/machine/invocationEvent.ts +0 -8
  130. package/src/internal/machine/machine.ts +154 -679
  131. package/src/internal/machine/planner.ts +74 -62
  132. package/src/internal/machine/process.ts +14 -21
  133. package/src/internal/machine/protocol.ts +9 -32
  134. package/src/internal/machine/readiness.ts +1 -0
  135. package/src/internal/machine/requirements.ts +11 -0
  136. package/src/internal/machine/runtime.ts +17 -186
  137. package/src/internal/machine/serialization.ts +22 -0
  138. package/src/internal/machine/stateDefinition.ts +36 -3
  139. package/src/internal/machine/targetBuilder.ts +525 -0
  140. package/src/internal/machine/topology.ts +3 -3
  141. package/src/internal/machine/transition.ts +26 -0
  142. package/src/internal/testing/machine/finiteModel.ts +22 -23
  143. package/src/internal/testing/machine/format.ts +120 -0
  144. package/src/internal/testing/machine/probe.ts +4 -4
  145. package/src/internal/testing/machine/referenceModel.ts +25 -17
  146. package/src/internal/testing/machine/trace.ts +4 -4
  147. package/src/internal/testing/machine/value.ts +77 -0
  148. package/src/internal/testing/machine/verification.ts +904 -1109
  149. package/src/testing/MachineTest.ts +103 -114
  150. package/src/unstable/cluster/ClusterMachine.ts +10 -20
  151. 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
- Define schemas first, derive the state topology, then add behavior:
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, Stream } from "effect"
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
- id: "Counter",
70
- states: States.states,
71
- events: CounterEvent,
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
- Idle: {
77
- on: {
78
- Start: (to) => to.full.Running().resolve(({ target }) => target.from({ count: 0 }))
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(CounterEvent.Start())
92
- yield* ref.send(CounterEvent.Increment())
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
- Use this order to preserve inference and keep boundaries explicit:
113
-
114
- 1. Define domain schemas used by state and by shared event fields.
115
- 2. Declare topology with `Machine.states`, naming a tagged state union
116
- when its `.cases` are reused.
117
- 3. Create event descriptors with `Machine.events`, `Machine.internalEvents`,
118
- and `Machine.emittedEvents`, passing tagged unions or tagged classes directly.
119
- 4. Create the machine and implement every active state with
120
- `Machine.make({...}).handle({...})`.
121
- 5. Add child descriptors, then runtime, Atom, testing, or cluster adapters at
122
- the application boundary.
123
-
124
- Keep one-off topology inline in `Machine.states`. Use `Machine.state` only when
125
- the same active state definition is mounted more than once; tagged schemas are
126
- already reusable without it. For repeated finite regions, derive names with
127
- `States.path(...)` so every literal in the path family is checked against the
128
- complete tree. Type full-snapshot helpers as `Machine.Snapshot<typeof States>`
129
- or `Machine.Snapshot<typeof machine>`, schema-backed state payloads as
130
- `Machine.Value<typeof States, Path>`, and path-rooted snapshots as
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.states({
191
- Form: {
192
- initial: "Editing",
193
- states: {
194
- Editing: {},
195
- Saving
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
- states: States.states,
202
- events: Machine.events(),
203
- initial: (to) => to.Form.initial.resolve(({ target }) => target.from((form) => form.Editing.from()))
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
- one-off tagged schema declarations inside `Machine.states`.
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.events(
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.internalEvents(
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.emittedEvents(
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
- states: States.states,
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.events(ChildFinished)
343
+ const ParentEvents = Machine.eventsFromSchemas(ChildFinished)
356
344
 
357
345
  const child = Machine.make({
358
- states: ChildStates.states,
346
+ root: ChildStates,
359
347
  events: ChildEvents,
360
348
  parent: Machine.parent(ParentEvents),
361
349
  initial: (to) => to.Working()
362
350
  }).handle({
363
- Working: {
364
- on: {
365
- Finish: (to) =>
366
- to.full.Done().resolve(({ parent, target }, enqueue) => {
367
- enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
368
- return target.from()
369
- })
370
- }
371
- },
372
- Done: {}
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.events(Start, ParentEvents)
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.full.Idle()` when the selected
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
@@ -427,7 +417,7 @@ restarts the source while retaining its configuration.
427
417
 
428
418
  Topology-only definition instructions are values: `to.none`, declared
429
419
  `.initial` and history selections, and `to.local.with`. Concrete state and
430
- choice destinations remain calls such as `to.full.Running()`. Runtime named
420
+ choice destinations remain calls such as `to.local.Running()`. Runtime named
431
421
  branch builders remain callable, including `select.unchanged()`, because their
432
422
  result carries the selected branch evidence.
433
423
 
@@ -574,7 +564,7 @@ lifecycle do not run.
574
564
 
575
565
  ## Statechart capabilities
576
566
 
577
- `Machine.states` supports:
567
+ `Machine.state` supports:
578
568
 
579
569
  - atomic states;
580
570
  - compound states with one active child;
@@ -599,16 +589,18 @@ State-scoped work starts on entry and is interrupted on exit:
599
589
 
600
590
  ```ts
601
591
  machine.handle({
602
- Loading: {
603
- invoke: (from) =>
604
- from.effect("save-document", () => saveDocument)
605
- .onDone((to) => to.full.Saved().resolve(({ output, target }) => target.from({ id: output.id })))
606
- .onFailure((to) => to.full.Failed().resolve(({ error, target }) => target.from({ message: String(error) })))
607
- },
608
- Waiting: {
609
- invoke: (from) =>
610
- from.timer("save-timeout", "3 seconds")
611
- .onDone((to) => to.full.Failed().resolve(({ target }) => target.from({ message: "Timed out" })))
592
+ states: {
593
+ Loading: {
594
+ invoke: (from) =>
595
+ from.effect("save-document", () => saveDocument)
596
+ .onDone((to) => to.branch.Saved().resolve(({ output, target }) => target.from({ id: output.id })))
597
+ .onFailure((to) => to.branch.Failed().resolve(({ error, target }) => target.from({ message: String(error) })))
598
+ },
599
+ Waiting: {
600
+ invoke: (from) =>
601
+ from.timer("save-timeout", "3 seconds")
602
+ .onDone((to) => to.branch.Failed().resolve(({ target }) => target.from({ message: "Timed out" })))
603
+ }
612
604
  }
613
605
  })
614
606
  ```
@@ -621,11 +613,13 @@ the completed chain is the value returned by the callback:
621
613
 
622
614
  ```ts
623
615
  machine.handle({
624
- Loading: {
625
- invoke: (from) =>
626
- from.effect("load-document", ({ state }) => loadDocument(state.documentId))
627
- .onDone((to) => to.full.Ready().resolve(({ output, target }) => target.from({ document: output })))
628
- .onFailure((to) => to.full.Failed().resolve(({ error, target }) => target.from({ message: error.message })))
616
+ states: {
617
+ Loading: {
618
+ invoke: (from) =>
619
+ from.effect("load-document", ({ state }) => loadDocument(state.documentId))
620
+ .onDone((to) => to.branch.Ready().resolve(({ output, target }) => target.from({ document: output })))
621
+ .onFailure((to) => to.branch.Failed().resolve(({ error, target }) => target.from({ message: error.message })))
622
+ }
629
623
  }
630
624
  })
631
625
  ```
@@ -636,16 +630,18 @@ macrostep commits:
636
630
 
637
631
  ```ts
638
632
  machine.handle({
639
- Listening: {
640
- invoke: (from) =>
641
- from.stream("channel", () => channelMessages)
642
- .onElement((to) =>
643
- to.none.resolve(({ element }, enqueue) => {
644
- enqueue.raise(Events.MessageReceived({ message: element }))
645
- })
646
- )
647
- .onDone((to) => to.none)
648
- .onFailure((to) => to.full.Failed().resolve(({ error, target }) => target.from({ error })))
633
+ states: {
634
+ Listening: {
635
+ invoke: (from) =>
636
+ from.stream("channel", () => channelMessages)
637
+ .onElement((to) =>
638
+ to.none.resolve(({ element }, enqueue) => {
639
+ enqueue.raise(Events.MessageReceived({ message: element }))
640
+ })
641
+ )
642
+ .onDone((to) => to.none)
643
+ .onFailure((to) => to.branch.Failed().resolve(({ error, target }) => target.from({ error })))
644
+ }
649
645
  }
650
646
  })
651
647
  ```
@@ -667,16 +663,18 @@ const machine = Machine.make({
667
663
  parent: Machine.parent(ParentEvents)
668
664
  // ...
669
665
  }).handle({
670
- Saving: {
671
- invoke: (from) =>
672
- from.effect("notify-parent", () => saveDocument)
673
- .onDone((to) =>
674
- to.none.resolve(({ parent, self }, enqueue) => {
675
- enqueue.sendTo(self, Commands.Save())
676
- enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
677
- })
678
- )
679
- .onFailure((to) => to.none)
666
+ states: {
667
+ Saving: {
668
+ invoke: (from) =>
669
+ from.effect("notify-parent", () => saveDocument)
670
+ .onDone((to) =>
671
+ to.none.resolve(({ parent, self }, enqueue) => {
672
+ enqueue.sendTo(self, Commands.Save())
673
+ enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
674
+ })
675
+ )
676
+ .onFailure((to) => to.none)
677
+ }
680
678
  }
681
679
  })
682
680
  ```
@@ -690,11 +688,13 @@ transitions retain the exact owning state and machine protocols.
690
688
  const refreshCache = Cache.refresh
691
689
 
692
690
  machine.handle({
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.full.Expired())
697
- ]
691
+ states: {
692
+ Active: {
693
+ invoke: (from) => [
694
+ from.effect("refresh-cache", () => refreshCache).onDone((to) => to.none).onFailure((to) => to.none),
695
+ from.timer("expire-session", "5 minutes").onDone((to) => to.branch.Expired())
696
+ ]
697
+ }
698
698
  }
699
699
  })
700
700
  ```
@@ -720,19 +720,21 @@ open set of children that must survive state changes:
720
720
  const Plant = Machine.childFamily(plantMachine)
721
721
 
722
722
  const central = Machine.make({
723
- events: Machine.events(ResourcesOffered, PlantBroken)
723
+ events: Machine.eventsFromSchemas(ResourcesOffered, PlantBroken)
724
724
  // ...
725
725
  }).handle({
726
- Commissioning: {
727
- invoke: (from) =>
728
- from.effect("commission-wave", ({ children, state }) =>
729
- Effect.forEach(
730
- state.plants,
731
- (input) => children.spawn(Plant(input.id), { input }),
732
- { discard: true }
733
- ))
734
- .onDone((to) => to.full.Operating())
735
- .onFailure((to) => to.full.CommissioningFailed())
726
+ states: {
727
+ Commissioning: {
728
+ invoke: (from) =>
729
+ from.effect("commission-wave", ({ children, state }) =>
730
+ Effect.forEach(
731
+ state.plants,
732
+ (input) => children.spawn(Plant(input.id), { input }),
733
+ { discard: true }
734
+ ))
735
+ .onDone((to) => to.branch.Operating())
736
+ .onFailure((to) => to.branch.CommissioningFailed())
737
+ }
736
738
  }
737
739
  })
738
740
  ```
@@ -857,7 +859,10 @@ or child instance.
857
859
 
858
860
  ## Persistence
859
861
 
860
- Logical snapshots can be validated for storage or transport:
862
+ Logical snapshots use codec version 2, including the root at path `""`. Earlier
863
+ encoded snapshots are rejected; migrate persisted data explicitly before decoding.
864
+
865
+ Snapshots can be validated for storage or transport:
861
866
 
862
867
  ```ts
863
868
  const encoded = yield * Machine.encodeSnapshot(machine, snapshot)