@typeonce/effect-machine 0.12.0 → 0.14.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 (103) hide show
  1. package/README.md +130 -30
  2. package/dist/Machine.d.ts +722 -262
  3. package/dist/Machine.d.ts.map +1 -1
  4. package/dist/Machine.js +130 -62
  5. package/dist/Machine.js.map +1 -1
  6. package/dist/internal/machine/atom.d.ts +1 -0
  7. package/dist/internal/machine/atom.d.ts.map +1 -1
  8. package/dist/internal/machine/atom.js +23 -0
  9. package/dist/internal/machine/atom.js.map +1 -1
  10. package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
  11. package/dist/internal/machine/commandRuntime.js +2 -5
  12. package/dist/internal/machine/commandRuntime.js.map +1 -1
  13. package/dist/internal/machine/configuration.d.ts +5 -0
  14. package/dist/internal/machine/configuration.d.ts.map +1 -1
  15. package/dist/internal/machine/configuration.js +6 -2
  16. package/dist/internal/machine/configuration.js.map +1 -1
  17. package/dist/internal/machine/executionPlan.d.ts +1 -0
  18. package/dist/internal/machine/executionPlan.d.ts.map +1 -1
  19. package/dist/internal/machine/executionPlan.js +57 -24
  20. package/dist/internal/machine/executionPlan.js.map +1 -1
  21. package/dist/internal/machine/initialization.d.ts +5 -0
  22. package/dist/internal/machine/initialization.d.ts.map +1 -0
  23. package/dist/internal/machine/initialization.js +61 -0
  24. package/dist/internal/machine/initialization.js.map +1 -0
  25. package/dist/internal/machine/inspectionRuntime.d.ts +25 -0
  26. package/dist/internal/machine/inspectionRuntime.d.ts.map +1 -0
  27. package/dist/internal/machine/inspectionRuntime.js +79 -0
  28. package/dist/internal/machine/inspectionRuntime.js.map +1 -0
  29. package/dist/internal/machine/invocation.d.ts +2 -1
  30. package/dist/internal/machine/invocation.d.ts.map +1 -1
  31. package/dist/internal/machine/invocation.js +17 -10
  32. package/dist/internal/machine/invocation.js.map +1 -1
  33. package/dist/internal/machine/machine.d.ts +4 -3
  34. package/dist/internal/machine/machine.d.ts.map +1 -1
  35. package/dist/internal/machine/machine.js +327 -32
  36. package/dist/internal/machine/machine.js.map +1 -1
  37. package/dist/internal/machine/planner.d.ts +28 -0
  38. package/dist/internal/machine/planner.d.ts.map +1 -1
  39. package/dist/internal/machine/planner.js +132 -51
  40. package/dist/internal/machine/planner.js.map +1 -1
  41. package/dist/internal/machine/process.d.ts.map +1 -1
  42. package/dist/internal/machine/process.js +4 -2
  43. package/dist/internal/machine/process.js.map +1 -1
  44. package/dist/internal/machine/runtime.d.ts +27 -3
  45. package/dist/internal/machine/runtime.d.ts.map +1 -1
  46. package/dist/internal/machine/runtime.js +440 -64
  47. package/dist/internal/machine/runtime.js.map +1 -1
  48. package/dist/internal/machine/symbols.d.ts +2 -0
  49. package/dist/internal/machine/symbols.d.ts.map +1 -1
  50. package/dist/internal/machine/symbols.js +2 -0
  51. package/dist/internal/machine/symbols.js.map +1 -1
  52. package/dist/internal/machine/topology.d.ts +25 -0
  53. package/dist/internal/machine/topology.d.ts.map +1 -1
  54. package/dist/internal/machine/topology.js +26 -10
  55. package/dist/internal/machine/topology.js.map +1 -1
  56. package/dist/internal/testing/machine/exploration.d.ts.map +1 -1
  57. package/dist/internal/testing/machine/exploration.js +11 -2
  58. package/dist/internal/testing/machine/exploration.js.map +1 -1
  59. package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
  60. package/dist/internal/testing/machine/finiteModel.js +75 -69
  61. package/dist/internal/testing/machine/finiteModel.js.map +1 -1
  62. package/dist/internal/testing/machine/transitionCoverage.d.ts +20 -0
  63. package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -0
  64. package/dist/internal/testing/machine/transitionCoverage.js +80 -0
  65. package/dist/internal/testing/machine/transitionCoverage.js.map +1 -0
  66. package/dist/internal/testing/machine/verification.d.ts.map +1 -1
  67. package/dist/internal/testing/machine/verification.js +171 -33
  68. package/dist/internal/testing/machine/verification.js.map +1 -1
  69. package/dist/testing/MachineTest.d.ts +97 -22
  70. package/dist/testing/MachineTest.d.ts.map +1 -1
  71. package/dist/testing/MachineTest.js +58 -16
  72. package/dist/testing/MachineTest.js.map +1 -1
  73. package/dist/unstable/cluster/ClusterMachine.d.ts +4 -1
  74. package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
  75. package/dist/unstable/cluster/ClusterMachine.js +4 -1
  76. package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
  77. package/dist/unstable/reactivity/AtomMachine.d.ts +21 -3
  78. package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
  79. package/dist/unstable/reactivity/AtomMachine.js +21 -3
  80. package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
  81. package/docs/agent-guide.md +240 -65
  82. package/package.json +5 -5
  83. package/src/Machine.ts +1240 -495
  84. package/src/internal/machine/atom.ts +26 -0
  85. package/src/internal/machine/commandRuntime.ts +2 -9
  86. package/src/internal/machine/configuration.ts +28 -3
  87. package/src/internal/machine/executionPlan.ts +72 -24
  88. package/src/internal/machine/initialization.ts +73 -0
  89. package/src/internal/machine/inspectionRuntime.ts +102 -0
  90. package/src/internal/machine/invocation.ts +37 -12
  91. package/src/internal/machine/machine.ts +438 -39
  92. package/src/internal/machine/planner.ts +180 -55
  93. package/src/internal/machine/process.ts +4 -2
  94. package/src/internal/machine/runtime.ts +661 -89
  95. package/src/internal/machine/symbols.ts +3 -0
  96. package/src/internal/machine/topology.ts +65 -12
  97. package/src/internal/testing/machine/exploration.ts +10 -2
  98. package/src/internal/testing/machine/finiteModel.ts +114 -80
  99. package/src/internal/testing/machine/transitionCoverage.ts +116 -0
  100. package/src/internal/testing/machine/verification.ts +216 -58
  101. package/src/testing/MachineTest.ts +118 -28
  102. package/src/unstable/cluster/ClusterMachine.ts +4 -1
  103. package/src/unstable/reactivity/AtomMachine.ts +24 -3
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.108
37
+ pnpm add @typeonce/effect-machine effect@4.0.0-rc.109
38
38
  ```
39
39
 
40
40
  `effect` is an exact peer dependency. Install the version above and upgrade it
@@ -66,19 +66,31 @@ const CounterDefinition = Machine.make({
66
66
  id: "Counter",
67
67
  states: States.states,
68
68
  events: CounterEvent,
69
- initial: () => States.initial.Idle.from()
69
+ initial: {
70
+ target: (to) => to.Idle(),
71
+ resolve: ({ target }) => target.from()
72
+ }
70
73
  })
71
74
 
72
75
  const Counter = CounterDefinition.handle({
73
76
  Idle: {
74
77
  on: {
75
- Start: ({ target }) => target.full.Running.from({ count: 0 })
78
+ Start: Machine.transition({
79
+ target: (to) => to.full.Running(),
80
+ resolve: ({ target }) => target.from({ count: 0 })
81
+ })
76
82
  }
77
83
  },
78
84
  Running: {
79
85
  on: {
80
- Increment: ({ state, target }) => target.full.Running.from({ count: state.count + 1 }),
81
- Stop: ({ target }) => target.full.Idle.from()
86
+ Increment: Machine.transition({
87
+ target: (to) => to.full.Running(),
88
+ resolve: ({ state, target }) => target.from({ count: state.count + 1 })
89
+ }),
90
+ Stop: Machine.transition({
91
+ target: (to) => to.full.Idle(),
92
+ resolve: ({ target }) => target.from()
93
+ })
82
94
  }
83
95
  }
84
96
  })
@@ -111,8 +123,7 @@ Use this order to preserve inference and keep boundaries explicit:
111
123
  Use `.from(...)` when constructing a new state from fields:
112
124
 
113
125
  ```ts
114
- target.local.Saving.from({ draft: event.draft })
115
- States.initial.Form.from({ draft: "" }, (form) => form.Editing.from())
126
+ target.from({ draft: event.draft })
116
127
  ```
117
128
 
118
129
  The machine runs these inputs through the state schema while planning. Schema
@@ -124,10 +135,12 @@ When sibling states share fields, remove the source discriminator and pass the
124
135
  remaining fields through the target schema:
125
136
 
126
137
  ```ts
127
- Submit: ;
128
- ;(({ state, target }) => {
129
- const { _tag: _, ...fields } = state
130
- return target.local.Saving.from({ ...fields, attempt: 1 })
138
+ Submit: Machine.transition({
139
+ target: (to) => to.local.Saving(),
140
+ resolve: ({ state, target }) => {
141
+ const { _tag: _, ...fields } = state
142
+ return target.from({ ...fields, attempt: 1 })
143
+ }
131
144
  })
132
145
  ```
133
146
 
@@ -144,7 +157,10 @@ const States = Machine.defineStates({
144
157
  }
145
158
  })
146
159
 
147
- States.initial.Form.from((form) => form.Editing.from())
160
+ initial: {
161
+ target: (to) => to.Form.initial(),
162
+ resolve: ({ target }) => target((form) => form.Editing.from())
163
+ }
148
164
  ```
149
165
 
150
166
  Schema-less states remain active, targetable, matchable, and visible through
@@ -181,7 +197,10 @@ const definition = Machine.make({
181
197
  events: CommandEvent,
182
198
  internalEvents: InternalEvent,
183
199
  emittedEvents: Emissions,
184
- initial: () => States.initial.Idle.from()
200
+ initial: {
201
+ target: (to) => to.Idle(),
202
+ resolve: ({ target }) => target.from()
203
+ }
185
204
  })
186
205
  ```
187
206
 
@@ -227,6 +246,56 @@ const ref = yield * prepared.start
227
246
  not observe startup emissions. Preparation does not retain or replay an
228
247
  emission: the observer is simply subscribed before initialization begins.
229
248
 
249
+ ### Inspect a live machine tree
250
+
251
+ `Machine.prepare(machine).inspection` is the operational counterpart to the
252
+ domain-facing `changes` and `emissions` streams. It observes the prepared root
253
+ and every locally owned child, `Logic` process, Effect, and timer in one total
254
+ publication order:
255
+
256
+ ```ts
257
+ const prepared = yield * Machine.prepare(checkout)
258
+
259
+ yield * prepared.inspection.pipe(
260
+ Stream.runForEach((record) => Console.log(record.sequence, record.subject.id, record._tag)),
261
+ Effect.forkScoped({ startImmediately: true })
262
+ )
263
+
264
+ const checkoutRef = yield * prepared.start
265
+ ```
266
+
267
+ For a handled input, the stream may expose values such as:
268
+
269
+ ```ts
270
+ { _tag: "EventSent", sequence: 2, deliveryId: 0,
271
+ subject: { id: "checkout", sessionId: "machine:0", kind: "Machine" },
272
+ source: undefined, target: { id: "checkout", sessionId: "machine:0" },
273
+ event: CheckoutEvents.Submit(), causedBy: undefined }
274
+
275
+ { _tag: "EventProcessed", sequence: 4, macrostepId: 0,
276
+ deliveryId: 0, handled: true, configurationChanged: true,
277
+ before: { status: "active", state: /* ... */ },
278
+ after: { status: "active", state: /* ... */ }, microsteps: [/* ... */] }
279
+ ```
280
+
281
+ The closed `Machine.Inspection.Event` union also reports creation,
282
+ initialization and startup failure, direct `Logic` state updates, outward
283
+ emissions, Effect/timer activity lifecycles, and termination. Records erase
284
+ unrelated child protocols to `unknown`; application-level observation remains
285
+ typed through each reference's `changes` and `emissions`.
286
+
287
+ The stream is hot, non-replayed, never fails, and completes after the root
288
+ terminates. Subscribe before `prepared.start` to capture initialization. Local
289
+ session ids are unique only inside that prepared ownership tree: `machine:0`
290
+ is the root and later ids identify its descendants. They are intentionally not
291
+ distributed identities. Cluster placement, routing, and correlation continue
292
+ to use Cluster entity, runner, and request identities at the integration
293
+ boundary.
294
+
295
+ `AtomMachine.inspection(machineAtom)` provides the same root-scoped stream and
296
+ starts a fresh atom-backed machine only after its inspection subscription is
297
+ installed.
298
+
230
299
  Invalid event and emission constructions fail the machine with a typed
231
300
  `MachineSchemaDecodeError`; they do not throw from the constructor call.
232
301
 
@@ -243,16 +312,22 @@ const child = Machine.make({
243
312
  states: ChildStates.states,
244
313
  events: ChildEvents,
245
314
  parentEvents: ParentEvents,
246
- initial: () => ChildStates.initial.Working.from()
315
+ initial: {
316
+ target: (to) => to.Working(),
317
+ resolve: ({ target }) => target.from()
318
+ }
247
319
  }).handle({
248
320
  Working: {
249
321
  on: {
250
- Finish: ({ parent, target }, enqueue) => {
251
- if (parent !== undefined) {
252
- enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
322
+ Finish: Machine.transition({
323
+ target: (to) => to.full.Done(),
324
+ resolve: ({ parent, target }, enqueue) => {
325
+ if (parent !== undefined) {
326
+ enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
327
+ }
328
+ return target.from()
253
329
  }
254
- return target.full.Done.from()
255
- }
330
+ })
256
331
  }
257
332
  },
258
333
  Done: {}
@@ -326,9 +401,15 @@ State-scoped work starts on entry and is interrupted on exit:
326
401
  Loading: {
327
402
  invoke: Machine.invoke({
328
403
  id: "save-document",
329
- effect: saveDocument,
330
- onDone: ({ output, target }) => target.full.Saved({ id: output.id }),
331
- onFailure: ({ error, target }) => target.full.Failed({ message: String(error) })
404
+ effect: () => saveDocument,
405
+ onDone: Machine.transition({
406
+ target: (to) => to.full.Saved(),
407
+ resolve: ({ output, target }) => target.from({ id: output.id })
408
+ }),
409
+ onFailure: Machine.transition({
410
+ target: (to) => to.full.Failed(),
411
+ resolve: ({ error, target }) => target.from({ message: String(error) })
412
+ })
332
413
  })
333
414
  }
334
415
 
@@ -336,7 +417,10 @@ Waiting: {
336
417
  invoke: Machine.invoke({
337
418
  id: "save-timeout",
338
419
  after: "3 seconds",
339
- onDone: ({ target }) => target.full.Failed({ message: "Timed out" })
420
+ onDone: Machine.transition({
421
+ target: (to) => to.full.Failed(),
422
+ resolve: ({ target }) => target.from({ message: "Timed out" })
423
+ })
340
424
  })
341
425
  }
342
426
  ```
@@ -351,8 +435,14 @@ for state-dependent Effects:
351
435
  invoke: Machine.invoke({
352
436
  id: "load-document",
353
437
  effect: ({ state }) => loadDocument(state.documentId),
354
- onDone: ({ output, target }) => target.full.Ready({ document: output }),
355
- onFailure: ({ error, target }) => target.full.Failed({ message: error.message })
438
+ onDone: Machine.transition({
439
+ target: (to) => to.full.Ready(),
440
+ resolve: ({ output, target }) => target.from({ document: output })
441
+ }),
442
+ onFailure: Machine.transition({
443
+ target: (to) => to.full.Failed(),
444
+ resolve: ({ error, target }) => target.from({ message: error.message })
445
+ })
356
446
  })
357
447
  ```
358
448
 
@@ -378,8 +468,14 @@ const machine = definition.handle({
378
468
  parent === undefined
379
469
  ? Effect.void
380
470
  : parent.send(ParentEvents.SaveStarted()),
381
- onDone: ({ target }) => target.none(),
382
- onFailure: ({ target }) => target.none()
471
+ onDone: Machine.transition({
472
+ target: (to) => to.none(),
473
+ resolve: () => undefined
474
+ }),
475
+ onFailure: Machine.transition({
476
+ target: (to) => to.none(),
477
+ resolve: () => undefined
478
+ })
383
479
  })
384
480
  }
385
481
  })
@@ -393,8 +489,10 @@ lookup.
393
489
  `onDone` is required for a non-`never` output, and `onFailure` is required for a
394
490
  non-`never` typed error; each handler is omitted when its channel is `never`.
395
491
  Defects, interruption, and source-construction failures terminate the owning
396
- runtime. `effect: Effect.sleep(...)` is valid, but `after` keeps timers explicit
397
- and makes static durations visible through activity inspection.
492
+ runtime. Effect sources are always factories evaluated when their state is
493
+ entered. Use `effect: () => Effect.sleep(...)` for a generic Effect, while
494
+ `after` keeps timers explicit and makes static durations visible through
495
+ activity inspection.
398
496
 
399
497
  ## Reactivity
400
498
 
@@ -449,8 +547,10 @@ encoded snapshot.
449
547
  The testing entrypoint provides complementary layers:
450
548
 
451
549
  - `MachineTest.run` and `verify` inspect pure planner traces;
550
+ - `coverage` reports exact transition-definition and conditional-branch hits;
452
551
  - invariants and generated scenarios check application laws;
453
- - `explore` performs bounded breadth-first state-space exploration;
552
+ - `explore` performs bounded breadth-first state-space exploration and retains
553
+ exact transition-definition and branch coverage for every plan it computes;
454
554
  - `probe` causally acknowledges live runtime commands;
455
555
  - runtime command models cover timers, invokes, bursts, and scheduling.
456
556