@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.
- package/README.md +130 -30
- package/dist/Machine.d.ts +722 -262
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +130 -62
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +1 -0
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +23 -0
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
- package/dist/internal/machine/commandRuntime.js +2 -5
- package/dist/internal/machine/commandRuntime.js.map +1 -1
- package/dist/internal/machine/configuration.d.ts +5 -0
- package/dist/internal/machine/configuration.d.ts.map +1 -1
- package/dist/internal/machine/configuration.js +6 -2
- 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 +57 -24
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/initialization.d.ts +5 -0
- package/dist/internal/machine/initialization.d.ts.map +1 -0
- package/dist/internal/machine/initialization.js +61 -0
- package/dist/internal/machine/initialization.js.map +1 -0
- package/dist/internal/machine/inspectionRuntime.d.ts +25 -0
- package/dist/internal/machine/inspectionRuntime.d.ts.map +1 -0
- package/dist/internal/machine/inspectionRuntime.js +79 -0
- package/dist/internal/machine/inspectionRuntime.js.map +1 -0
- package/dist/internal/machine/invocation.d.ts +2 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +17 -10
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +4 -3
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +327 -32
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +28 -0
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +132 -51
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/process.d.ts.map +1 -1
- package/dist/internal/machine/process.js +4 -2
- package/dist/internal/machine/process.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +27 -3
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +440 -64
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/symbols.d.ts +2 -0
- package/dist/internal/machine/symbols.d.ts.map +1 -1
- package/dist/internal/machine/symbols.js +2 -0
- package/dist/internal/machine/symbols.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +25 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +26 -10
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/exploration.d.ts.map +1 -1
- package/dist/internal/testing/machine/exploration.js +11 -2
- package/dist/internal/testing/machine/exploration.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +75 -69
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.d.ts +20 -0
- package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -0
- package/dist/internal/testing/machine/transitionCoverage.js +80 -0
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -0
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +171 -33
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +97 -22
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +58 -16
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +4 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +4 -1
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +21 -3
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +21 -3
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +240 -65
- package/package.json +5 -5
- package/src/Machine.ts +1240 -495
- package/src/internal/machine/atom.ts +26 -0
- package/src/internal/machine/commandRuntime.ts +2 -9
- package/src/internal/machine/configuration.ts +28 -3
- package/src/internal/machine/executionPlan.ts +72 -24
- package/src/internal/machine/initialization.ts +73 -0
- package/src/internal/machine/inspectionRuntime.ts +102 -0
- package/src/internal/machine/invocation.ts +37 -12
- package/src/internal/machine/machine.ts +438 -39
- package/src/internal/machine/planner.ts +180 -55
- package/src/internal/machine/process.ts +4 -2
- package/src/internal/machine/runtime.ts +661 -89
- package/src/internal/machine/symbols.ts +3 -0
- package/src/internal/machine/topology.ts +65 -12
- package/src/internal/testing/machine/exploration.ts +10 -2
- package/src/internal/testing/machine/finiteModel.ts +114 -80
- package/src/internal/testing/machine/transitionCoverage.ts +116 -0
- package/src/internal/testing/machine/verification.ts +216 -58
- package/src/testing/MachineTest.ts +118 -28
- package/src/unstable/cluster/ClusterMachine.ts +4 -1
- 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.
|
|
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:
|
|
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:
|
|
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:
|
|
81
|
-
|
|
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.
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
315
|
+
initial: {
|
|
316
|
+
target: (to) => to.Working(),
|
|
317
|
+
resolve: ({ target }) => target.from()
|
|
318
|
+
}
|
|
247
319
|
}).handle({
|
|
248
320
|
Working: {
|
|
249
321
|
on: {
|
|
250
|
-
Finish: ({
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
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:
|
|
331
|
-
|
|
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:
|
|
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:
|
|
355
|
-
|
|
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: ({
|
|
382
|
-
|
|
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.
|
|
397
|
-
|
|
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
|
|