@typeonce/effect-machine 0.16.0 → 0.17.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 +98 -106
- package/dist/Machine.d.ts +324 -367
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +61 -96
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +7 -7
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/cluster.d.ts +2 -2
- package/dist/internal/machine/cluster.d.ts.map +1 -1
- package/dist/internal/machine/cluster.js +6 -5
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +10 -3
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +8 -6
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +191 -25
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +19 -4
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +56 -20
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
- package/dist/internal/machine/stateDefinition.js +14 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +9 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +16 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +15 -20
- 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 +2 -0
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +11 -11
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +5 -8
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +2 -2
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +12 -12
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +144 -147
- package/package.json +1 -1
- package/src/Machine.ts +1118 -1178
- package/src/internal/machine/atom.ts +20 -15
- package/src/internal/machine/cluster.ts +14 -9
- package/src/internal/machine/executionPlan.ts +8 -3
- package/src/internal/machine/machine.ts +301 -40
- package/src/internal/machine/planner.ts +89 -27
- package/src/internal/machine/stateDefinition.ts +39 -0
- package/src/internal/machine/topology.ts +28 -0
- package/src/internal/testing/machine/finiteModel.ts +18 -21
- package/src/internal/testing/machine/transitionCoverage.ts +2 -0
- package/src/testing/MachineTest.ts +14 -11
- package/src/unstable/cluster/ClusterMachine.ts +8 -4
- package/src/unstable/reactivity/AtomMachine.ts +19 -12
package/docs/agent-guide.md
CHANGED
|
@@ -38,8 +38,9 @@ declared:
|
|
|
38
38
|
1. Domain schemas used by state, and by event fields when they are shared.
|
|
39
39
|
2. `Machine.states`, using a tagged state union and `.cases` when state
|
|
40
40
|
schemas need to be reused.
|
|
41
|
-
3. `Machine.events`, `Machine.internalEvents`, `Machine.emittedEvents`, and
|
|
42
|
-
|
|
41
|
+
3. `Machine.events`, `Machine.internalEvents`, `Machine.emittedEvents`, and any
|
|
42
|
+
protocol passed to `Machine.parent` or `Machine.optionalParent`; pass
|
|
43
|
+
`Schema.TaggedUnion({...})` or tagged classes directly.
|
|
43
44
|
4. `Machine.make({...}).handle({...})`.
|
|
44
45
|
5. Child descriptors, then runtime, Atom, or Cluster adapters.
|
|
45
46
|
|
|
@@ -109,8 +110,9 @@ the deferred constructors preserve that identity after decoding.
|
|
|
109
110
|
lookup. Independently constructed descriptors are equivalent only when both
|
|
110
111
|
their id and machine identity match.
|
|
111
112
|
- `events` is the public machine-input protocol. `internalEvents` contains
|
|
112
|
-
machine-local raised events. `
|
|
113
|
-
|
|
113
|
+
machine-local raised events. `parent: Machine.parent(events)` requires an
|
|
114
|
+
owner, while `parent: Machine.optionalParent(events)` permits a root and
|
|
115
|
+
exposes an optional owner. `emittedEvents` describes outward ephemeral
|
|
114
116
|
notifications and is never delivered implicitly to a parent.
|
|
115
117
|
- Event tags in `events` and `internalEvents` must be disjoint.
|
|
116
118
|
- Event tags must also be unique within each protocol list.
|
|
@@ -127,9 +129,9 @@ its extra control is required:
|
|
|
127
129
|
externally produced values, `after` for a timer, `logic` for reusable process
|
|
128
130
|
logic, and `child` for a complete child
|
|
129
131
|
statechart. `Machine.invoke({...})` preserves owner state and source channels
|
|
130
|
-
across sibling lifecycle handlers. Inside `.handle(...)`, `self` and
|
|
131
|
-
use the owning definition's exact
|
|
132
|
-
|
|
132
|
+
across sibling lifecycle handlers. Inside `.handle(...)`, `self` and any
|
|
133
|
+
declared `parent` use the owning definition's exact protocols; no intermediate
|
|
134
|
+
definition method is required.
|
|
133
135
|
- Use `Machine.child(id, machine)` for a complete statechart descriptor and
|
|
134
136
|
`Machine.childAddress<Event>(id)` for a low-level process address. A logic
|
|
135
137
|
invocation is addressable only when `Machine.invoke` receives that
|
|
@@ -231,10 +233,7 @@ const States = Machine.states({
|
|
|
231
233
|
}
|
|
232
234
|
})
|
|
233
235
|
|
|
234
|
-
initial: {
|
|
235
|
-
target: (to) => to.Form.initial(),
|
|
236
|
-
resolve: ({ target }) => target((form) => form.Editing.from())
|
|
237
|
-
}
|
|
236
|
+
initial: (to) => to.Form.initial.resolve(({ target }) => target((form) => form.Editing.from()))
|
|
238
237
|
```
|
|
239
238
|
|
|
240
239
|
Schema-less states have the same control semantics as schema-backed states:
|
|
@@ -244,13 +243,11 @@ in snapshots. They do not have a state value:
|
|
|
244
243
|
```ts
|
|
245
244
|
Idle: {
|
|
246
245
|
on: {
|
|
247
|
-
Start:
|
|
248
|
-
|
|
249
|
-
resolve: ({ state, target }) => {
|
|
246
|
+
Start: (to) =>
|
|
247
|
+
to.full.Form.initial.resolve(({ state, target }) => {
|
|
250
248
|
// state: undefined
|
|
251
249
|
return target.from((form) => form.Editing.from())
|
|
252
|
-
}
|
|
253
|
-
})
|
|
250
|
+
})
|
|
254
251
|
}
|
|
255
252
|
}
|
|
256
253
|
|
|
@@ -331,10 +328,7 @@ const States = Machine.states({
|
|
|
331
328
|
const machine = Machine.make({
|
|
332
329
|
states: States.states,
|
|
333
330
|
events: Machine.events(),
|
|
334
|
-
initial: {
|
|
335
|
-
target: (to) => to.Done(),
|
|
336
|
-
resolve: ({ target }) => target.from()
|
|
337
|
-
}
|
|
331
|
+
initial: (to) => to.Done().resolve(({ target }) => target.from())
|
|
338
332
|
}).handle({
|
|
339
333
|
Done: {
|
|
340
334
|
output: () => "done"
|
|
@@ -351,12 +345,12 @@ with `.initial`. This is available on top-level state methods under
|
|
|
351
345
|
`target.branch`; atomic and final state methods do not expose it:
|
|
352
346
|
|
|
353
347
|
```ts
|
|
354
|
-
Open:
|
|
355
|
-
target: (to) => to.full.opened.initial(),
|
|
356
|
-
resolve: ({ target }) => target.from({ teamId: "team-1" })
|
|
357
|
-
})
|
|
348
|
+
Open: (to) => to.full.opened.initial.resolve(({ target }) => target.from({ teamId: "team-1" }))
|
|
358
349
|
```
|
|
359
350
|
|
|
351
|
+
The definition-time `.initial` property is a topology value. The exact
|
|
352
|
+
resolver `target` is still a callable runtime builder.
|
|
353
|
+
|
|
360
354
|
The selected state's own value is passed directly to `initial(value)` or
|
|
361
355
|
constructed inside planning with `initial.from(input)`. A structural selected
|
|
362
356
|
state uses `initial()`.
|
|
@@ -431,12 +425,12 @@ checkout: {
|
|
|
431
425
|
Target it without a value:
|
|
432
426
|
|
|
433
427
|
```ts
|
|
434
|
-
Resume:
|
|
435
|
-
target: (to) => to.history.checkout.exact(),
|
|
436
|
-
resolve: ({ target }) => target()
|
|
437
|
-
})
|
|
428
|
+
Resume: (to) => to.history.checkout.exact.resolve(({ target }) => target())
|
|
438
429
|
```
|
|
439
430
|
|
|
431
|
+
Each declared history leaf is a topology value; the resolver's selected
|
|
432
|
+
history builder remains callable to construct restoration evidence.
|
|
433
|
+
|
|
440
434
|
Deep history restores the complete remembered subtree and its decoded values.
|
|
441
435
|
Shallow history restores only parent and direct-child values. If the remembered
|
|
442
436
|
child is compound, its configured initial child needs a freshly constructed
|
|
@@ -486,6 +480,22 @@ prior effects, machine instances, and timers are not rewound.
|
|
|
486
480
|
| `target.full` | The destination may be under any top-level root | Nothing is inferred for a newly selected root; build its complete active snapshot |
|
|
487
481
|
| `target.history` | The destination is a declared history pseudo-state | Its parent's remembered configuration, or a source-independent complete default containing that owner before the first capture |
|
|
488
482
|
|
|
483
|
+
Definition-time instructions that only identify topology are values:
|
|
484
|
+
`to.none`, `to.full.Flow.initial`, `to.history.Flow.recent`, and
|
|
485
|
+
`to.local.with`. State and choice destinations remain calls, such as
|
|
486
|
+
`to.full.Running()` and `to.local.Routing()`, because those calls select the
|
|
487
|
+
node. Resolver-time builders also remain callable because they construct and,
|
|
488
|
+
for named branches, brand runtime evidence such as `select.unchanged()`.
|
|
489
|
+
|
|
490
|
+
Use `to.local.with` when a descendant transition updates the nearest
|
|
491
|
+
schema-backed compound value while retaining that same compound scope:
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
Play: (to) =>
|
|
495
|
+
to.local.with.resolve(({ containingState, target }) =>
|
|
496
|
+
target.from({ ...containingState, playing: true }, (flow) => flow.Playing.from()))
|
|
497
|
+
```
|
|
498
|
+
|
|
489
499
|
Entering an inactive parallel state through `target.local` or `target.branch`
|
|
490
500
|
requires a complete callback with one selection per region. A parallel state
|
|
491
501
|
that is already active remains partially addressable through `target.branch`;
|
|
@@ -497,11 +507,16 @@ sets. A `target.full` result with the same active paths can update values withou
|
|
|
497
507
|
exiting shared states. To force the source to exit and enter again:
|
|
498
508
|
|
|
499
509
|
```ts
|
|
500
|
-
Refresh:
|
|
501
|
-
target
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
510
|
+
Refresh: (to) =>
|
|
511
|
+
to.full.Ready().resolve(({ state, target }) => target.from({ value: state.value }), { reenter: true })
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
When no resolver is needed, use the selected target directly and append
|
|
515
|
+
`.reenter()` only when restart semantics are intentional:
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
Finish: (to) => to.full.Done()
|
|
519
|
+
Restart: (to) => to.none.reenter()
|
|
505
520
|
```
|
|
506
521
|
|
|
507
522
|
Do not use `target.full` merely because it is easiest to discover. Prefer the
|
|
@@ -561,8 +576,9 @@ only schema-backed paths; use `matches` or `getSnapshot` for any active path.
|
|
|
561
576
|
`context.containingState` is the immediate typed state value (`undefined` at a
|
|
562
577
|
root or when that state is schema-less). `context.ancestors` contains only
|
|
563
578
|
valued structural ancestors. This is separate from `context.parent`, which is
|
|
564
|
-
|
|
565
|
-
|
|
579
|
+
present only when declared by the machine. `Machine.parent` makes it a required
|
|
580
|
+
owning-machine target; `Machine.optionalParent` makes it a target or
|
|
581
|
+
`undefined`. Use full state paths when another ancestor value is needed:
|
|
566
582
|
|
|
567
583
|
```ts
|
|
568
584
|
ancestors["Route.Ready"]
|
|
@@ -578,16 +594,14 @@ Event, `always`, and `onDone` transition contexts include a fully typed
|
|
|
578
594
|
microstep, before any selected transition is applied:
|
|
579
595
|
|
|
580
596
|
```ts
|
|
581
|
-
BufferReady:
|
|
582
|
-
branches
|
|
597
|
+
BufferReady: (to) =>
|
|
598
|
+
to.branches({
|
|
583
599
|
online: { target: to.local.Playing() },
|
|
584
|
-
unchanged: { target: to.none
|
|
585
|
-
}),
|
|
586
|
-
resolve: ({ snapshot, select }) =>
|
|
600
|
+
unchanged: { target: to.none }
|
|
601
|
+
}).resolve(({ snapshot, select }) =>
|
|
587
602
|
States.matches(snapshot, "Player.Network.Online")
|
|
588
603
|
? select.online.from()
|
|
589
604
|
: select.unchanged()
|
|
590
|
-
})
|
|
591
605
|
```
|
|
592
606
|
|
|
593
607
|
Use the existing `States.matches`, `States.get`, `States.getWithParents`, and
|
|
@@ -622,13 +636,11 @@ When sibling state payloads share fields, destructure away the source
|
|
|
622
636
|
discriminator and construct the destination through its target builder:
|
|
623
637
|
|
|
624
638
|
```ts
|
|
625
|
-
Submit:
|
|
626
|
-
|
|
627
|
-
resolve: ({ state, target }) => {
|
|
639
|
+
Submit: (to) =>
|
|
640
|
+
to.local.Saving().resolve(({ state, target }) => {
|
|
628
641
|
const { _tag: _, ...fields } = state
|
|
629
642
|
return target.from({ ...fields, attempt: 1 })
|
|
630
|
-
}
|
|
631
|
-
})
|
|
643
|
+
})
|
|
632
644
|
```
|
|
633
645
|
|
|
634
646
|
The target schema remains responsible for defaults, transforms, refinements,
|
|
@@ -641,45 +653,66 @@ A transition declares every possible branch and resolves the selected target
|
|
|
641
653
|
synchronously:
|
|
642
654
|
|
|
643
655
|
```ts
|
|
644
|
-
Submit:
|
|
645
|
-
branches
|
|
656
|
+
Submit: (to) =>
|
|
657
|
+
to.branches({
|
|
646
658
|
valid: { target: to.local.Saving() },
|
|
647
|
-
invalid: { target: to.none
|
|
648
|
-
}),
|
|
649
|
-
resolve: ({ state, select }) => state.valid
|
|
659
|
+
invalid: { target: to.none }
|
|
660
|
+
}).resolve(({ state, select }) => state.valid
|
|
650
661
|
? select.valid.from({ draft: state.draft })
|
|
651
662
|
: select.invalid()
|
|
652
|
-
})
|
|
653
663
|
```
|
|
654
664
|
|
|
655
665
|
Every installed event, `always`, `onDone`, choice, and invoke lifecycle handler
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
666
|
+
receives a bound `to` selector. A direct transition selects one target and calls
|
|
667
|
+
its `resolve` method. A branching transition calls `to.branches` with every
|
|
668
|
+
possible target, then uses ordinary TypeScript control flow in `resolve` to return one
|
|
659
669
|
typed `select` builder. Branch keys are stable testing and inspection identities;
|
|
660
670
|
an optional `title` controls presentation and otherwise defaults to the key.
|
|
661
|
-
Selecting a branch whose target is `to.none
|
|
671
|
+
Selecting a branch whose target is `to.none` handles the transition without a
|
|
662
672
|
destination while retaining queued commands, raised events, and emitted events.
|
|
663
673
|
|
|
674
|
+
Set `declinable: true` only when the resolver may decide that its transition is
|
|
675
|
+
not enabled. The flag adds a typed `decline()` capability to that resolver and
|
|
676
|
+
permits its opaque result:
|
|
677
|
+
|
|
678
|
+
```ts
|
|
679
|
+
Submit: (to) =>
|
|
680
|
+
to.branches({
|
|
681
|
+
accepted: { target: to.local.Saving() },
|
|
682
|
+
consumed: { target: to.none }
|
|
683
|
+
}).resolve(({ event, select, decline }) => {
|
|
684
|
+
if (!belongsToThisState(event)) return decline()
|
|
685
|
+
return event.consume ? select.consumed() : select.accepted.from()
|
|
686
|
+
}, { declinable: true })
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
Declining discards that resolver's enqueue buffer and resumes hierarchical
|
|
690
|
+
event or eventless selection at the next eligible ancestor. If no candidate
|
|
691
|
+
accepts, the trigger is unhandled. This is deliberately different from
|
|
692
|
+
`to.none`, which consumes the trigger. `decline()` is absent and its result is
|
|
693
|
+
rejected unless the literal flag is present. Choice and initial routing remain
|
|
694
|
+
total and cannot decline. Static inspection exposes the distinction through
|
|
695
|
+
`TransitionDefinition.acceptance` without executing resolver code. Completion
|
|
696
|
+
and invocation outcomes have no ancestor candidate; declining one ignores that
|
|
697
|
+
lifecycle occurrence and leaves the current configuration active.
|
|
698
|
+
|
|
664
699
|
The `branches` callback runs once when handlers are installed. Its record uses
|
|
665
700
|
the deterministic ECMAScript property order for presentation and `branchIndex`;
|
|
666
701
|
array-index and symbol keys are rejected. Treat the string key as semantic:
|
|
667
702
|
reordering named properties may change their display index, but visualizers,
|
|
668
703
|
coverage, and trace verification identify each branch by its key.
|
|
669
704
|
|
|
670
|
-
`reenter: true` remains meaningful with `to.none
|
|
705
|
+
`reenter: true` remains meaningful with `to.none`: the source exits and
|
|
671
706
|
enters again while its logical configuration is retained.
|
|
672
707
|
|
|
673
708
|
Closed statechart and machine operations use `enqueue`:
|
|
674
709
|
|
|
675
710
|
```ts
|
|
676
|
-
Submit:
|
|
677
|
-
|
|
678
|
-
resolve: ({ target }, enqueue) => {
|
|
711
|
+
Submit: (to) =>
|
|
712
|
+
to.local.Saving().resolve(({ target }, enqueue) => {
|
|
679
713
|
enqueue.emit(Emissions.SaveRequested())
|
|
680
714
|
return target.from()
|
|
681
|
-
}
|
|
682
|
-
})
|
|
715
|
+
})
|
|
683
716
|
```
|
|
684
717
|
|
|
685
718
|
Declare emission constructors separately from machine inputs:
|
|
@@ -768,20 +801,15 @@ export const ParentEvents = Machine.events(ChildFinished)
|
|
|
768
801
|
|
|
769
802
|
const child = Machine.make({
|
|
770
803
|
events: ChildEvents,
|
|
771
|
-
|
|
804
|
+
parent: Machine.parent(ParentEvents),
|
|
772
805
|
// ...
|
|
773
806
|
}).handle({
|
|
774
807
|
Working: {
|
|
775
808
|
on: {
|
|
776
|
-
Finish:
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
enqueue.sendTo(parent, ParentEvents.ChildFinished())
|
|
781
|
-
}
|
|
782
|
-
return undefined
|
|
783
|
-
}
|
|
784
|
-
})
|
|
809
|
+
Finish: (to) =>
|
|
810
|
+
to.none.resolve(({ parent }, enqueue) => {
|
|
811
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished())
|
|
812
|
+
})
|
|
785
813
|
}
|
|
786
814
|
}
|
|
787
815
|
})
|
|
@@ -792,12 +820,14 @@ const parent = Machine.make({
|
|
|
792
820
|
})
|
|
793
821
|
```
|
|
794
822
|
|
|
795
|
-
Invoking the child under a parent that lacks any required
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
823
|
+
Invoking the child under a parent that lacks any required parent event is a
|
|
824
|
+
type error. Within child handlers, `parent` accepts only that protocol and is
|
|
825
|
+
not optional. Root APIs reject the machine. Use
|
|
826
|
+
`Machine.optionalParent(ParentEvents)` instead when the same definition must
|
|
827
|
+
also run as a root; then `parent` is optional. With no declaration, callbacks
|
|
828
|
+
have no `parent` property. `self` accepts the machine's public inputs. Both
|
|
829
|
+
targets are minimal `MachineTarget<Event>` values. Neither machine target is a
|
|
830
|
+
structural state value; use
|
|
801
831
|
`containingState` and `ancestors` for statechart ancestry.
|
|
802
832
|
|
|
803
833
|
Atom-backed machines retain the same transient semantics. Use
|
|
@@ -856,10 +886,7 @@ const definition = Machine.make({
|
|
|
856
886
|
states: States.states,
|
|
857
887
|
events: Events,
|
|
858
888
|
internalEvents: InternalEvents,
|
|
859
|
-
initial: {
|
|
860
|
-
target: (to) => to.Idle(),
|
|
861
|
-
resolve: ({ target }) => target.from()
|
|
862
|
-
}
|
|
889
|
+
initial: (to) => to.Idle().resolve(({ target }) => target.from())
|
|
863
890
|
})
|
|
864
891
|
```
|
|
865
892
|
|
|
@@ -915,14 +942,9 @@ receive the typed Effect channels and can transition directly:
|
|
|
915
942
|
invoke: Machine.invoke({
|
|
916
943
|
id: "save",
|
|
917
944
|
effect: () => SaveService.save(draft),
|
|
918
|
-
onDone:
|
|
919
|
-
|
|
920
|
-
resolve
|
|
921
|
-
}),
|
|
922
|
-
onFailure: Machine.transition({
|
|
923
|
-
target: (to) => to.full.SaveFailed(),
|
|
924
|
-
resolve: ({ error, target }) => target.from({ message: error.message })
|
|
925
|
-
})
|
|
945
|
+
onDone: (to) => to.full.Saved().resolve(({ output, target }) => target.from({ entry: output })),
|
|
946
|
+
onFailure: (to) =>
|
|
947
|
+
to.full.SaveFailed().resolve(({ error, target }) => target.from({ message: error.message }))
|
|
926
948
|
})
|
|
927
949
|
```
|
|
928
950
|
|
|
@@ -945,17 +967,12 @@ handles normal Stream completion and `onFailure` handles the typed Stream error:
|
|
|
945
967
|
invoke: Machine.invoke({
|
|
946
968
|
id: "broadcast-channel",
|
|
947
969
|
stream: () => messages,
|
|
948
|
-
onElement:
|
|
949
|
-
|
|
950
|
-
resolve: ({ element }, enqueue) => {
|
|
970
|
+
onElement: (to) =>
|
|
971
|
+
to.none.resolve(({ element }, enqueue) => {
|
|
951
972
|
enqueue.raise(Events.MessageReceived({ message: element }))
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
onFailure: Machine.transition({
|
|
956
|
-
target: (to) => to.full.Disconnected(),
|
|
957
|
-
resolve: ({ error, target }) => target.from({ error })
|
|
958
|
-
})
|
|
973
|
+
}),
|
|
974
|
+
onDone: (to) => to.none,
|
|
975
|
+
onFailure: (to) => to.full.Disconnected().resolve(({ error, target }) => target.from({ error }))
|
|
959
976
|
})
|
|
960
977
|
```
|
|
961
978
|
|
|
@@ -964,10 +981,9 @@ after the selected parent macrostep commits. Exiting or reentering the owner
|
|
|
964
981
|
interrupts the Stream and runs its finalizers. A later entry starts a fresh
|
|
965
982
|
Stream. Stream defects and self-interruption fail the owning machine.
|
|
966
983
|
|
|
967
|
-
|
|
968
|
-
when
|
|
969
|
-
|
|
970
|
-
named branches, or reentry.
|
|
984
|
+
Use `to.none` when a transition keeps the current configuration. Call
|
|
985
|
+
`to.none.resolve(...)` when it also enqueues commands; a block resolver may
|
|
986
|
+
omit its return because it is contextually typed to return `undefined`.
|
|
971
987
|
|
|
972
988
|
When a source function reads `state`, `containingState`, `ancestors`, or the entry `event`,
|
|
973
989
|
`Machine.invoke` infers that owner context and the returned Effect's output,
|
|
@@ -977,54 +993,40 @@ error, and service channels together. No return annotation is needed:
|
|
|
977
993
|
invoke: Machine.invoke({
|
|
978
994
|
id: "load",
|
|
979
995
|
effect: ({ state }) => LoadService.load(state.userId),
|
|
980
|
-
onDone:
|
|
981
|
-
|
|
982
|
-
resolve: ({ output, target }) => target.from({ user: output })
|
|
983
|
-
}),
|
|
984
|
-
onFailure: Machine.transition({
|
|
985
|
-
target: (to) => to.full.LoadFailed(),
|
|
986
|
-
resolve: ({ error, target }) => target.from({ error })
|
|
987
|
-
})
|
|
996
|
+
onDone: (to) => to.full.Loaded().resolve(({ output, target }) => target.from({ user: output })),
|
|
997
|
+
onFailure: (to) => to.full.LoadFailed().resolve(({ error, target }) => target.from({ error }))
|
|
988
998
|
})
|
|
989
999
|
```
|
|
990
1000
|
|
|
991
1001
|
Inside `.handle(...)`, the constructor receives the owning machine's public
|
|
992
|
-
input and
|
|
1002
|
+
input and declared parent protocol contextually. Sources and lifecycle handlers
|
|
993
1003
|
can send through `self` and `parent` without naming the definition:
|
|
994
1004
|
|
|
995
1005
|
```ts
|
|
996
1006
|
const machine = Machine.make({
|
|
997
1007
|
events: Commands,
|
|
998
1008
|
internalEvents: InternalEvents,
|
|
999
|
-
|
|
1009
|
+
parent: Machine.parent(ParentEvents),
|
|
1000
1010
|
// ...
|
|
1001
1011
|
}).handle({
|
|
1002
1012
|
Saving: {
|
|
1003
1013
|
invoke: Machine.invoke({
|
|
1004
1014
|
id: "notify-parent",
|
|
1005
1015
|
effect: () => saveDocument,
|
|
1006
|
-
onDone:
|
|
1007
|
-
|
|
1008
|
-
resolve: ({ parent, self }, enqueue) => {
|
|
1016
|
+
onDone: (to) =>
|
|
1017
|
+
to.none.resolve(({ parent, self }, enqueue) => {
|
|
1009
1018
|
enqueue.sendTo(self, Commands.Save())
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
return undefined
|
|
1014
|
-
}
|
|
1015
|
-
}),
|
|
1016
|
-
onFailure: Machine.transition({
|
|
1017
|
-
target: (to) => to.none(),
|
|
1018
|
-
resolve: () => undefined
|
|
1019
|
-
})
|
|
1019
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
1020
|
+
}),
|
|
1021
|
+
onFailure: (to) => to.none
|
|
1020
1022
|
})
|
|
1021
1023
|
}
|
|
1022
1024
|
})
|
|
1023
1025
|
```
|
|
1024
1026
|
|
|
1025
|
-
The
|
|
1026
|
-
definition is
|
|
1027
|
-
when lifecycle handlers do not need source-derived context.
|
|
1027
|
+
The standard `Machine.invoke(...)` form retains the owning machine protocols
|
|
1028
|
+
even when the definition is named separately. A direct `invoke: { ... }` object
|
|
1029
|
+
remains available when lifecycle handlers do not need source-derived context.
|
|
1028
1030
|
|
|
1029
1031
|
A cancellable timer uses the same object:
|
|
1030
1032
|
|
|
@@ -1032,10 +1034,7 @@ A cancellable timer uses the same object:
|
|
|
1032
1034
|
invoke: Machine.invoke({
|
|
1033
1035
|
id: "clear-status",
|
|
1034
1036
|
after: "3 seconds",
|
|
1035
|
-
onDone:
|
|
1036
|
-
target: (to) => to.full.Clear(),
|
|
1037
|
-
resolve: ({ target }) => target()
|
|
1038
|
-
})
|
|
1037
|
+
onDone: (to) => to.full.Clear().resolve(({ target }) => target())
|
|
1039
1038
|
})
|
|
1040
1039
|
```
|
|
1041
1040
|
|
|
@@ -1062,10 +1061,7 @@ Invoke it from its owning state:
|
|
|
1062
1061
|
invoke: Machine.invoke({
|
|
1063
1062
|
child: Editor,
|
|
1064
1063
|
input: editorInput,
|
|
1065
|
-
onDone:
|
|
1066
|
-
target: (to) => to.full.EditorDone(),
|
|
1067
|
-
resolve: ({ output, target }) => target.from({ output })
|
|
1068
|
-
})
|
|
1064
|
+
onDone: (to) => to.full.EditorDone().resolve(({ output, target }) => target.from({ output }))
|
|
1069
1065
|
})
|
|
1070
1066
|
```
|
|
1071
1067
|
|
|
@@ -1313,9 +1309,10 @@ const step = yield * probe.sendAndAwait(event)
|
|
|
1313
1309
|
```
|
|
1314
1310
|
|
|
1315
1311
|
Inspect `step.before`, `step.after`, `step.plan`, `step.handled`, and
|
|
1316
|
-
`step.configurationChanged`. An ignored event
|
|
1317
|
-
|
|
1318
|
-
|
|
1312
|
+
`step.configurationChanged`. An ignored event, including one for which every
|
|
1313
|
+
eligible candidate declines, has `handled: false` and an empty microstep list,
|
|
1314
|
+
but still completes its acknowledgement. A targetless handler has
|
|
1315
|
+
`handled: true` even if its before and after snapshots are equal.
|
|
1319
1316
|
|
|
1320
1317
|
Do not use a probe as a substitute for a domain completion event. The
|
|
1321
1318
|
acknowledgement covers the submitted event's synchronous macrostep, state
|
|
@@ -1385,10 +1382,7 @@ reference model when correctness of the expected behavior matters.
|
|
|
1385
1382
|
Select the initial root separately from constructing its value:
|
|
1386
1383
|
|
|
1387
1384
|
```ts
|
|
1388
|
-
initial: {
|
|
1389
|
-
target: (to) => to.Idle(),
|
|
1390
|
-
resolve: ({ target }) => target.from()
|
|
1391
|
-
}
|
|
1385
|
+
initial: (to) => to.Idle().resolve(({ target }) => target.from())
|
|
1392
1386
|
```
|
|
1393
1387
|
|
|
1394
1388
|
### Invoked child expects events not accepted by the parent
|
|
@@ -1399,13 +1393,16 @@ the parent's public events:
|
|
|
1399
1393
|
```ts
|
|
1400
1394
|
export const ChildParentEvents = Machine.events(ChildFinished)
|
|
1401
1395
|
|
|
1402
|
-
// child
|
|
1403
|
-
|
|
1396
|
+
// child-only machine
|
|
1397
|
+
parent: Machine.parent(ChildParentEvents)
|
|
1404
1398
|
|
|
1405
1399
|
// parent
|
|
1406
1400
|
events: Machine.events(Submit, ChildParentEvents)
|
|
1407
1401
|
```
|
|
1408
1402
|
|
|
1403
|
+
Use `Machine.optionalParent(ChildParentEvents)` only when the child is also a
|
|
1404
|
+
valid independent root and narrow `parent` before sending.
|
|
1405
|
+
|
|
1409
1406
|
### An internal event is rejected by `send`
|
|
1410
1407
|
|
|
1411
1408
|
This is intentional. Public input boundaries accept only schemas declared in
|