@typeonce/effect-machine 0.14.1 → 0.16.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 +99 -48
- package/dist/Machine.d.ts +436 -156
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +114 -57
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +2 -0
- package/dist/internal/machine/activities.d.ts.map +1 -1
- package/dist/internal/machine/activities.js +4 -0
- package/dist/internal/machine/activities.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +6 -1
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts +1 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +25 -3
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationEvent.d.ts +8 -0
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
- package/dist/internal/machine/invocationEvent.js +8 -0
- package/dist/internal/machine/invocationEvent.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +9 -4
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +131 -55
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +7 -0
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +20 -11
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +1 -0
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +25 -16
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/stateDefinition.d.ts +3 -1
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
- package/dist/internal/machine/stateDefinition.js +35 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +11 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +17 -6
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +1 -1
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.js +6 -2
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +6 -0
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +9 -8
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +7 -7
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +3 -3
- package/dist/unstable/reactivity/AtomMachine.js +3 -3
- package/docs/agent-guide.md +207 -88
- package/package.json +1 -1
- package/src/Machine.ts +871 -256
- package/src/internal/machine/activities.ts +7 -0
- package/src/internal/machine/executionPlan.ts +6 -1
- package/src/internal/machine/invocation.ts +39 -4
- package/src/internal/machine/invocationEvent.ts +16 -0
- package/src/internal/machine/machine.ts +187 -67
- package/src/internal/machine/planner.ts +17 -3
- package/src/internal/machine/runtime.ts +61 -25
- package/src/internal/machine/stateDefinition.ts +41 -1
- package/src/internal/machine/topology.ts +31 -2
- package/src/internal/testing/machine/finiteModel.ts +1 -1
- package/src/internal/testing/machine/transitionCoverage.ts +6 -2
- package/src/internal/testing/machine/verification.ts +11 -0
- package/src/testing/MachineTest.ts +9 -7
- package/src/unstable/cluster/ClusterMachine.ts +1 -1
- package/src/unstable/reactivity/AtomMachine.ts +3 -3
package/README.md
CHANGED
|
@@ -53,14 +53,14 @@ const State = Schema.TaggedUnion({
|
|
|
53
53
|
Running: { count: Schema.Number }
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
const States = Machine.states(State.cases)
|
|
57
|
+
const CounterEvent = Machine.events(
|
|
58
|
+
Schema.TaggedUnion({
|
|
59
|
+
Start: {},
|
|
60
|
+
Increment: {},
|
|
61
|
+
Stop: {}
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
64
|
|
|
65
65
|
const CounterDefinition = Machine.make({
|
|
66
66
|
id: "Counter",
|
|
@@ -102,6 +102,16 @@ const program = Effect.gen(function*() {
|
|
|
102
102
|
})
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
`handle` creates a complete implementation boundary. Its result does not
|
|
106
|
+
expose `handle`, so all behavior for one machine belongs in the same handler
|
|
107
|
+
tree. Reuse the definition when multiple independent implementations are
|
|
108
|
+
useful, such as production and testing variants:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const ProductionCounter = CounterDefinition.handle(productionHandlers)
|
|
112
|
+
const TestingCounter = CounterDefinition.handle(testingHandlers)
|
|
113
|
+
```
|
|
114
|
+
|
|
105
115
|
`Machine.start` returns a `MachineRef` with `send`, `state`, `snapshot`,
|
|
106
116
|
`changes`, `emissions`, `join`, and `stop`. Sending enqueues an event; observe
|
|
107
117
|
`changes` or use the testing probe when work must be causally acknowledged.
|
|
@@ -110,13 +120,21 @@ const program = Effect.gen(function*() {
|
|
|
110
120
|
|
|
111
121
|
Use this order to preserve inference and keep boundaries explicit:
|
|
112
122
|
|
|
113
|
-
1. Define domain
|
|
114
|
-
2. Declare topology with `Machine.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
123
|
+
1. Define domain schemas used by state and by shared event fields.
|
|
124
|
+
2. Declare topology with `Machine.states`, naming a tagged state union
|
|
125
|
+
when its `.cases` are reused.
|
|
126
|
+
3. Create event descriptors with `Machine.events`, `Machine.internalEvents`,
|
|
127
|
+
and `Machine.emittedEvents`, passing tagged unions or tagged classes directly.
|
|
128
|
+
4. Create the machine and implement every active state with
|
|
129
|
+
`Machine.make({...}).handle({...})`.
|
|
130
|
+
5. Add child descriptors, then runtime, Atom, testing, or cluster adapters at
|
|
131
|
+
the application boundary.
|
|
132
|
+
|
|
133
|
+
Keep one-off topology inline in `Machine.states`. Use `Machine.state` only when
|
|
134
|
+
the same active state definition is mounted more than once; tagged schemas are
|
|
135
|
+
already reusable without it. For repeated finite regions, derive names with
|
|
136
|
+
`States.path(...)` so every literal in the path family is checked against the
|
|
137
|
+
complete tree. Type full-snapshot helpers as `Machine.Snapshot<typeof States>`.
|
|
120
138
|
|
|
121
139
|
### Construct state through builders
|
|
122
140
|
|
|
@@ -147,7 +165,7 @@ Submit: Machine.transition({
|
|
|
147
165
|
Omit `schema` when a state represents control flow but owns no data:
|
|
148
166
|
|
|
149
167
|
```ts
|
|
150
|
-
const States = Machine.
|
|
168
|
+
const States = Machine.states({
|
|
151
169
|
Form: {
|
|
152
170
|
initial: "Editing",
|
|
153
171
|
states: {
|
|
@@ -178,19 +196,21 @@ belong in `internalEvents`. Ephemeral outward notifications have their own
|
|
|
178
196
|
`emittedEvents` protocol:
|
|
179
197
|
|
|
180
198
|
```ts
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
SaveFailed: { message: Schema.String }
|
|
185
|
-
})
|
|
186
|
-
const Emitted = Schema.TaggedUnion({
|
|
187
|
-
SaveObserved: { id: Schema.String }
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
export const CommandEvent = Machine.events(Command)
|
|
199
|
+
export const CommandEvent = Machine.events(
|
|
200
|
+
Schema.TaggedUnion({ Save: {} })
|
|
201
|
+
)
|
|
191
202
|
export type PublicCommandEvent = Machine.EventOf<typeof CommandEvent>
|
|
192
|
-
const InternalEvent = Machine.internalEvents(
|
|
193
|
-
|
|
203
|
+
const InternalEvent = Machine.internalEvents(
|
|
204
|
+
Schema.TaggedUnion({
|
|
205
|
+
Saved: { id: Schema.String },
|
|
206
|
+
SaveFailed: { message: Schema.String }
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
const Emissions = Machine.emittedEvents(
|
|
210
|
+
Schema.TaggedUnion({
|
|
211
|
+
SaveObserved: { id: Schema.String }
|
|
212
|
+
})
|
|
213
|
+
)
|
|
194
214
|
|
|
195
215
|
const definition = Machine.make({
|
|
196
216
|
states: States.states,
|
|
@@ -374,7 +394,7 @@ change; use `{ reenter: true, transition }` when the source must restart. With
|
|
|
374
394
|
|
|
375
395
|
## Statechart capabilities
|
|
376
396
|
|
|
377
|
-
`Machine.
|
|
397
|
+
`Machine.states` supports:
|
|
378
398
|
|
|
379
399
|
- atomic states;
|
|
380
400
|
- compound states with one active child;
|
|
@@ -384,7 +404,7 @@ change; use `{ reenter: true, transition }` when the source must restart. With
|
|
|
384
404
|
- shallow and deep history states.
|
|
385
405
|
|
|
386
406
|
Declare topology—including finality, output schemas, choices, and history—only
|
|
387
|
-
in `
|
|
407
|
+
in `states`. Handlers implement behavior and output computation without
|
|
388
408
|
repeating structural metadata. Final children complete their parent, so
|
|
389
409
|
`onDone` belongs on that compound or parallel parent.
|
|
390
410
|
|
|
@@ -393,7 +413,7 @@ synchronous. Conditions use ordinary TypeScript control flow. Callbacks may
|
|
|
393
413
|
select state and enqueue explicit `raise`, `emit`, `sendTo`, or `stop` commands;
|
|
394
414
|
arbitrary asynchronous Effects do not run inside planning.
|
|
395
415
|
|
|
396
|
-
## Effects, timers, and child machines
|
|
416
|
+
## Effects, Streams, timers, and child machines
|
|
397
417
|
|
|
398
418
|
State-scoped work starts on entry and is interrupted on exit:
|
|
399
419
|
|
|
@@ -425,8 +445,9 @@ Waiting: {
|
|
|
425
445
|
}
|
|
426
446
|
```
|
|
427
447
|
|
|
428
|
-
Use `effect` for one Effect, `
|
|
429
|
-
|
|
448
|
+
Use `effect` for one Effect, `stream` for a sequence of externally produced
|
|
449
|
+
values, `after` for a cancellable delay, `logic` for a reusable process, and
|
|
450
|
+
`child` for a complete child statechart—all through
|
|
430
451
|
`Machine.invoke({...})`. The helper is an identity at runtime and preserves
|
|
431
452
|
owner-context and source-channel inference across lifecycle handlers, including
|
|
432
453
|
for state-dependent Effects:
|
|
@@ -446,31 +467,58 @@ invoke: Machine.invoke({
|
|
|
446
467
|
})
|
|
447
468
|
```
|
|
448
469
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
owning definition so those references use its exact public input and
|
|
453
|
-
`parentEvents` protocols:
|
|
470
|
+
A Stream source remains independent of the parent event protocol. Each element
|
|
471
|
+
is mapped by `onElement`, and the next element is not pulled until that parent
|
|
472
|
+
macrostep commits:
|
|
454
473
|
|
|
455
474
|
```ts
|
|
456
|
-
|
|
475
|
+
invoke: Machine.invoke({
|
|
476
|
+
id: "channel",
|
|
477
|
+
stream: () => channelMessages,
|
|
478
|
+
onElement: {
|
|
479
|
+
target: Machine.targetless,
|
|
480
|
+
resolve: ({ element }, enqueue) => {
|
|
481
|
+
enqueue.raise(Events.MessageReceived({ message: element }))
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
onDone: { target: Machine.targetless },
|
|
485
|
+
onFailure: Machine.transition({
|
|
486
|
+
target: (to) => to.full.Failed(),
|
|
487
|
+
resolve: ({ error, target }) => target.from({ error })
|
|
488
|
+
})
|
|
489
|
+
})
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
`target: Machine.targetless` is the direct shorthand for a non-reentering
|
|
493
|
+
transition that keeps the current configuration. Its optional `resolve`
|
|
494
|
+
callback may enqueue commands and must return `undefined`. Use
|
|
495
|
+
`Machine.transition(...)` for transitions that select state or reenter.
|
|
496
|
+
|
|
497
|
+
Inside `.handle(...)`, `Machine.invoke(...)` receives the owning machine's
|
|
498
|
+
public input and `parentEvents` protocols contextually. Its source and lifecycle
|
|
499
|
+
callbacks can send through `self` and `parent` while retaining the invoked
|
|
500
|
+
Effect's output and error inference:
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
const machine = Machine.make({
|
|
457
504
|
events: Commands,
|
|
458
505
|
internalEvents: InternalEvents,
|
|
459
506
|
parentEvents: ParentEvents
|
|
460
507
|
// ...
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
const machine = definition.handle({
|
|
508
|
+
}).handle({
|
|
464
509
|
Saving: {
|
|
465
|
-
invoke:
|
|
510
|
+
invoke: Machine.invoke({
|
|
466
511
|
id: "notify-parent",
|
|
467
|
-
effect: (
|
|
468
|
-
parent === undefined
|
|
469
|
-
? Effect.void
|
|
470
|
-
: parent.send(ParentEvents.SaveStarted()),
|
|
512
|
+
effect: () => saveDocument,
|
|
471
513
|
onDone: Machine.transition({
|
|
472
514
|
target: (to) => to.none(),
|
|
473
|
-
resolve: () =>
|
|
515
|
+
resolve: ({ parent, self }, enqueue) => {
|
|
516
|
+
enqueue.sendTo(self, Commands.Save())
|
|
517
|
+
if (parent !== undefined) {
|
|
518
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
519
|
+
}
|
|
520
|
+
return undefined
|
|
521
|
+
}
|
|
474
522
|
}),
|
|
475
523
|
onFailure: Machine.transition({
|
|
476
524
|
target: (to) => to.none(),
|
|
@@ -481,6 +529,9 @@ const machine = definition.handle({
|
|
|
481
529
|
})
|
|
482
530
|
```
|
|
483
531
|
|
|
532
|
+
The machine-bound `definition.invoke(...)` form remains equivalent when a
|
|
533
|
+
definition is already named; it is not required for `self` or `parent` typing.
|
|
534
|
+
|
|
484
535
|
A direct `invoke: { ... }` object is also supported when its lifecycle handlers
|
|
485
536
|
do not need source-derived context. Reuse one exported
|
|
486
537
|
`Machine.child(id, machine)` descriptor for invocation, `sendTo`, and child
|