@typeonce/effect-machine 0.8.0 → 0.10.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 +180 -40
- package/dist/Machine.d.ts +659 -481
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +86 -222
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +5 -12
- package/dist/internal/machine/activities.d.ts.map +1 -1
- package/dist/internal/machine/activities.js +47 -17
- package/dist/internal/machine/activities.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +6 -2
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js +5 -0
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/cluster.d.ts.map +1 -1
- package/dist/internal/machine/cluster.js +2 -1
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/command.js +2 -2
- package/dist/internal/machine/command.js.map +1 -1
- package/dist/internal/machine/commandRuntime.d.ts.map +1 -1
- package/dist/internal/machine/commandRuntime.js +6 -3
- package/dist/internal/machine/commandRuntime.js.map +1 -1
- package/dist/internal/machine/configuration.d.ts +8 -1
- package/dist/internal/machine/configuration.d.ts.map +1 -1
- package/dist/internal/machine/configuration.js +9 -4
- package/dist/internal/machine/configuration.js.map +1 -1
- package/dist/internal/machine/errors.d.ts +1 -1
- package/dist/internal/machine/errors.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts +3 -3
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +74 -24
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts +10 -2
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +94 -34
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationEvent.d.ts +39 -0
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -0
- package/dist/internal/machine/invocationEvent.js +43 -0
- package/dist/internal/machine/invocationEvent.js.map +1 -0
- package/dist/internal/machine/machine.d.ts +16 -65
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +18 -73
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +4 -2
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +104 -24
- 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 +8 -8
- package/dist/internal/machine/process.js.map +1 -1
- package/dist/internal/machine/protocol.d.ts +8 -5
- package/dist/internal/machine/protocol.d.ts.map +1 -1
- package/dist/internal/machine/protocol.js +207 -15
- package/dist/internal/machine/protocol.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +8 -2
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +86 -8
- 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 +7 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +29 -0
- 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/runtime.d.ts.map +1 -1
- package/dist/internal/testing/machine/runtime.js +2 -1
- package/dist/internal/testing/machine/runtime.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +5 -3
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +7 -7
- package/dist/testing/MachineTest.js +7 -7
- package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +25 -8
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js +18 -3
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +242 -122
- package/package.json +2 -2
- package/src/Machine.ts +1453 -812
- package/src/internal/machine/activities.ts +48 -33
- package/src/internal/machine/atom.ts +38 -13
- package/src/internal/machine/cluster.ts +2 -1
- package/src/internal/machine/command.ts +2 -2
- package/src/internal/machine/commandRuntime.ts +9 -4
- package/src/internal/machine/configuration.ts +23 -5
- package/src/internal/machine/errors.ts +1 -1
- package/src/internal/machine/executionPlan.ts +102 -28
- package/src/internal/machine/invocation.ts +182 -50
- package/src/internal/machine/invocationEvent.ts +72 -0
- package/src/internal/machine/machine.ts +80 -387
- package/src/internal/machine/planner.ts +127 -32
- package/src/internal/machine/process.ts +18 -8
- package/src/internal/machine/protocol.ts +313 -25
- package/src/internal/machine/runtime.ts +135 -9
- package/src/internal/machine/symbols.ts +3 -0
- package/src/internal/machine/topology.ts +38 -0
- package/src/internal/testing/machine/finiteModel.ts +1 -1
- package/src/internal/testing/machine/runtime.ts +2 -1
- package/src/internal/testing/machine/verification.ts +5 -3
- package/src/testing/MachineTest.ts +7 -7
- package/src/unstable/cluster/ClusterMachine.ts +1 -1
- package/src/unstable/reactivity/AtomMachine.ts +45 -13
package/docs/agent-guide.md
CHANGED
|
@@ -22,10 +22,10 @@ declared:
|
|
|
22
22
|
|
|
23
23
|
1. Domain schemas used by state and event fields.
|
|
24
24
|
2. Tagged schemas for states that own data.
|
|
25
|
-
3. Tagged public-event, internal-event, and emitted-event schemas.
|
|
25
|
+
3. Tagged public-event, internal-event, parent-event, and emitted-event schemas.
|
|
26
26
|
4. `Machine.defineStates`.
|
|
27
|
-
5. `Machine.make`, including input, events
|
|
28
|
-
initial function.
|
|
27
|
+
5. `Machine.make`, including input, `events`, `internalEvents`, `parentEvents`,
|
|
28
|
+
`emittedEvents`, and the initial function.
|
|
29
29
|
6. One or more `.handle(...)` calls.
|
|
30
30
|
7. Child descriptors.
|
|
31
31
|
8. Runtime, Atom, or Cluster adapters.
|
|
@@ -49,13 +49,17 @@ const InternalEvent = Schema.TaggedUnion({
|
|
|
49
49
|
})
|
|
50
50
|
|
|
51
51
|
const States = Machine.defineStates(State.cases)
|
|
52
|
+
const Events = Machine.events(Event)
|
|
53
|
+
const InternalEvents = Machine.internalEvents(InternalEvent)
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
values through the target or initial
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
Pass these descriptors to `Machine.make` and export `Events` instead of the raw
|
|
57
|
+
event schema. Construct new state values through the target or initial
|
|
58
|
+
builder's `.from(...)` method. Both event constructors and state `.from(...)`
|
|
59
|
+
defer schema construction until planning, so validation failures remain typed
|
|
60
|
+
machine errors. Use
|
|
61
|
+
`Schema.TaggedClass` when a case needs class methods or nominal class identity;
|
|
62
|
+
the deferred constructors preserve that identity after decoding.
|
|
59
63
|
|
|
60
64
|
## Hard invariants
|
|
61
65
|
|
|
@@ -68,23 +72,26 @@ nominal class identity; `.from(...)` preserves that identity.
|
|
|
68
72
|
- Return snapshots or typed target-builder results from transitions. Do not
|
|
69
73
|
return raw decoded state values.
|
|
70
74
|
- Transition and lifecycle callbacks are synchronous. Put asynchronous work in
|
|
71
|
-
an invoked Effect,
|
|
75
|
+
an invoked Effect, logic process, or child machine and handle its lifecycle
|
|
76
|
+
with `onDone`, `onFailure`, and `onSnapshot`.
|
|
72
77
|
- Put data on the narrowest state where it is valid. Put data shared by sibling
|
|
73
78
|
phases on their compound parent.
|
|
74
79
|
- Declare finality only in the state definition. Do not put `type: "final"` in
|
|
75
80
|
a handler.
|
|
76
81
|
- Every declared output schema needs a matching handler implementation before
|
|
77
82
|
planning or execution.
|
|
78
|
-
- `
|
|
83
|
+
- Handler `ancestors` keys are full dotted paths.
|
|
79
84
|
- Invoke lifetimes follow state entry and exit, not the spelling of the target
|
|
80
85
|
builder.
|
|
81
|
-
-
|
|
82
|
-
|
|
83
|
-
- Reuse
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
- Handle every typed invoked Effect failure with `onFailure`. Defects and
|
|
87
|
+
interruption terminate the owning machine.
|
|
88
|
+
- Reuse an exported child descriptor for inline invocation, `sendTo`, and child
|
|
89
|
+
lookup. Independently constructed descriptors are equivalent only when both
|
|
90
|
+
their id and machine identity match.
|
|
91
|
+
- `events` is the public actor-input protocol. `internalEvents` contains
|
|
92
|
+
machine-local raised events. `parentEvents` describes the public events a
|
|
93
|
+
child may send to its owner. `emittedEvents` describes outward ephemeral
|
|
94
|
+
notifications and is never delivered implicitly to a parent.
|
|
88
95
|
- Event tags in `events` and `internalEvents` must be disjoint.
|
|
89
96
|
- Event tags must also be unique within each protocol list.
|
|
90
97
|
|
|
@@ -96,13 +103,15 @@ its extra control is required:
|
|
|
96
103
|
- Bind a shared Atom runtime once with `AtomMachine.bind(runtime)`, then use the
|
|
97
104
|
returned `make` or `resume`. Use `AtomMachine.make(machine)` and
|
|
98
105
|
`AtomMachine.resume(machine, snapshot)` for service-free machines.
|
|
99
|
-
- Use `
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
- Use one invocation object: `effect` for one-shot work, `after` for a timer,
|
|
107
|
+
`logic` for reusable process logic, and `child` for a complete child
|
|
108
|
+
statechart. `Machine.invoke({...})` preserves owner context and source
|
|
109
|
+
channels across sibling lifecycle handlers. Use a direct object only when its
|
|
110
|
+
lifecycle handlers do not need source-derived context.
|
|
102
111
|
- Use `Machine.child(id, machine)` for a complete statechart descriptor and
|
|
103
|
-
`Machine.childAddress<Event>(id)` for a low-level process address.
|
|
104
|
-
invocation is addressable only when `Machine.invoke` receives that
|
|
105
|
-
explicitly.
|
|
112
|
+
`Machine.childAddress<Event>(id)` for a low-level process address. A logic
|
|
113
|
+
invocation is addressable only when `Machine.invoke` receives that
|
|
114
|
+
address explicitly.
|
|
106
115
|
- Use the callback's `enqueue` argument for `raise`, `emit`, `sendTo`, and
|
|
107
116
|
`stop`. These operations record closed actor commands and do not run Effects.
|
|
108
117
|
|
|
@@ -148,8 +157,8 @@ States.get(snapshot, "Form") // type error: no value schema
|
|
|
148
157
|
|
|
149
158
|
For a schema-less path, builders expose only `.from(...)`; the direct callable
|
|
150
159
|
form is reserved for already-decoded schema values. Structural ancestors are
|
|
151
|
-
also omitted from `
|
|
152
|
-
`undefined`. Add `schema` when a state begins to own data or needs runtime
|
|
160
|
+
also omitted from `ancestors`; an immediate structural containing state is
|
|
161
|
+
typed as `undefined`. Add `schema` when a state begins to own data or needs runtime
|
|
153
162
|
validation and persistence for that data.
|
|
154
163
|
|
|
155
164
|
Use an atomic state when no child phase can be active beneath it.
|
|
@@ -217,7 +226,7 @@ const States = Machine.defineStates({
|
|
|
217
226
|
|
|
218
227
|
const machine = Machine.make({
|
|
219
228
|
states: States.states,
|
|
220
|
-
events:
|
|
229
|
+
events: Machine.events(),
|
|
221
230
|
initial: () => States.initial.Done.from()
|
|
222
231
|
}).handle({
|
|
223
232
|
Done: {
|
|
@@ -372,7 +381,7 @@ parallel builders still require a callback selecting their active child or
|
|
|
372
381
|
every active region. Omitted input is normalized to `{}` and still passes
|
|
373
382
|
through `schema.makeEffect`, including refinements.
|
|
374
383
|
|
|
375
|
-
## Reading state and
|
|
384
|
+
## Reading state and structural ancestors
|
|
376
385
|
|
|
377
386
|
`Machine.defineStates` returns typed helpers:
|
|
378
387
|
|
|
@@ -394,16 +403,18 @@ States.matches(ready, "Route.Ready.Saving")
|
|
|
394
403
|
|
|
395
404
|
All paths are checked against the definition. `get` and `getWithParents` accept
|
|
396
405
|
only schema-backed paths; use `matches` or `getSnapshot` for any active path.
|
|
397
|
-
`context.
|
|
398
|
-
when that
|
|
399
|
-
|
|
406
|
+
`context.containingState` is the immediate typed state value (`undefined` at a
|
|
407
|
+
root or when that state is schema-less). `context.ancestors` contains only
|
|
408
|
+
valued structural ancestors. This is separate from `context.parent`, which is
|
|
409
|
+
the owning actor reference or `undefined` for a root actor. Use full state paths
|
|
410
|
+
when another ancestor value is needed:
|
|
400
411
|
|
|
401
412
|
```ts
|
|
402
|
-
|
|
403
|
-
|
|
413
|
+
ancestors["Route.Ready"]
|
|
414
|
+
ancestors["Route.Ready.Editing"]
|
|
404
415
|
```
|
|
405
416
|
|
|
406
|
-
Do not guess short properties such as `
|
|
417
|
+
Do not guess short properties such as `ancestors.Ready`.
|
|
407
418
|
|
|
408
419
|
### Inspecting the full transition configuration
|
|
409
420
|
|
|
@@ -415,7 +426,7 @@ microstep, before any selected transition is applied:
|
|
|
415
426
|
BufferReady: ({ snapshot, target }) =>
|
|
416
427
|
States.matches(snapshot, "Player.Network.Online")
|
|
417
428
|
? target.local.Playing.from()
|
|
418
|
-
:
|
|
429
|
+
: target.none()
|
|
419
430
|
```
|
|
420
431
|
|
|
421
432
|
Use the existing `States.matches`, `States.get`, `States.getWithParents`, and
|
|
@@ -446,11 +457,19 @@ history definitions may declare an `annotations` object containing only
|
|
|
446
457
|
they cannot change behavior, identity, or targeting. Visualization may show a
|
|
447
458
|
title, while the structural path remains authoritative.
|
|
448
459
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
460
|
+
When sibling state payloads share fields, destructure away the source
|
|
461
|
+
discriminator and construct the destination through its target builder:
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
Submit: ({ state, target }) => {
|
|
465
|
+
const { _tag: _, ...fields } = state
|
|
466
|
+
return target.local.Saving.from({ ...fields, attempt: 1 })
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
The target schema remains responsible for defaults, transforms, refinements,
|
|
471
|
+
and class identity. Prefer moving broadly shared data to the compound parent
|
|
472
|
+
rather than copying it through every phase.
|
|
454
473
|
|
|
455
474
|
## Planning, actions, raised events, and emissions
|
|
456
475
|
|
|
@@ -458,18 +477,89 @@ A transition returns a target synchronously:
|
|
|
458
477
|
|
|
459
478
|
```ts
|
|
460
479
|
Submit: ({ state, target }) =>
|
|
461
|
-
state.valid ? target.local.Saving.from({ draft: state.draft }) :
|
|
480
|
+
state.valid ? target.local.Saving.from({ draft: state.draft }) : target.none()
|
|
462
481
|
```
|
|
463
482
|
|
|
483
|
+
Every installed event, `always`, `onDone`, and invoke lifecycle handler must
|
|
484
|
+
return a concrete target or `target.none()`. An absent event handler means the
|
|
485
|
+
event is ignored. Returning `target.none()` means it was handled without a
|
|
486
|
+
destination, so queued commands, raised events, and emitted events are still
|
|
487
|
+
retained. It remains valid when a transition declares `targets`: those paths
|
|
488
|
+
are an upper bound on concrete destinations, not an exhaustive result set.
|
|
489
|
+
|
|
490
|
+
`reenter: true` remains meaningful with `target.none()`: the source exits and
|
|
491
|
+
enters again while its logical configuration is retained.
|
|
492
|
+
|
|
464
493
|
Closed statechart and actor operations use `enqueue`:
|
|
465
494
|
|
|
466
495
|
```ts
|
|
467
496
|
Submit: ({ target }, enqueue) => {
|
|
468
|
-
enqueue.emit(
|
|
497
|
+
enqueue.emit(Emissions.SaveRequested())
|
|
469
498
|
return target.local.Saving.from()
|
|
470
499
|
}
|
|
471
500
|
```
|
|
472
501
|
|
|
502
|
+
Declare emission constructors separately from actor inputs:
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
const Emissions = Machine.emittedEvents(SaveRequested, AuditRecorded)
|
|
506
|
+
|
|
507
|
+
const definition = Machine.make({
|
|
508
|
+
events: Commands,
|
|
509
|
+
internalEvents: InternalEvents,
|
|
510
|
+
emittedEvents: Emissions,
|
|
511
|
+
// ...
|
|
512
|
+
})
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
`enqueue.raise(...)` is a same-macrostep input to self. `enqueue.sendTo(...)`
|
|
516
|
+
targets an actor mailbox and is processed later. `enqueue.emit(...)` is neither:
|
|
517
|
+
it publishes a one-off outward notification. Observe it with
|
|
518
|
+
`ref.emissions`, a hot non-replayed `Stream` that completes with the actor.
|
|
519
|
+
`ref.changes` is stateful and begins with the current lifecycle snapshot.
|
|
520
|
+
Startup emissions occur before `Machine.start` returns and therefore are not
|
|
521
|
+
visible through the returned ref; use state for facts that must be retained.
|
|
522
|
+
|
|
523
|
+
For child-to-parent input, export a public builder protocol and reuse it at both
|
|
524
|
+
composition boundaries:
|
|
525
|
+
|
|
526
|
+
```ts
|
|
527
|
+
export const ParentEvents = Machine.events(ChildFinished)
|
|
528
|
+
|
|
529
|
+
const child = Machine.make({
|
|
530
|
+
events: ChildEvents,
|
|
531
|
+
parentEvents: ParentEvents,
|
|
532
|
+
// ...
|
|
533
|
+
}).handle({
|
|
534
|
+
Working: {
|
|
535
|
+
on: {
|
|
536
|
+
Finish: ({ parent }, enqueue) => {
|
|
537
|
+
if (parent !== undefined) {
|
|
538
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished())
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
const parent = Machine.make({
|
|
546
|
+
events: Machine.events(ParentCommands, ParentEvents),
|
|
547
|
+
// ...
|
|
548
|
+
})
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Invoking the child under a parent that lacks any required `parentEvents` case
|
|
552
|
+
is a type error. Within child handlers, `parent` accepts only that protocol.
|
|
553
|
+
The same child may run as a root, where `parent` is `undefined`. `self` accepts
|
|
554
|
+
the machine's public inputs. Neither actor reference is a structural state
|
|
555
|
+
value; use `containingState` and `ancestors` for statechart ancestry.
|
|
556
|
+
|
|
557
|
+
Atom-backed actors retain the same transient semantics. Use
|
|
558
|
+
`AtomMachine.emissions(machineAtom)` for a root and
|
|
559
|
+
`AtomMachine.childEmissions(childAtom)` for the currently active child. Both
|
|
560
|
+
return streams requiring the corresponding `AtomRegistry`; emissions are not
|
|
561
|
+
stored as atom state.
|
|
562
|
+
|
|
473
563
|
For asynchronous validation or persistence, invoke an Effect or child machine
|
|
474
564
|
from the state and handle its typed success or failure event in a later
|
|
475
565
|
transition. This keeps `(state, event) => [nextState, commands]` synchronous.
|
|
@@ -499,86 +589,119 @@ an event for the parent. Both operations validate their schemas.
|
|
|
499
589
|
union handled inside the statechart:
|
|
500
590
|
|
|
501
591
|
```ts
|
|
502
|
-
const
|
|
592
|
+
const Events = Machine.events(Event)
|
|
593
|
+
const InternalEvents = Machine.internalEvents(InternalEvent)
|
|
594
|
+
|
|
595
|
+
const definition = Machine.make({
|
|
503
596
|
states: States.states,
|
|
504
|
-
events:
|
|
505
|
-
internalEvents:
|
|
597
|
+
events: Events,
|
|
598
|
+
internalEvents: InternalEvents,
|
|
506
599
|
initial: () => States.initial.Idle.from()
|
|
507
600
|
})
|
|
508
601
|
```
|
|
509
602
|
|
|
510
|
-
|
|
511
|
-
it once through its owning machine protocol:
|
|
603
|
+
Use the protocol-bound constructors at every machine delivery boundary:
|
|
512
604
|
|
|
513
605
|
```ts
|
|
514
|
-
|
|
515
|
-
|
|
606
|
+
yield* ref.send(Events.Save())
|
|
607
|
+
enqueue.raise(InternalEvents.Saved({ id: "entry-1" }))
|
|
516
608
|
```
|
|
517
609
|
|
|
518
|
-
`Machine.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
610
|
+
`Machine.events` exposes only public constructors;
|
|
611
|
+
`Machine.internalEvents` exposes only machine-local constructors. Both flatten
|
|
612
|
+
configured tagged unions and preserve tagged classes, finite discriminator
|
|
613
|
+
unions, required inputs, and constructor defaults. A constructor returns an
|
|
614
|
+
opaque instruction whose `_tag` is available for activity metadata. Its decoded
|
|
615
|
+
fields are intentionally unavailable until the owning machine processes it.
|
|
616
|
+
|
|
617
|
+
Invalid constructor input fails `Machine.plan` or the running machine with
|
|
618
|
+
`MachineSchemaDecodeError`; creating the instruction itself never performs
|
|
619
|
+
schema validation. APIs that explicitly retain decoded events, such as manual
|
|
620
|
+
model-testing scenarios or transport messages, can receive complete event
|
|
621
|
+
objects directly.
|
|
622
|
+
|
|
623
|
+
An open discriminator such as `_tag: Schema.String` cannot produce named
|
|
624
|
+
constructors because its tag set is not finite. The schema still participates
|
|
625
|
+
in the protocol; pass a complete event object at the delivery boundary.
|
|
524
626
|
|
|
525
627
|
Use the exported utility types when another API must preserve the boundary:
|
|
526
628
|
|
|
527
629
|
```ts
|
|
528
|
-
type PublicEvent = Machine.Machine.InputEvent<typeof
|
|
529
|
-
type AnyHandledEvent = Machine.Machine.Event<typeof
|
|
630
|
+
type PublicEvent = Machine.Machine.InputEvent<typeof definition>
|
|
631
|
+
type AnyHandledEvent = Machine.Machine.Event<typeof definition>
|
|
530
632
|
```
|
|
531
633
|
|
|
532
|
-
`MachineRef.send`, `machineAtom.send`, and `Machine.plan`
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
634
|
+
`MachineRef.send`, `machineAtom.send`, and `Machine.plan` accept decoded public
|
|
635
|
+
events or constructions returned by `Machine.events`. Transition handlers
|
|
636
|
+
receive only decoded events. Raised events additionally accept constructions
|
|
637
|
+
from `Machine.internalEvents`; outward notifications accept constructions from
|
|
638
|
+
`Machine.emittedEvents`. The
|
|
639
|
+
local planner and runtime intentionally share the complete decoder to support
|
|
640
|
+
those internal deliveries, so JavaScript or `any` can bypass the local public
|
|
641
|
+
distinction.
|
|
537
642
|
Cluster RPC payloads are additionally decoded against the public `events`
|
|
538
643
|
schemas at the transport boundary. Never repeat an `_tag` within a list or
|
|
539
644
|
across both configuration lists.
|
|
540
645
|
|
|
541
646
|
## Recoverable state-scoped work
|
|
542
647
|
|
|
543
|
-
Use `Machine.
|
|
544
|
-
|
|
648
|
+
Use `Machine.invoke` with an `effect` for one-shot work. Lifecycle callbacks
|
|
649
|
+
receive the typed Effect channels and can transition directly:
|
|
545
650
|
|
|
546
651
|
```ts
|
|
547
|
-
invoke: ({
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
652
|
+
invoke: Machine.invoke({
|
|
653
|
+
id: "save",
|
|
654
|
+
effect: SaveService.save(draft),
|
|
655
|
+
onDone: ({ output, target }) => target.full.Saved({ entry: output }),
|
|
656
|
+
onFailure: ({ error, target }) =>
|
|
657
|
+
target.full.SaveFailed({ message: error.message })
|
|
658
|
+
})
|
|
554
659
|
```
|
|
555
660
|
|
|
556
661
|
The owning state scopes the child. Owner-driven interruption on state exit is
|
|
557
|
-
normal cancellation and stale output is ignored.
|
|
558
|
-
|
|
559
|
-
|
|
662
|
+
normal cancellation and stale output is ignored. An Effect that defects or
|
|
663
|
+
self-interrupts fails the parent. `onDone` is required when the output is not
|
|
664
|
+
`never`; `onFailure` is required when the typed error is not `never`. Handlers
|
|
665
|
+
are forbidden when their channel is `never`.
|
|
666
|
+
|
|
667
|
+
The source may also be a function of the owning state's entry context when it
|
|
668
|
+
needs `state`, `containingState`, `ancestors`, or the entry `event`. Source construction
|
|
669
|
+
errors, defects, and interruption are machine failures rather than a second
|
|
670
|
+
phase in `onFailure`.
|
|
671
|
+
|
|
672
|
+
When a source function reads `state`, `containingState`, `ancestors`, or the entry `event`,
|
|
673
|
+
`Machine.invoke` infers that owner context and the returned Effect's output,
|
|
674
|
+
error, and service channels together. No return annotation is needed:
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
invoke: Machine.invoke({
|
|
678
|
+
id: "load",
|
|
679
|
+
effect: ({ state }) => LoadService.load(state.userId),
|
|
680
|
+
onDone: ({ output, target }) => target.full.Loaded({ user: output }),
|
|
681
|
+
onFailure: ({ error, target }) => target.full.LoadFailed({ error })
|
|
682
|
+
})
|
|
683
|
+
```
|
|
560
684
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
add handlers for the relevant tags. Leave defects and interruption fatal;
|
|
564
|
-
recover only expected typed failures.
|
|
685
|
+
A direct `invoke: { ... }` object remains available when lifecycle handlers do
|
|
686
|
+
not need source-derived context.
|
|
565
687
|
|
|
566
|
-
A cancellable timer uses
|
|
688
|
+
A cancellable timer uses the same object:
|
|
567
689
|
|
|
568
690
|
```ts
|
|
569
|
-
invoke: Machine.
|
|
570
|
-
id: "clear-status"
|
|
691
|
+
invoke: Machine.invoke({
|
|
692
|
+
id: "clear-status",
|
|
693
|
+
after: "3 seconds",
|
|
694
|
+
onDone: ({ target }) => target.full.Clear()
|
|
571
695
|
})
|
|
572
696
|
```
|
|
573
697
|
|
|
574
|
-
The timer starts on state entry and is interrupted on exit.
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
state-local lifecycle
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
unique among simultaneously active invokes owned by the same state.
|
|
698
|
+
The timer starts on state entry and is interrupted on exit. Its `onDone` is
|
|
699
|
+
always required. `effect: Effect.sleep(...)` has the same scoped cancellation
|
|
700
|
+
behavior, but `after` records timer intent and exposes a static duration through
|
|
701
|
+
`Machine.activityDefinitions`. For reusable process logic, provide `logic`, a
|
|
702
|
+
state-local lifecycle `id`, and a typed `address`. TypeScript checks the address
|
|
703
|
+
protocol against the logic event protocol. Lifecycle ids and addresses serve
|
|
704
|
+
different purposes and must both be explicit.
|
|
582
705
|
|
|
583
706
|
## Invoked child statecharts
|
|
584
707
|
|
|
@@ -591,24 +714,24 @@ const Editor = Machine.child("editor", EditorMachine)
|
|
|
591
714
|
Invoke it from its owning state:
|
|
592
715
|
|
|
593
716
|
```ts
|
|
594
|
-
invoke: Machine.
|
|
717
|
+
invoke: Machine.invoke({
|
|
595
718
|
child: Editor,
|
|
596
719
|
input: editorInput,
|
|
597
|
-
onDone: ({ output }) =>
|
|
720
|
+
onDone: ({ output, target }) => target.full.EditorDone({ output })
|
|
598
721
|
})
|
|
599
722
|
```
|
|
600
723
|
|
|
601
724
|
Use `Editor` for:
|
|
602
725
|
|
|
603
726
|
```ts
|
|
604
|
-
Machine.sendTo(Editor,
|
|
727
|
+
Machine.sendTo(Editor, EditorEvent.Reset())
|
|
605
728
|
parentRef.child(Editor)
|
|
606
729
|
parentAtom.child(Editor)
|
|
607
730
|
```
|
|
608
731
|
|
|
609
|
-
Child emissions
|
|
610
|
-
|
|
611
|
-
|
|
732
|
+
Child emissions are delivered through the parent's internal protocol.
|
|
733
|
+
`onSnapshot`, `onDone`, and `onFailure` are direct parent transitions. Invoked
|
|
734
|
+
child IDs must be unique while simultaneously active.
|
|
612
735
|
|
|
613
736
|
Descriptors with the same id and machine identity address the same child, even
|
|
614
737
|
when independently constructed. The descriptor objects themselves are not
|
|
@@ -620,21 +743,18 @@ logic that does not have a complete machine descriptor.
|
|
|
620
743
|
### Inspecting state-owned activities
|
|
621
744
|
|
|
622
745
|
Use `Machine.activityDefinitions(machine)` to inspect invokes without running
|
|
623
|
-
them. Static `Machine.invoke
|
|
624
|
-
|
|
746
|
+
them. Static inline `Machine.invoke` definitions expose serializable ownership
|
|
747
|
+
metadata:
|
|
625
748
|
|
|
626
749
|
```ts
|
|
627
750
|
Machine.activityDefinitions(machine)
|
|
628
751
|
// [{ source: "Loading", id: "load-timeout", type: "timer",
|
|
629
|
-
// duration: "10s"
|
|
752
|
+
// duration: "10s" }]
|
|
630
753
|
```
|
|
631
754
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
activity because inspection must not evaluate user code. The existing invoke
|
|
636
|
-
helpers remain the only execution API; this metadata does not add lifecycle
|
|
637
|
-
configuration syntax or affect execution.
|
|
755
|
+
Child machines expose descriptor identity, never their runtime or
|
|
756
|
+
implementation. Function-valued sources and durations are represented as
|
|
757
|
+
dynamic because inspection must not evaluate user code.
|
|
638
758
|
|
|
639
759
|
## AtomMachine and React
|
|
640
760
|
|
|
@@ -743,8 +863,8 @@ Encoding does not preserve:
|
|
|
743
863
|
- completion and history records survive but do not retrigger `onDone`;
|
|
744
864
|
- active-state invokes start once in ordinary ancestor/document order with
|
|
745
865
|
`Machine.InitialEvent`;
|
|
746
|
-
-
|
|
747
|
-
|
|
866
|
+
- inline Effects restart, child machines start fresh from their normal initial
|
|
867
|
+
state, and timers restart their complete duration;
|
|
748
868
|
- inactive invokes, spawned children, child snapshots, elapsed timer time, and
|
|
749
869
|
prior `RuntimeSnapshot` status/errors are not restored;
|
|
750
870
|
- a final logical snapshot creates an immediately completed ref;
|
|
@@ -918,26 +1038,26 @@ Wrap the initial builder result:
|
|
|
918
1038
|
initial: () => States.initial.Idle.from()
|
|
919
1039
|
```
|
|
920
1040
|
|
|
921
|
-
### Invoked child
|
|
1041
|
+
### Invoked child expects events not accepted by the parent
|
|
922
1042
|
|
|
923
|
-
|
|
924
|
-
|
|
1043
|
+
Export one parent-event protocol from the child boundary and compose it into
|
|
1044
|
+
the parent's public events:
|
|
925
1045
|
|
|
926
|
-
|
|
1046
|
+
```ts
|
|
1047
|
+
export const ChildParentEvents = Machine.events(ChildFinished)
|
|
927
1048
|
|
|
928
|
-
|
|
1049
|
+
// child
|
|
1050
|
+
parentEvents: ChildParentEvents
|
|
929
1051
|
|
|
930
|
-
|
|
931
|
-
events:
|
|
932
|
-
internalEvents: [...ChildMachine.emits]
|
|
1052
|
+
// parent
|
|
1053
|
+
events: Machine.events(Submit, ChildParentEvents)
|
|
933
1054
|
```
|
|
934
1055
|
|
|
935
1056
|
### An internal event is rejected by `send`
|
|
936
1057
|
|
|
937
1058
|
This is intentional. Public input boundaries accept only schemas declared in
|
|
938
|
-
`events`. Handle the event as
|
|
939
|
-
|
|
940
|
-
send it.
|
|
1059
|
+
`events`. Handle the event as a child delivery or raised event; move it to
|
|
1060
|
+
`events` only if external callers should genuinely be allowed to send it.
|
|
941
1061
|
|
|
942
1062
|
### Public and internal event tags overlap
|
|
943
1063
|
|
|
@@ -965,15 +1085,15 @@ handlers own behavior.
|
|
|
965
1085
|
|
|
966
1086
|
### Parent property does not exist
|
|
967
1087
|
|
|
968
|
-
Use
|
|
1088
|
+
Use the structural ancestor's full path:
|
|
969
1089
|
|
|
970
1090
|
```ts
|
|
971
|
-
|
|
1091
|
+
ancestors["Route.Ready"]
|
|
972
1092
|
```
|
|
973
1093
|
|
|
974
1094
|
### Child descriptor types are unrelated
|
|
975
1095
|
|
|
976
|
-
Use the descriptor exported by the module that configured
|
|
1096
|
+
Use the descriptor exported by the module that configured the child invocation.
|
|
977
1097
|
An independently created descriptor with the same id and machine identity also
|
|
978
1098
|
matches; the same id paired with a different machine remains a distinct child.
|
|
979
1099
|
|
|
@@ -997,6 +1117,6 @@ The current API does not include:
|
|
|
997
1117
|
- declarative first-class guards;
|
|
998
1118
|
- a complete inspectable graph for arbitrary transition Effects.
|
|
999
1119
|
|
|
1000
|
-
Use ordinary TypeScript conditions for guards and `Machine.
|
|
1001
|
-
state-scoped timers. Do not invent undocumented state-node
|
|
1002
|
-
`guard`.
|
|
1120
|
+
Use ordinary TypeScript conditions for guards and an inline `Machine.invoke`
|
|
1121
|
+
with `after` for state-scoped timers. Do not invent undocumented state-node
|
|
1122
|
+
properties such as `guard`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeonce/effect-machine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Schema-first state machines and statecharts for Effect",
|
|
5
5
|
"author": "Sandro Maglione",
|
|
6
6
|
"repository": {
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"test": "vitest run",
|
|
70
70
|
"test:types": "tstyche",
|
|
71
71
|
"check:architecture": "node --test scripts/check-architecture.test.mjs && node scripts/check-architecture.mjs",
|
|
72
|
-
"check:ci": "node --test scripts/ci-changes.test.mjs scripts/runtime-performance-regression.test.mjs",
|
|
72
|
+
"check:ci": "node --test scripts/ci-changes.test.mjs scripts/runtime-performance-compatibility.test.mjs scripts/runtime-performance-regression.test.mjs",
|
|
73
73
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
74
74
|
"perf:types": "pnpm build && node scripts/type-performance.mjs",
|
|
75
75
|
"perf:runtime": "pnpm build && node --expose-gc scripts/runtime-performance.mjs",
|