@typeonce/effect-machine 0.15.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 +115 -95
- package/dist/Machine.d.ts +464 -337
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +72 -107
- 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/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 +16 -4
- 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 +8 -6
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +308 -71
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +26 -4
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +76 -31
- 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.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 +20 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +33 -6
- 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 +8 -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 +13 -12
- 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 +182 -159
- package/package.json +1 -1
- package/src/Machine.ts +1073 -700
- package/src/internal/machine/activities.ts +7 -0
- package/src/internal/machine/atom.ts +20 -15
- package/src/internal/machine/cluster.ts +14 -9
- package/src/internal/machine/executionPlan.ts +14 -4
- package/src/internal/machine/invocation.ts +39 -4
- package/src/internal/machine/invocationEvent.ts +16 -0
- package/src/internal/machine/machine.ts +460 -86
- package/src/internal/machine/planner.ts +106 -30
- package/src/internal/machine/runtime.ts +61 -25
- package/src/internal/machine/stateDefinition.ts +39 -0
- package/src/internal/machine/topology.ts +59 -2
- package/src/internal/testing/machine/finiteModel.ts +18 -21
- package/src/internal/testing/machine/transitionCoverage.ts +8 -2
- package/src/internal/testing/machine/verification.ts +11 -0
- package/src/testing/MachineTest.ts +16 -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.
|
|
@@ -123,12 +125,13 @@ its extra control is required:
|
|
|
123
125
|
- Bind a shared Atom runtime once with `AtomMachine.bind(runtime)`, then use the
|
|
124
126
|
returned `make` or `resume`. Use `AtomMachine.make(machine)` and
|
|
125
127
|
`AtomMachine.resume(machine, snapshot)` for service-free machines.
|
|
126
|
-
- Use one invocation object: `effect` for one-shot work, `
|
|
127
|
-
`
|
|
128
|
+
- Use one invocation object: `effect` for one-shot work, `stream` for repeated
|
|
129
|
+
externally produced values, `after` for a timer, `logic` for reusable process
|
|
130
|
+
logic, and `child` for a complete child
|
|
128
131
|
statechart. `Machine.invoke({...})` preserves owner state and source channels
|
|
129
|
-
across sibling lifecycle handlers. Inside `.handle(...)`, `self` and
|
|
130
|
-
use the owning definition's exact
|
|
131
|
-
|
|
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.
|
|
132
135
|
- Use `Machine.child(id, machine)` for a complete statechart descriptor and
|
|
133
136
|
`Machine.childAddress<Event>(id)` for a low-level process address. A logic
|
|
134
137
|
invocation is addressable only when `Machine.invoke` receives that
|
|
@@ -230,10 +233,7 @@ const States = Machine.states({
|
|
|
230
233
|
}
|
|
231
234
|
})
|
|
232
235
|
|
|
233
|
-
initial: {
|
|
234
|
-
target: (to) => to.Form.initial(),
|
|
235
|
-
resolve: ({ target }) => target((form) => form.Editing.from())
|
|
236
|
-
}
|
|
236
|
+
initial: (to) => to.Form.initial.resolve(({ target }) => target((form) => form.Editing.from()))
|
|
237
237
|
```
|
|
238
238
|
|
|
239
239
|
Schema-less states have the same control semantics as schema-backed states:
|
|
@@ -243,13 +243,11 @@ in snapshots. They do not have a state value:
|
|
|
243
243
|
```ts
|
|
244
244
|
Idle: {
|
|
245
245
|
on: {
|
|
246
|
-
Start:
|
|
247
|
-
|
|
248
|
-
resolve: ({ state, target }) => {
|
|
246
|
+
Start: (to) =>
|
|
247
|
+
to.full.Form.initial.resolve(({ state, target }) => {
|
|
249
248
|
// state: undefined
|
|
250
249
|
return target.from((form) => form.Editing.from())
|
|
251
|
-
}
|
|
252
|
-
})
|
|
250
|
+
})
|
|
253
251
|
}
|
|
254
252
|
}
|
|
255
253
|
|
|
@@ -330,10 +328,7 @@ const States = Machine.states({
|
|
|
330
328
|
const machine = Machine.make({
|
|
331
329
|
states: States.states,
|
|
332
330
|
events: Machine.events(),
|
|
333
|
-
initial: {
|
|
334
|
-
target: (to) => to.Done(),
|
|
335
|
-
resolve: ({ target }) => target.from()
|
|
336
|
-
}
|
|
331
|
+
initial: (to) => to.Done().resolve(({ target }) => target.from())
|
|
337
332
|
}).handle({
|
|
338
333
|
Done: {
|
|
339
334
|
output: () => "done"
|
|
@@ -350,12 +345,12 @@ with `.initial`. This is available on top-level state methods under
|
|
|
350
345
|
`target.branch`; atomic and final state methods do not expose it:
|
|
351
346
|
|
|
352
347
|
```ts
|
|
353
|
-
Open:
|
|
354
|
-
target: (to) => to.full.opened.initial(),
|
|
355
|
-
resolve: ({ target }) => target.from({ teamId: "team-1" })
|
|
356
|
-
})
|
|
348
|
+
Open: (to) => to.full.opened.initial.resolve(({ target }) => target.from({ teamId: "team-1" }))
|
|
357
349
|
```
|
|
358
350
|
|
|
351
|
+
The definition-time `.initial` property is a topology value. The exact
|
|
352
|
+
resolver `target` is still a callable runtime builder.
|
|
353
|
+
|
|
359
354
|
The selected state's own value is passed directly to `initial(value)` or
|
|
360
355
|
constructed inside planning with `initial.from(input)`. A structural selected
|
|
361
356
|
state uses `initial()`.
|
|
@@ -430,12 +425,12 @@ checkout: {
|
|
|
430
425
|
Target it without a value:
|
|
431
426
|
|
|
432
427
|
```ts
|
|
433
|
-
Resume:
|
|
434
|
-
target: (to) => to.history.checkout.exact(),
|
|
435
|
-
resolve: ({ target }) => target()
|
|
436
|
-
})
|
|
428
|
+
Resume: (to) => to.history.checkout.exact.resolve(({ target }) => target())
|
|
437
429
|
```
|
|
438
430
|
|
|
431
|
+
Each declared history leaf is a topology value; the resolver's selected
|
|
432
|
+
history builder remains callable to construct restoration evidence.
|
|
433
|
+
|
|
439
434
|
Deep history restores the complete remembered subtree and its decoded values.
|
|
440
435
|
Shallow history restores only parent and direct-child values. If the remembered
|
|
441
436
|
child is compound, its configured initial child needs a freshly constructed
|
|
@@ -485,6 +480,22 @@ prior effects, machine instances, and timers are not rewound.
|
|
|
485
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 |
|
|
486
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 |
|
|
487
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
|
+
|
|
488
499
|
Entering an inactive parallel state through `target.local` or `target.branch`
|
|
489
500
|
requires a complete callback with one selection per region. A parallel state
|
|
490
501
|
that is already active remains partially addressable through `target.branch`;
|
|
@@ -496,11 +507,16 @@ sets. A `target.full` result with the same active paths can update values withou
|
|
|
496
507
|
exiting shared states. To force the source to exit and enter again:
|
|
497
508
|
|
|
498
509
|
```ts
|
|
499
|
-
Refresh:
|
|
500
|
-
target
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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()
|
|
504
520
|
```
|
|
505
521
|
|
|
506
522
|
Do not use `target.full` merely because it is easiest to discover. Prefer the
|
|
@@ -560,8 +576,9 @@ only schema-backed paths; use `matches` or `getSnapshot` for any active path.
|
|
|
560
576
|
`context.containingState` is the immediate typed state value (`undefined` at a
|
|
561
577
|
root or when that state is schema-less). `context.ancestors` contains only
|
|
562
578
|
valued structural ancestors. This is separate from `context.parent`, which is
|
|
563
|
-
|
|
564
|
-
|
|
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:
|
|
565
582
|
|
|
566
583
|
```ts
|
|
567
584
|
ancestors["Route.Ready"]
|
|
@@ -577,23 +594,14 @@ Event, `always`, and `onDone` transition contexts include a fully typed
|
|
|
577
594
|
microstep, before any selected transition is applied:
|
|
578
595
|
|
|
579
596
|
```ts
|
|
580
|
-
BufferReady:
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
target: (to) => to.local.Playing(),
|
|
589
|
-
resolve: ({ target }) => target.from()
|
|
590
|
-
})
|
|
591
|
-
],
|
|
592
|
-
otherwise: {
|
|
593
|
-
target: (to) => to.none(),
|
|
594
|
-
resolve: () => undefined
|
|
595
|
-
}
|
|
596
|
-
})
|
|
597
|
+
BufferReady: (to) =>
|
|
598
|
+
to.branches({
|
|
599
|
+
online: { target: to.local.Playing() },
|
|
600
|
+
unchanged: { target: to.none }
|
|
601
|
+
}).resolve(({ snapshot, select }) =>
|
|
602
|
+
States.matches(snapshot, "Player.Network.Online")
|
|
603
|
+
? select.online.from()
|
|
604
|
+
: select.unchanged()
|
|
597
605
|
```
|
|
598
606
|
|
|
599
607
|
Use the existing `States.matches`, `States.get`, `States.getWithParents`, and
|
|
@@ -628,13 +636,11 @@ When sibling state payloads share fields, destructure away the source
|
|
|
628
636
|
discriminator and construct the destination through its target builder:
|
|
629
637
|
|
|
630
638
|
```ts
|
|
631
|
-
Submit:
|
|
632
|
-
|
|
633
|
-
resolve: ({ state, target }) => {
|
|
639
|
+
Submit: (to) =>
|
|
640
|
+
to.local.Saving().resolve(({ state, target }) => {
|
|
634
641
|
const { _tag: _, ...fields } = state
|
|
635
642
|
return target.from({ ...fields, attempt: 1 })
|
|
636
|
-
}
|
|
637
|
-
})
|
|
643
|
+
})
|
|
638
644
|
```
|
|
639
645
|
|
|
640
646
|
The target schema remains responsible for defaults, transforms, refinements,
|
|
@@ -647,45 +653,66 @@ A transition declares every possible branch and resolves the selected target
|
|
|
647
653
|
synchronously:
|
|
648
654
|
|
|
649
655
|
```ts
|
|
650
|
-
Submit:
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
})
|
|
658
|
-
],
|
|
659
|
-
otherwise: {
|
|
660
|
-
target: (to) => to.none(),
|
|
661
|
-
resolve: () => undefined
|
|
662
|
-
}
|
|
663
|
-
})
|
|
656
|
+
Submit: (to) =>
|
|
657
|
+
to.branches({
|
|
658
|
+
valid: { target: to.local.Saving() },
|
|
659
|
+
invalid: { target: to.none }
|
|
660
|
+
}).resolve(({ state, select }) => state.valid
|
|
661
|
+
? select.valid.from({ draft: state.draft })
|
|
662
|
+
: select.invalid()
|
|
664
663
|
```
|
|
665
664
|
|
|
666
665
|
Every installed event, `always`, `onDone`, choice, and invoke lifecycle handler
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
|
669
|
+
typed `select` builder. Branch keys are stable testing and inspection identities;
|
|
670
|
+
an optional `title` controls presentation and otherwise defaults to the key.
|
|
671
|
+
Selecting a branch whose target is `to.none` handles the transition without a
|
|
672
|
+
destination while retaining queued commands, raised events, and emitted events.
|
|
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
|
+
|
|
699
|
+
The `branches` callback runs once when handlers are installed. Its record uses
|
|
700
|
+
the deterministic ECMAScript property order for presentation and `branchIndex`;
|
|
701
|
+
array-index and symbol keys are rejected. Treat the string key as semantic:
|
|
702
|
+
reordering named properties may change their display index, but visualizers,
|
|
703
|
+
coverage, and trace verification identify each branch by its key.
|
|
704
|
+
|
|
705
|
+
`reenter: true` remains meaningful with `to.none`: the source exits and
|
|
677
706
|
enters again while its logical configuration is retained.
|
|
678
707
|
|
|
679
708
|
Closed statechart and machine operations use `enqueue`:
|
|
680
709
|
|
|
681
710
|
```ts
|
|
682
|
-
Submit:
|
|
683
|
-
|
|
684
|
-
resolve: ({ target }, enqueue) => {
|
|
711
|
+
Submit: (to) =>
|
|
712
|
+
to.local.Saving().resolve(({ target }, enqueue) => {
|
|
685
713
|
enqueue.emit(Emissions.SaveRequested())
|
|
686
714
|
return target.from()
|
|
687
|
-
}
|
|
688
|
-
})
|
|
715
|
+
})
|
|
689
716
|
```
|
|
690
717
|
|
|
691
718
|
Declare emission constructors separately from machine inputs:
|
|
@@ -774,20 +801,15 @@ export const ParentEvents = Machine.events(ChildFinished)
|
|
|
774
801
|
|
|
775
802
|
const child = Machine.make({
|
|
776
803
|
events: ChildEvents,
|
|
777
|
-
|
|
804
|
+
parent: Machine.parent(ParentEvents),
|
|
778
805
|
// ...
|
|
779
806
|
}).handle({
|
|
780
807
|
Working: {
|
|
781
808
|
on: {
|
|
782
|
-
Finish:
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
enqueue.sendTo(parent, ParentEvents.ChildFinished())
|
|
787
|
-
}
|
|
788
|
-
return undefined
|
|
789
|
-
}
|
|
790
|
-
})
|
|
809
|
+
Finish: (to) =>
|
|
810
|
+
to.none.resolve(({ parent }, enqueue) => {
|
|
811
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished())
|
|
812
|
+
})
|
|
791
813
|
}
|
|
792
814
|
}
|
|
793
815
|
})
|
|
@@ -798,12 +820,14 @@ const parent = Machine.make({
|
|
|
798
820
|
})
|
|
799
821
|
```
|
|
800
822
|
|
|
801
|
-
Invoking the child under a parent that lacks any required
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
|
807
831
|
`containingState` and `ancestors` for statechart ancestry.
|
|
808
832
|
|
|
809
833
|
Atom-backed machines retain the same transient semantics. Use
|
|
@@ -862,10 +886,7 @@ const definition = Machine.make({
|
|
|
862
886
|
states: States.states,
|
|
863
887
|
events: Events,
|
|
864
888
|
internalEvents: InternalEvents,
|
|
865
|
-
initial: {
|
|
866
|
-
target: (to) => to.Idle(),
|
|
867
|
-
resolve: ({ target }) => target.from()
|
|
868
|
-
}
|
|
889
|
+
initial: (to) => to.Idle().resolve(({ target }) => target.from())
|
|
869
890
|
})
|
|
870
891
|
```
|
|
871
892
|
|
|
@@ -921,14 +942,9 @@ receive the typed Effect channels and can transition directly:
|
|
|
921
942
|
invoke: Machine.invoke({
|
|
922
943
|
id: "save",
|
|
923
944
|
effect: () => SaveService.save(draft),
|
|
924
|
-
onDone:
|
|
925
|
-
|
|
926
|
-
resolve
|
|
927
|
-
}),
|
|
928
|
-
onFailure: Machine.transition({
|
|
929
|
-
target: (to) => to.full.SaveFailed(),
|
|
930
|
-
resolve: ({ error, target }) => target.from({ message: error.message })
|
|
931
|
-
})
|
|
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 }))
|
|
932
948
|
})
|
|
933
949
|
```
|
|
934
950
|
|
|
@@ -943,6 +959,32 @@ needs `state`, `containingState`, `ancestors`, or the entry `event`. Source cons
|
|
|
943
959
|
errors, defects, and interruption are machine failures rather than a second
|
|
944
960
|
phase in `onFailure`.
|
|
945
961
|
|
|
962
|
+
Use a Stream invocation for repeated values that are not themselves machine
|
|
963
|
+
events. `onElement` maps each value into an owner transition, while `onDone`
|
|
964
|
+
handles normal Stream completion and `onFailure` handles the typed Stream error:
|
|
965
|
+
|
|
966
|
+
```ts
|
|
967
|
+
invoke: Machine.invoke({
|
|
968
|
+
id: "broadcast-channel",
|
|
969
|
+
stream: () => messages,
|
|
970
|
+
onElement: (to) =>
|
|
971
|
+
to.none.resolve(({ element }, enqueue) => {
|
|
972
|
+
enqueue.raise(Events.MessageReceived({ message: element }))
|
|
973
|
+
}),
|
|
974
|
+
onDone: (to) => to.none,
|
|
975
|
+
onFailure: (to) => to.full.Disconnected().resolve(({ error, target }) => target.from({ error }))
|
|
976
|
+
})
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
Element delivery is owner-scoped and backpressured: the Stream pulls again only
|
|
980
|
+
after the selected parent macrostep commits. Exiting or reentering the owner
|
|
981
|
+
interrupts the Stream and runs its finalizers. A later entry starts a fresh
|
|
982
|
+
Stream. Stream defects and self-interruption fail the owning machine.
|
|
983
|
+
|
|
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`.
|
|
987
|
+
|
|
946
988
|
When a source function reads `state`, `containingState`, `ancestors`, or the entry `event`,
|
|
947
989
|
`Machine.invoke` infers that owner context and the returned Effect's output,
|
|
948
990
|
error, and service channels together. No return annotation is needed:
|
|
@@ -951,54 +993,40 @@ error, and service channels together. No return annotation is needed:
|
|
|
951
993
|
invoke: Machine.invoke({
|
|
952
994
|
id: "load",
|
|
953
995
|
effect: ({ state }) => LoadService.load(state.userId),
|
|
954
|
-
onDone:
|
|
955
|
-
|
|
956
|
-
resolve: ({ output, target }) => target.from({ user: output })
|
|
957
|
-
}),
|
|
958
|
-
onFailure: Machine.transition({
|
|
959
|
-
target: (to) => to.full.LoadFailed(),
|
|
960
|
-
resolve: ({ error, target }) => target.from({ error })
|
|
961
|
-
})
|
|
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 }))
|
|
962
998
|
})
|
|
963
999
|
```
|
|
964
1000
|
|
|
965
1001
|
Inside `.handle(...)`, the constructor receives the owning machine's public
|
|
966
|
-
input and
|
|
1002
|
+
input and declared parent protocol contextually. Sources and lifecycle handlers
|
|
967
1003
|
can send through `self` and `parent` without naming the definition:
|
|
968
1004
|
|
|
969
1005
|
```ts
|
|
970
1006
|
const machine = Machine.make({
|
|
971
1007
|
events: Commands,
|
|
972
1008
|
internalEvents: InternalEvents,
|
|
973
|
-
|
|
1009
|
+
parent: Machine.parent(ParentEvents),
|
|
974
1010
|
// ...
|
|
975
1011
|
}).handle({
|
|
976
1012
|
Saving: {
|
|
977
1013
|
invoke: Machine.invoke({
|
|
978
1014
|
id: "notify-parent",
|
|
979
1015
|
effect: () => saveDocument,
|
|
980
|
-
onDone:
|
|
981
|
-
|
|
982
|
-
resolve: ({ parent, self }, enqueue) => {
|
|
1016
|
+
onDone: (to) =>
|
|
1017
|
+
to.none.resolve(({ parent, self }, enqueue) => {
|
|
983
1018
|
enqueue.sendTo(self, Commands.Save())
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
return undefined
|
|
988
|
-
}
|
|
989
|
-
}),
|
|
990
|
-
onFailure: Machine.transition({
|
|
991
|
-
target: (to) => to.none(),
|
|
992
|
-
resolve: () => undefined
|
|
993
|
-
})
|
|
1019
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
1020
|
+
}),
|
|
1021
|
+
onFailure: (to) => to.none
|
|
994
1022
|
})
|
|
995
1023
|
}
|
|
996
1024
|
})
|
|
997
1025
|
```
|
|
998
1026
|
|
|
999
|
-
The
|
|
1000
|
-
definition is
|
|
1001
|
-
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.
|
|
1002
1030
|
|
|
1003
1031
|
A cancellable timer uses the same object:
|
|
1004
1032
|
|
|
@@ -1006,10 +1034,7 @@ A cancellable timer uses the same object:
|
|
|
1006
1034
|
invoke: Machine.invoke({
|
|
1007
1035
|
id: "clear-status",
|
|
1008
1036
|
after: "3 seconds",
|
|
1009
|
-
onDone:
|
|
1010
|
-
target: (to) => to.full.Clear(),
|
|
1011
|
-
resolve: ({ target }) => target()
|
|
1012
|
-
})
|
|
1037
|
+
onDone: (to) => to.full.Clear().resolve(({ target }) => target())
|
|
1013
1038
|
})
|
|
1014
1039
|
```
|
|
1015
1040
|
|
|
@@ -1036,10 +1061,7 @@ Invoke it from its owning state:
|
|
|
1036
1061
|
invoke: Machine.invoke({
|
|
1037
1062
|
child: Editor,
|
|
1038
1063
|
input: editorInput,
|
|
1039
|
-
onDone:
|
|
1040
|
-
target: (to) => to.full.EditorDone(),
|
|
1041
|
-
resolve: ({ output, target }) => target.from({ output })
|
|
1042
|
-
})
|
|
1064
|
+
onDone: (to) => to.full.EditorDone().resolve(({ output, target }) => target.from({ output }))
|
|
1043
1065
|
})
|
|
1044
1066
|
```
|
|
1045
1067
|
|
|
@@ -1287,9 +1309,10 @@ const step = yield * probe.sendAndAwait(event)
|
|
|
1287
1309
|
```
|
|
1288
1310
|
|
|
1289
1311
|
Inspect `step.before`, `step.after`, `step.plan`, `step.handled`, and
|
|
1290
|
-
`step.configurationChanged`. An ignored event
|
|
1291
|
-
|
|
1292
|
-
|
|
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.
|
|
1293
1316
|
|
|
1294
1317
|
Do not use a probe as a substitute for a domain completion event. The
|
|
1295
1318
|
acknowledgement covers the submitted event's synchronous macrostep, state
|
|
@@ -1359,10 +1382,7 @@ reference model when correctness of the expected behavior matters.
|
|
|
1359
1382
|
Select the initial root separately from constructing its value:
|
|
1360
1383
|
|
|
1361
1384
|
```ts
|
|
1362
|
-
initial: {
|
|
1363
|
-
target: (to) => to.Idle(),
|
|
1364
|
-
resolve: ({ target }) => target.from()
|
|
1365
|
-
}
|
|
1385
|
+
initial: (to) => to.Idle().resolve(({ target }) => target.from())
|
|
1366
1386
|
```
|
|
1367
1387
|
|
|
1368
1388
|
### Invoked child expects events not accepted by the parent
|
|
@@ -1373,13 +1393,16 @@ the parent's public events:
|
|
|
1373
1393
|
```ts
|
|
1374
1394
|
export const ChildParentEvents = Machine.events(ChildFinished)
|
|
1375
1395
|
|
|
1376
|
-
// child
|
|
1377
|
-
|
|
1396
|
+
// child-only machine
|
|
1397
|
+
parent: Machine.parent(ChildParentEvents)
|
|
1378
1398
|
|
|
1379
1399
|
// parent
|
|
1380
1400
|
events: Machine.events(Submit, ChildParentEvents)
|
|
1381
1401
|
```
|
|
1382
1402
|
|
|
1403
|
+
Use `Machine.optionalParent(ChildParentEvents)` only when the child is also a
|
|
1404
|
+
valid independent root and narrow `parent` before sending.
|
|
1405
|
+
|
|
1383
1406
|
### An internal event is rejected by `send`
|
|
1384
1407
|
|
|
1385
1408
|
This is intentional. Public input boundaries accept only schemas declared in
|