@typeonce/effect-machine 0.3.0 → 0.4.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 (149) hide show
  1. package/README.md +203 -16
  2. package/dist/Machine.d.ts +784 -289
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +158 -528
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/activities.d.ts +57 -0
  7. package/dist/internal/machine/activities.d.ts.map +1 -0
  8. package/dist/internal/machine/activities.js +49 -0
  9. package/dist/internal/machine/activities.js.map +1 -0
  10. package/dist/internal/machine/atom.d.ts +69 -0
  11. package/dist/internal/machine/atom.d.ts.map +1 -0
  12. package/dist/{AtomMachine.js → internal/machine/atom.js} +19 -94
  13. package/dist/internal/machine/atom.js.map +1 -0
  14. package/dist/internal/machine/cluster.d.ts +63 -0
  15. package/dist/internal/machine/cluster.d.ts.map +1 -0
  16. package/dist/{ClusterMachine.js → internal/machine/cluster.js} +14 -118
  17. package/dist/internal/machine/cluster.js.map +1 -0
  18. package/dist/internal/machine/command.d.ts +16 -0
  19. package/dist/internal/machine/command.d.ts.map +1 -0
  20. package/dist/internal/machine/command.js +45 -0
  21. package/dist/internal/machine/command.js.map +1 -0
  22. package/dist/internal/machine/commandRuntime.d.ts +13 -0
  23. package/dist/internal/machine/commandRuntime.d.ts.map +1 -0
  24. package/dist/internal/machine/commandRuntime.js +16 -0
  25. package/dist/internal/machine/commandRuntime.js.map +1 -0
  26. package/dist/internal/{machineModel.d.ts → machine/configuration.d.ts} +11 -60
  27. package/dist/internal/machine/configuration.d.ts.map +1 -0
  28. package/dist/internal/{machineModel.js → machine/configuration.js} +249 -544
  29. package/dist/internal/machine/configuration.js.map +1 -0
  30. package/dist/internal/{machineErrors.d.ts → machine/errors.d.ts} +1 -1
  31. package/dist/internal/machine/errors.d.ts.map +1 -0
  32. package/dist/internal/{machineErrors.js → machine/errors.js} +1 -1
  33. package/dist/internal/machine/errors.js.map +1 -0
  34. package/dist/internal/machine/executionPlan.d.ts +49 -0
  35. package/dist/internal/machine/executionPlan.d.ts.map +1 -0
  36. package/dist/internal/machine/executionPlan.js +667 -0
  37. package/dist/internal/machine/executionPlan.js.map +1 -0
  38. package/dist/internal/machine/invocation.d.ts +23 -0
  39. package/dist/internal/machine/invocation.d.ts.map +1 -0
  40. package/dist/internal/machine/invocation.js +77 -0
  41. package/dist/internal/machine/invocation.js.map +1 -0
  42. package/dist/internal/machine/machine.d.ts +183 -0
  43. package/dist/internal/machine/machine.d.ts.map +1 -0
  44. package/dist/internal/machine/machine.js +552 -0
  45. package/dist/internal/machine/machine.js.map +1 -0
  46. package/dist/internal/machine/planner.d.ts +182 -0
  47. package/dist/internal/machine/planner.d.ts.map +1 -0
  48. package/dist/internal/machine/planner.js +1082 -0
  49. package/dist/internal/machine/planner.js.map +1 -0
  50. package/dist/internal/{machineProcess.d.ts → machine/process.d.ts} +10 -4
  51. package/dist/internal/machine/process.d.ts.map +1 -0
  52. package/dist/internal/machine/process.js +446 -0
  53. package/dist/internal/machine/process.js.map +1 -0
  54. package/dist/internal/machine/protocol.d.ts +34 -0
  55. package/dist/internal/machine/protocol.d.ts.map +1 -0
  56. package/dist/internal/machine/protocol.js +182 -0
  57. package/dist/internal/machine/protocol.js.map +1 -0
  58. package/dist/internal/machine/readiness.d.ts +4 -0
  59. package/dist/internal/machine/readiness.d.ts.map +1 -0
  60. package/dist/internal/machine/readiness.js +2 -0
  61. package/dist/internal/machine/readiness.js.map +1 -0
  62. package/dist/internal/{machineRuntime.d.ts → machine/runtime.d.ts} +104 -2
  63. package/dist/internal/machine/runtime.d.ts.map +1 -0
  64. package/dist/internal/machine/runtime.js +1381 -0
  65. package/dist/internal/machine/runtime.js.map +1 -0
  66. package/dist/internal/machine/serialization.d.ts +14 -0
  67. package/dist/internal/machine/serialization.d.ts.map +1 -0
  68. package/dist/internal/machine/serialization.js +338 -0
  69. package/dist/internal/machine/serialization.js.map +1 -0
  70. package/dist/internal/machine/stateDefinition.d.ts +15 -0
  71. package/dist/internal/machine/stateDefinition.d.ts.map +1 -0
  72. package/dist/internal/machine/stateDefinition.js +210 -0
  73. package/dist/internal/machine/stateDefinition.js.map +1 -0
  74. package/dist/internal/machine/symbols.d.ts +3 -0
  75. package/dist/internal/machine/symbols.d.ts.map +1 -0
  76. package/dist/internal/machine/symbols.js +3 -0
  77. package/dist/internal/machine/symbols.js.map +1 -0
  78. package/dist/internal/machine/topology.d.ts +98 -0
  79. package/dist/internal/machine/topology.d.ts.map +1 -0
  80. package/dist/internal/machine/topology.js +347 -0
  81. package/dist/internal/machine/topology.js.map +1 -0
  82. package/dist/internal/testing/machine/arbitrary.d.ts +36 -0
  83. package/dist/internal/testing/machine/arbitrary.d.ts.map +1 -0
  84. package/dist/internal/testing/machine/arbitrary.js +68 -0
  85. package/dist/internal/testing/machine/arbitrary.js.map +1 -0
  86. package/dist/internal/testing/machine/finiteModel.d.ts +322 -0
  87. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -0
  88. package/dist/internal/testing/machine/finiteModel.js +976 -0
  89. package/dist/internal/testing/machine/finiteModel.js.map +1 -0
  90. package/dist/internal/testing/machine/referenceModel.d.ts +203 -0
  91. package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -0
  92. package/dist/internal/testing/machine/referenceModel.js +1012 -0
  93. package/dist/internal/testing/machine/referenceModel.js.map +1 -0
  94. package/dist/internal/testing/machine/runtime.d.ts +328 -0
  95. package/dist/internal/testing/machine/runtime.d.ts.map +1 -0
  96. package/dist/internal/testing/machine/runtime.js +437 -0
  97. package/dist/internal/testing/machine/runtime.js.map +1 -0
  98. package/dist/internal/testing/machine/verification.d.ts +33 -0
  99. package/dist/internal/testing/machine/verification.d.ts.map +1 -0
  100. package/dist/internal/testing/machine/verification.js +1446 -0
  101. package/dist/internal/testing/machine/verification.js.map +1 -0
  102. package/dist/testing/MachineTest.d.ts +606 -0
  103. package/dist/testing/MachineTest.d.ts.map +1 -0
  104. package/dist/testing/MachineTest.js +102 -0
  105. package/dist/testing/MachineTest.js.map +1 -0
  106. package/dist/testing/index.d.ts +7 -0
  107. package/dist/testing/index.d.ts.map +1 -0
  108. package/dist/testing/index.js +7 -0
  109. package/dist/testing/index.js.map +1 -0
  110. package/dist/{ClusterMachine.d.ts → unstable/cluster/ClusterMachine.d.ts} +19 -27
  111. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -0
  112. package/dist/unstable/cluster/ClusterMachine.js +126 -0
  113. package/dist/unstable/cluster/ClusterMachine.js.map +1 -0
  114. package/dist/{cluster.js → unstable/cluster/index.d.ts} +1 -1
  115. package/dist/unstable/cluster/index.d.ts.map +1 -0
  116. package/dist/{cluster.d.ts → unstable/cluster/index.js} +1 -1
  117. package/dist/unstable/cluster/index.js.map +1 -0
  118. package/dist/{AtomMachine.d.ts → unstable/reactivity/AtomMachine.d.ts} +35 -20
  119. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -0
  120. package/dist/unstable/reactivity/AtomMachine.js +113 -0
  121. package/dist/unstable/reactivity/AtomMachine.js.map +1 -0
  122. package/dist/{reactivity.js → unstable/reactivity/index.d.ts} +1 -1
  123. package/dist/unstable/reactivity/index.d.ts.map +1 -0
  124. package/dist/{reactivity.d.ts → unstable/reactivity/index.js} +1 -1
  125. package/dist/unstable/reactivity/index.js.map +1 -0
  126. package/docs/agent-guide.md +169 -31
  127. package/package.json +19 -10
  128. package/dist/AtomMachine.d.ts.map +0 -1
  129. package/dist/AtomMachine.js.map +0 -1
  130. package/dist/ClusterMachine.d.ts.map +0 -1
  131. package/dist/ClusterMachine.js.map +0 -1
  132. package/dist/cluster.d.ts.map +0 -1
  133. package/dist/cluster.js.map +0 -1
  134. package/dist/internal/machineErrors.d.ts.map +0 -1
  135. package/dist/internal/machineErrors.js.map +0 -1
  136. package/dist/internal/machineModel.d.ts.map +0 -1
  137. package/dist/internal/machineModel.js.map +0 -1
  138. package/dist/internal/machinePlanner.d.ts +0 -74
  139. package/dist/internal/machinePlanner.d.ts.map +0 -1
  140. package/dist/internal/machinePlanner.js +0 -797
  141. package/dist/internal/machinePlanner.js.map +0 -1
  142. package/dist/internal/machineProcess.d.ts.map +0 -1
  143. package/dist/internal/machineProcess.js +0 -181
  144. package/dist/internal/machineProcess.js.map +0 -1
  145. package/dist/internal/machineRuntime.d.ts.map +0 -1
  146. package/dist/internal/machineRuntime.js +0 -365
  147. package/dist/internal/machineRuntime.js.map +0 -1
  148. package/dist/reactivity.d.ts.map +0 -1
  149. package/dist/reactivity.js.map +0 -1
package/README.md CHANGED
@@ -8,11 +8,11 @@ Schema-first state machines and statecharts for Effect.
8
8
  ## Installation
9
9
 
10
10
  ```sh
11
- pnpm add @typeonce/effect-machine effect@4.0.0-beta.102
11
+ pnpm add @typeonce/effect-machine effect@4.0.0-beta.107
12
12
  ```
13
13
 
14
14
  `effect` is an exact peer dependency, not a bundled runtime dependency.
15
- Consumers must install `effect@4.0.0-beta.102`. Upgrading this package may
15
+ Consumers must install `effect@4.0.0-beta.107`. Upgrading this package may
16
16
  require upgrading Effect in lockstep; do not override the peer to another beta.
17
17
 
18
18
  ## Entrypoints
@@ -66,6 +66,10 @@ const Counter = Machine.make({
66
66
  })
67
67
  ```
68
68
 
69
+ Handler objects mirror the state definition recursively. `effect-machine` does
70
+ not impose a fixed handler-tree depth; inference continues until TypeScript's
71
+ normal, shape-dependent compiler limits.
72
+
69
73
  `initial` is always a function. For a machine with an input schema, the
70
74
  initializer receives the decoded input.
71
75
 
@@ -126,6 +130,27 @@ const machine = Machine.make({
126
130
  })
127
131
  ```
128
132
 
133
+ Construct reusable events with `Machine.event` when the schema is owned by the
134
+ machine protocol:
135
+
136
+ ```ts
137
+ const save = Machine.event(machine, Command.cases.Save)
138
+ yield * ref.send(save)
139
+ ```
140
+
141
+ The schema constructor runs once and the decoded value is trusted by that
142
+ machine and definitions derived from it with `handle`. This avoids decoding a
143
+ known event again on every delivery. A configured `Schema.TaggedUnion` can use
144
+ either the union schema itself or one of its `cases`. Treat the returned event
145
+ as immutable; sending it to an unrelated machine goes through that machine's
146
+ normal decoder.
147
+
148
+ Ordinary values remain valid and are decoded at every boundary:
149
+
150
+ ```ts
151
+ yield * ref.send({ _tag: "Save" })
152
+ ```
153
+
129
154
  Handlers and machine logic see the complete union. Local public APIs such as
130
155
  `MachineRef.send`, `machineAtom.send`, and `Machine.plan` expose only `events`
131
156
  in TypeScript. The local planner and runtime still share the complete event
@@ -147,7 +172,7 @@ masquerade as an internal result.
147
172
 
148
173
  ## Statechart structure
149
174
 
150
- `Machine.defineStates` accepts atomic, compound, parallel, final, and history
175
+ `Machine.defineStates` accepts atomic, compound, parallel, final, choice, and history
151
176
  nodes:
152
177
 
153
178
  ```ts
@@ -205,6 +230,65 @@ Put data on the narrowest state where it is valid. If several sibling phases
205
230
  share data, prefer storing it on their compound parent instead of copying it
206
231
  into every child state.
207
232
 
233
+ ### Choice states
234
+
235
+ A choice is a transient, targetable decision point. Declare it with only
236
+ `type: "choice"`; it has no schema, value, children, lifecycle actions, invoke,
237
+ or event handlers. A choice is therefore absent from `StateIdentifier`, stable
238
+ snapshots, configurations, and encoded snapshots.
239
+
240
+ ```ts
241
+ const States = Machine.defineStates({
242
+ Flow: {
243
+ schema: State.cases.Flow,
244
+ initial: "Routing",
245
+ states: {
246
+ Routing: { type: "choice" },
247
+ Approved: State.cases.Approved,
248
+ Rejected: State.cases.Rejected
249
+ }
250
+ }
251
+ })
252
+
253
+ const machine = Machine.make({
254
+ states: States.states,
255
+ events: [Event],
256
+ initial: () =>
257
+ States.initial.Flow(
258
+ State.cases.Flow.make({ score: 80 }),
259
+ (flow) => flow.Routing()
260
+ )
261
+ }).handle({
262
+ Flow: {
263
+ states: {
264
+ Routing: {
265
+ choice: {
266
+ targets: ["Flow.Approved", "Flow.Rejected"],
267
+ transition: ({ parent, target }) =>
268
+ parent.score >= 70
269
+ ? target.local.Approved(State.cases.Approved.make({}))
270
+ : target.local.Rejected(State.cases.Rejected.make({}))
271
+ }
272
+ }
273
+ }
274
+ }
275
+ })
276
+ ```
277
+
278
+ The resolver is ordinary TypeScript or an `Effect`. It receives the triggering
279
+ lifecycle event, typed parent values, target builders, and the normal planning
280
+ capabilities, but no `state` because the choice itself has no state value. Its
281
+ declared `targets` are both a compile-time bound and inspectable graph edges.
282
+ The resolver must return one of them; missing, malformed, or undeclared targets
283
+ fail planning. Choice implementations are required before execution APIs are
284
+ available.
285
+
286
+ Initial, event, completion, always, history-default, and other choice
287
+ transitions settle in the same macrostep. Chained choices use the normal
288
+ infinite-transition limit. Choice nodes never run entry or exit actions and
289
+ never become active while their resolution remains visible in traces and
290
+ coverage as a `choice` transition trigger.
291
+
208
292
  ### History states
209
293
 
210
294
  A history pseudo-state remembers the last active configuration of its parent.
@@ -254,6 +338,38 @@ machine.handle({
254
338
  })
255
339
  ```
256
340
 
341
+ A history default is source-independent. It must construct a complete root
342
+ configuration containing the history owner, including every inactive ancestor
343
+ above a nested owner and every required region of a parallel ancestor. For a
344
+ top-level owner, its owner snapshot is already a complete root snapshot.
345
+
346
+ For example, a history node owned by `App.Workspace` can be targeted from an
347
+ unrelated `Closed` root and supplies the complete `App` configuration on first
348
+ use:
349
+
350
+ ```ts
351
+ Workspace: {
352
+ history: {
353
+ resume: {
354
+ default: ({ target }) =>
355
+ target.App(
356
+ State.cases.App.make({ workspaceId: "default" }),
357
+ (app) =>
358
+ app.Workspace(
359
+ State.cases.Workspace.make({}),
360
+ (workspace) =>
361
+ workspace.Editing(State.cases.Editing.make({}))
362
+ )
363
+ )
364
+ }
365
+ }
366
+ }
367
+ ```
368
+
369
+ The containing branch is enforced statically: unrelated roots, sibling
370
+ compound branches that omit the owner, owner-only nested snapshots, and
371
+ incomplete parallel configurations are rejected.
372
+
257
373
  Deep history restores every remembered descendant value. Shallow history
258
374
  restores the parent and direct-child values, then follows normal initial paths.
259
375
  Only compound or parallel states that shallow restoration can enter implicitly
@@ -314,25 +430,58 @@ Refresh: {
314
430
  keyed by full dotted paths, such as `parents["Form.Editing"]`; `context.parent`
315
431
  provides the immediate parent directly and is `undefined` at a root state.
316
432
 
317
- ## Planning Effects and staged actions
433
+ Event, eventless, and completion transition contexts also expose `snapshot`, a
434
+ read-only view of the complete logical configuration captured at the beginning
435
+ of that transition microstep. This lets one parallel region inspect a sibling
436
+ without copying active-state facts into parent values:
437
+
438
+ ```ts
439
+ BufferReady: ;
440
+ ;(({ snapshot, target }) =>
441
+ States.matches(snapshot, "Player.Network.Online")
442
+ ? target.local.Playing(State.cases.Playing.make({}))
443
+ : undefined)
444
+ ```
445
+
446
+ All non-conflicting handlers selected together observe the same captured
447
+ snapshot. Handlers are synchronous and cannot read mutable live runtime state
448
+ later. `snapshot` is intentionally absent from entry, exit,
449
+ invoke, and choice contexts. In particular, startup and chained choices may run
450
+ before a complete stable snapshot containing their pseudo-source exists.
318
451
 
319
- An Effect returned by a transition handler is part of planning. Use it to read
320
- services, choose a target, raise an event, or emit an event. Wrap external side
321
- effects in `Machine.action`; actions are staged during planning and run by the
322
- managed runtime before it publishes the next state.
452
+ Effect Schema annotations are the metadata source for active states. Annotate
453
+ the schema itself; `Machine.stateNodes` exposes the resolved annotation map:
454
+
455
+ ```ts
456
+ const Saving = State.cases.Saving.annotate({
457
+ title: "Saving document",
458
+ description: "Persisting local changes to the server"
459
+ })
460
+ ```
461
+
462
+ Schema-less choice and history nodes accept only descriptive `title`,
463
+ `description`, and `documentation` annotations. Titles may be used as display
464
+ labels, but structural paths remain the only identity and targeting mechanism.
465
+
466
+ ## Synchronous transitions and actor commands
467
+
468
+ Transition, entry, exit, choice, initial, and history callbacks are synchronous.
469
+ They select state and may enqueue only explicit statechart or actor operations:
470
+ raise an internal event, emit to the parent, send to an invoked child, or stop a
471
+ child. Arbitrary Effects are not accepted at this boundary.
323
472
 
324
473
  ```ts
325
474
  const handlers = {
326
- Save: ({ target }) => Machine.action(writeAuditLog, target.local.Saving.from())
475
+ Save: ({ target }, enqueue) => {
476
+ enqueue.emit(new SaveRequested({}))
477
+ return target.local.Saving.from()
478
+ }
327
479
  }
328
480
  ```
329
481
 
330
- The one-argument form returns `void` after staging. The two-argument form
331
- returns its second argument, which avoids a generator when an action and the
332
- next target are the whole transition.
333
-
334
- If an action fails, the runtime keeps the previously published state and
335
- suppresses emissions from that plan.
482
+ Use `Machine.invokeEffect`, `Machine.invoke`, or an invoked child machine for
483
+ asynchronous work. Their results return to the parent as typed events, keeping
484
+ the transition core deterministic and synchronous.
336
485
 
337
486
  `Machine.plan` and `Machine.planInitial` return a `done` discriminator. When
338
487
  `done` is `true`, `output` is the schema-derived structural terminal union;
@@ -407,6 +556,14 @@ Descriptors are matched by id and machine identity, so independently created
407
556
  descriptors for the same pair address the same child without a global cache.
408
557
  Exporting one descriptor remains the clearest module boundary.
409
558
 
559
+ `Machine.activityDefinitions(machine)` inspects state-owned work without
560
+ executing it. Static descriptors report their source path, lifecycle id, and
561
+ kind. Timers also report normalized duration and emitted event tag;
562
+ `invokeEffect` mappings are described as dynamic; invoked machines expose only
563
+ safe child identity. A function-valued `invoke` factory is reported as dynamic
564
+ and is never evaluated during inspection. The result is serializable and does
565
+ not contain Effects, closures, services, or child runtimes.
566
+
410
567
  ## Reactivity
411
568
 
412
569
  `AtomMachine.make` creates a lazy bridge backed by one running machine per
@@ -465,8 +622,38 @@ contain the machine definition, machine version, services, subscriptions, or
465
622
  running child processes. Store machine identity and migration/version metadata
466
623
  alongside it.
467
624
 
625
+ Resume a decoded logical snapshot explicitly:
626
+
627
+ ```ts
628
+ const encoded = yield * Machine.encodeSnapshot(machine, snapshot)
629
+ const decoded = yield * Machine.decodeSnapshot(machine, encoded)
630
+ const ref = yield * Machine.resume(machine, decoded)
631
+ ```
632
+
633
+ `resume` does not call `initial`, require machine input, or replay entry,
634
+ transition, completion, eventless, raised-event, or emitted-event work that
635
+ produced the snapshot. The decoded snapshot is the first published logical
636
+ state. A final snapshot immediately yields a completed ref with its output.
637
+
638
+ Resumption creates a fresh runtime. Invokes owned by active states start once in
639
+ normal ancestor/document order and receive `Machine.InitialEvent` as their
640
+ lifecycle event. `invokeEffect` runs again, invoked machines start from their
641
+ own initial state, and `Machine.after` timers restart from their full declared
642
+ duration. Spawned children, queued events, subscriptions, fibers, scopes,
643
+ elapsed timer time, child snapshots, and prior `RuntimeSnapshot` status/errors
644
+ are not restored. Completion and history metadata remain logical state and are
645
+ not replayed. A changed machine definition does not cause `resume` itself to
646
+ evaluate newly enabled `always` or `onDone` transitions.
647
+
648
+ Reactive applications use `AtomMachine.resume(machine, decoded)` for a
649
+ service-free machine or `AtomMachine.bind(runtime).resume(machine, decoded)`
650
+ for a service-backed machine. These bridges have the same lazy one-runtime-per-
651
+ registry ownership and disposal behavior as `AtomMachine.make`.
652
+
468
653
  `ClusterMachine` provides a separate persisted entity adapter. Its process-local
469
- restrictions and delivery guarantees are documented on that API.
654
+ restrictions, checkpoint planning, and delivery guarantees are documented on
655
+ that API. `Machine.resume` is logical resumption, not durable process or cluster
656
+ restoration.
470
657
 
471
658
  ## Current limits
472
659