@typeonce/effect-machine 0.18.0 → 0.19.1

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 (33) hide show
  1. package/README.md +54 -16
  2. package/dist/Machine.d.ts +300 -30
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +10 -6
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/cluster.d.ts +2 -2
  7. package/dist/internal/machine/cluster.d.ts.map +1 -1
  8. package/dist/internal/machine/cluster.js +2 -1
  9. package/dist/internal/machine/cluster.js.map +1 -1
  10. package/dist/internal/machine/serialization.d.ts.map +1 -1
  11. package/dist/internal/machine/serialization.js +75 -18
  12. package/dist/internal/machine/serialization.js.map +1 -1
  13. package/dist/internal/testing/machine/verification.d.ts +1 -1
  14. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  15. package/dist/internal/testing/machine/verification.js +9 -6
  16. package/dist/internal/testing/machine/verification.js.map +1 -1
  17. package/dist/testing/MachineTest.d.ts +9 -6
  18. package/dist/testing/MachineTest.d.ts.map +1 -1
  19. package/dist/testing/MachineTest.js +5 -3
  20. package/dist/testing/MachineTest.js.map +1 -1
  21. package/dist/unstable/cluster/ClusterMachine.d.ts +22 -2
  22. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
  23. package/dist/unstable/cluster/ClusterMachine.js +4 -0
  24. package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
  25. package/docs/agent-guide.md +278 -1389
  26. package/docs/effect-atom-react.md +202 -0
  27. package/package.json +4 -4
  28. package/src/Machine.ts +315 -33
  29. package/src/internal/machine/cluster.ts +4 -1
  30. package/src/internal/machine/serialization.ts +100 -25
  31. package/src/internal/testing/machine/verification.ts +15 -10
  32. package/src/testing/MachineTest.ts +9 -6
  33. package/src/unstable/cluster/ClusterMachine.ts +51 -1
package/README.md CHANGED
@@ -34,7 +34,7 @@ Cluster and are exposed only through explicit integration boundaries.
34
34
  ## Install
35
35
 
36
36
  ```sh
37
- pnpm add @typeonce/effect-machine effect@4.0.0-rc.109
37
+ pnpm add @typeonce/effect-machine effect@4.0.0-rc.111
38
38
  ```
39
39
 
40
40
  `effect` is an exact peer dependency. Install the version above and upgrade it
@@ -49,11 +49,14 @@ import { Machine } from "@typeonce/effect-machine"
49
49
  import { Effect, Schema, Stream } from "effect"
50
50
 
51
51
  const State = Schema.TaggedUnion({
52
- Idle: {},
53
52
  Running: { count: Schema.Number }
54
53
  })
55
54
 
56
- const States = Machine.states(State.cases)
55
+ const States = Machine.states({
56
+ Idle: {},
57
+ Running: State.cases.Running
58
+ })
59
+
57
60
  const CounterEvent = Machine.events(
58
61
  Schema.TaggedUnion({
59
62
  Start: {},
@@ -66,7 +69,7 @@ const CounterDefinition = Machine.make({
66
69
  id: "Counter",
67
70
  states: States.states,
68
71
  events: CounterEvent,
69
- initial: (to) => to.Idle().resolve(({ target }) => target.from())
72
+ initial: (to) => to.Idle()
70
73
  })
71
74
 
72
75
  const Counter = CounterDefinition.handle({
@@ -78,7 +81,7 @@ const Counter = CounterDefinition.handle({
78
81
  Running: {
79
82
  on: {
80
83
  Increment: (to) => to.full.Running().resolve(({ state, target }) => target.from({ count: state.count + 1 })),
81
- Stop: (to) => to.full.Idle().resolve(({ target }) => target.from())
84
+ Stop: (to) => to.full.Idle()
82
85
  }
83
86
  }
84
87
  })
@@ -127,6 +130,23 @@ or `Machine.Snapshot<typeof machine>`, schema-backed state payloads as
127
130
  `Machine.Value<typeof States, Path>`, and path-rooted snapshots as
128
131
  `Machine.SnapshotAt<typeof States, Path>`.
129
132
 
133
+ ### Make invalid states unrepresentable
134
+
135
+ Treat topology as a domain contract, not as file organization. A parallel state
136
+ declares the full Cartesian product of its regions, so use it only when every
137
+ combination has a coherent meaning. If one region must inspect another before
138
+ entering a state safely, prefer a compound hierarchy that makes the forbidden
139
+ combination impossible. `matches` remains useful for views, tests, and genuine
140
+ coordination between independent regions; it should not repair an invalid
141
+ state product.
142
+
143
+ Keep state-scoped Effects beneath the state that guarantees their resources,
144
+ and enforce command availability in the machine rather than only by disabling
145
+ UI controls. When entering an inactive compound or parallel state's declared
146
+ default, select `.initial`; explicitly construct descendants only for a
147
+ non-default configuration or a complete replacement of an already-active
148
+ parallel root.
149
+
130
150
  ### Construct state through builders
131
151
 
132
152
  Use `.from(...)` when constructing a new state from fields:
@@ -153,7 +173,8 @@ const handlers = {
153
173
  }
154
174
  ```
155
175
 
156
- Omit `schema` when a state represents control flow but owns no data:
176
+ Omit `schema` when a state represents control flow but owns no data. Use `{}`
177
+ instead of defining an empty tagged schema:
157
178
 
158
179
  ```ts
159
180
  const States = Machine.states({
@@ -178,6 +199,11 @@ Schema-less states remain active, targetable, matchable, and visible through
178
199
  their handler `state` is `undefined`, and `get` / `getWithParents` accept only
179
200
  schema-backed paths. Add a schema later if the state starts owning data.
180
201
 
202
+ Keep data-bearing state schemas together in a named `Schema.TaggedUnion` and
203
+ reference its cases from the topology. For a standalone state schema whose
204
+ class identity is useful, declare a named `Schema.TaggedClass`. Do not bury
205
+ one-off tagged schema declarations inside `Machine.states`.
206
+
181
207
  Put data on the narrowest state where it is valid. If sibling phases share
182
208
  data, put it on their compound parent.
183
209
 
@@ -209,7 +235,7 @@ const definition = Machine.make({
209
235
  events: CommandEvent,
210
236
  internalEvents: InternalEvent,
211
237
  emittedEvents: Emissions,
212
- initial: (to) => to.Idle().resolve(({ target }) => target.from())
238
+ initial: (to) => to.Idle()
213
239
  })
214
240
  ```
215
241
 
@@ -321,7 +347,7 @@ const child = Machine.make({
321
347
  states: ChildStates.states,
322
348
  events: ChildEvents,
323
349
  parent: Machine.parent(ParentEvents),
324
- initial: (to) => to.Working().resolve(({ target }) => target.from())
350
+ initial: (to) => to.Working()
325
351
  }).handle({
326
352
  Working: {
327
353
  on: {
@@ -372,9 +398,13 @@ paths. `parent` always means the owning machine target.
372
398
  | `target.history` | Restoring a declared history node | The remembered configuration or its typed default |
373
399
 
374
400
  Every required transition handler selects a target from its inline `to`
375
- builder. A bare selection uses the target schema's default construction; call
376
- `.resolve(...)` when construction depends on handler context. An absent handler
377
- ignores the trigger; `to.none` handles
401
+ builder. Return a bare selection such as `to.full.Idle()` when the selected
402
+ builder supports zero-argument construction; the machine applies the same
403
+ default construction as `target.from()`. This includes empty schemas and
404
+ schemas whose constructor fields are all optional or defaulted. TypeScript
405
+ rejects the bare form when state data or nested configuration is required.
406
+ Call `.resolve(...)` when construction depends on handler context or the
407
+ transition needs to enqueue commands. An absent handler ignores the trigger; `to.none` handles
378
408
  it and retains queued commands, raised events, and emitted events without
379
409
  selecting a destination. Concrete destinations stay narrowed inside their
380
410
  resolver, and `to.branches({...})` gives the resolver only the declared named
@@ -594,6 +624,14 @@ const decoded = yield * Machine.decodeSnapshot(machine, encoded)
594
624
  const ref = yield * Machine.resume(machine, decoded)
595
625
  ```
596
626
 
627
+ Decoded snapshots are local runtime values and may contain class instances or
628
+ other process-local data. `encodeSnapshot` is the persistence boundary: it uses
629
+ each declared schema's canonical JSON codec and succeeds only when every active
630
+ state value, completion output, and history value is JSON. Rich values such as
631
+ dates and bigints use their schema-defined JSON representation; cyclic or
632
+ non-JSON values fail with `MachineSchemaEncodeError` instead of escaping to a
633
+ later `JSON.stringify` crash.
634
+
597
635
  Resumption restores logical state, values, completion, and history metadata.
598
636
  It creates a fresh runtime: active invokes restart, timers restart at their
599
637
  full duration, and prior fibers, subscriptions, queues, and child runtimes are
@@ -646,11 +684,11 @@ Each ESM entrypoint is independent and tree-shakeable.
646
684
  Every package directly under [`examples/`](./examples) has its own lockfile and
647
685
  `check` script.
648
686
 
649
- | Example | What it demonstrates |
650
- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
651
- | [Playground](./examples/playground) | Five focused React examples: atomic turnstile commands, state-scoped traffic-light timers, microwave safety across parallel regions, a service-backed media player, and a worker-hosted machine synchronized across tabs |
652
- | [Pokémon](./examples/pokemon) | Compound and parallel states, invoked child machines, typed emissions, Atom reactivity, and a live Effect service |
653
- | [Platformer](./examples/platformer) | Nested parallel statecharts, typed deep history, raised events, state-scoped timers, deterministic model tests, and a playable SVG adapter |
687
+ | Example | What it demonstrates |
688
+ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
689
+ | [Playground](./examples/playground) | Five focused React examples: atomic turnstile commands, state-scoped traffic-light timers, hierarchical microwave safety, a resource-owned media player, and a worker-hosted machine synchronized across tabs |
690
+ | [Pokémon](./examples/pokemon) | Compound workflow states, invoked child machines, typed emissions, Atom reactivity, and a live Effect service |
691
+ | [Platformer](./examples/platformer) | Nested parallel statecharts, typed deep history, raised events, state-scoped timers, deterministic model tests, and a playable SVG adapter |
654
692
 
655
693
  The playground is the shortest path from one concept to working code. The
656
694
  standalone examples show larger composition and ownership boundaries.