@typeonce/effect-machine 0.14.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -48
- package/dist/Machine.d.ts +436 -156
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +114 -57
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +2 -0
- package/dist/internal/machine/activities.d.ts.map +1 -1
- package/dist/internal/machine/activities.js +4 -0
- package/dist/internal/machine/activities.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +6 -1
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts +1 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +25 -3
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/invocationEvent.d.ts +8 -0
- package/dist/internal/machine/invocationEvent.d.ts.map +1 -1
- package/dist/internal/machine/invocationEvent.js +8 -0
- package/dist/internal/machine/invocationEvent.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +9 -4
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +131 -55
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +7 -0
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +20 -11
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/runtime.d.ts +1 -0
- package/dist/internal/machine/runtime.d.ts.map +1 -1
- package/dist/internal/machine/runtime.js +25 -16
- package/dist/internal/machine/runtime.js.map +1 -1
- package/dist/internal/machine/stateDefinition.d.ts +3 -1
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
- package/dist/internal/machine/stateDefinition.js +35 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +11 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +17 -6
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +1 -1
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.js +6 -2
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +6 -0
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +9 -8
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +7 -7
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +3 -3
- package/dist/unstable/reactivity/AtomMachine.js +3 -3
- package/docs/agent-guide.md +207 -88
- package/package.json +1 -1
- package/src/Machine.ts +871 -256
- package/src/internal/machine/activities.ts +7 -0
- package/src/internal/machine/executionPlan.ts +6 -1
- package/src/internal/machine/invocation.ts +39 -4
- package/src/internal/machine/invocationEvent.ts +16 -0
- package/src/internal/machine/machine.ts +187 -67
- package/src/internal/machine/planner.ts +17 -3
- package/src/internal/machine/runtime.ts +61 -25
- package/src/internal/machine/stateDefinition.ts +41 -1
- package/src/internal/machine/topology.ts +31 -2
- package/src/internal/testing/machine/finiteModel.ts +1 -1
- package/src/internal/testing/machine/transitionCoverage.ts +6 -2
- package/src/internal/testing/machine/verification.ts +11 -0
- package/src/testing/MachineTest.ts +9 -7
- package/src/unstable/cluster/ClusterMachine.ts +1 -1
- package/src/unstable/reactivity/AtomMachine.ts +3 -3
package/docs/agent-guide.md
CHANGED
|
@@ -35,15 +35,19 @@ currently coupled to the exact Effect peer version listed in its `package.json`.
|
|
|
35
35
|
Use this order so inference has all schemas available when handlers are
|
|
36
36
|
declared:
|
|
37
37
|
|
|
38
|
-
1. Domain schemas used by state and event fields.
|
|
39
|
-
2.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
38
|
+
1. Domain schemas used by state, and by event fields when they are shared.
|
|
39
|
+
2. `Machine.states`, using a tagged state union and `.cases` when state
|
|
40
|
+
schemas need to be reused.
|
|
41
|
+
3. `Machine.events`, `Machine.internalEvents`, `Machine.emittedEvents`, and
|
|
42
|
+
`parentEvents`; pass `Schema.TaggedUnion({...})` or tagged classes directly.
|
|
43
|
+
4. `Machine.make({...}).handle({...})`.
|
|
44
|
+
5. Child descriptors, then runtime, Atom, or Cluster adapters.
|
|
45
|
+
|
|
46
|
+
`Machine.make` returns a reusable definition. Each `handle` call creates one
|
|
47
|
+
independent machine implementation and the result does not expose `handle`
|
|
48
|
+
again. Put one implementation's complete behavior in a single handler tree;
|
|
49
|
+
call `handle` again on the original definition for a separate production,
|
|
50
|
+
testing, or simulation variant.
|
|
47
51
|
|
|
48
52
|
`Schema.TaggedUnion` avoids one class declaration per case:
|
|
49
53
|
|
|
@@ -54,22 +58,23 @@ const State = Schema.TaggedUnion({
|
|
|
54
58
|
Failed: { message: Schema.String }
|
|
55
59
|
})
|
|
56
60
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
const States = Machine.states(State.cases)
|
|
62
|
+
export const Event = Machine.events(
|
|
63
|
+
Schema.TaggedUnion({
|
|
64
|
+
Save: {}
|
|
65
|
+
})
|
|
66
|
+
)
|
|
67
|
+
export const Internal = Machine.internalEvents(
|
|
68
|
+
Schema.TaggedUnion({
|
|
69
|
+
Saved: { id: Schema.String },
|
|
70
|
+
SaveFailed: { message: Schema.String }
|
|
71
|
+
})
|
|
72
|
+
)
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
Pass these descriptors to `Machine.make
|
|
72
|
-
|
|
75
|
+
Pass these descriptors to `Machine.make`; the event descriptor is the public
|
|
76
|
+
handle, so do not introduce a tagged-union binding used only by an event helper.
|
|
77
|
+
Construct new state values through the target or initial
|
|
73
78
|
builder's `.from(...)` method. Both event constructors and state `.from(...)`
|
|
74
79
|
defer schema construction until planning, so validation failures remain typed
|
|
75
80
|
machine errors. Use
|
|
@@ -118,12 +123,13 @@ its extra control is required:
|
|
|
118
123
|
- Bind a shared Atom runtime once with `AtomMachine.bind(runtime)`, then use the
|
|
119
124
|
returned `make` or `resume`. Use `AtomMachine.make(machine)` and
|
|
120
125
|
`AtomMachine.resume(machine, snapshot)` for service-free machines.
|
|
121
|
-
- Use one invocation object: `effect` for one-shot work, `
|
|
122
|
-
`
|
|
126
|
+
- Use one invocation object: `effect` for one-shot work, `stream` for repeated
|
|
127
|
+
externally produced values, `after` for a timer, `logic` for reusable process
|
|
128
|
+
logic, and `child` for a complete child
|
|
123
129
|
statechart. `Machine.invoke({...})` preserves owner state and source channels
|
|
124
|
-
across sibling lifecycle handlers.
|
|
125
|
-
|
|
126
|
-
definition
|
|
130
|
+
across sibling lifecycle handlers. Inside `.handle(...)`, `self` and `parent`
|
|
131
|
+
use the owning definition's exact public input and `parentEvents` protocols.
|
|
132
|
+
The bound `definition.invoke({...})` form is equivalent, not required.
|
|
127
133
|
- Use `Machine.child(id, machine)` for a complete statechart descriptor and
|
|
128
134
|
`Machine.childAddress<Event>(id)` for a low-level process address. A logic
|
|
129
135
|
invocation is addressable only when `Machine.invoke` receives that
|
|
@@ -133,11 +139,88 @@ its extra control is required:
|
|
|
133
139
|
|
|
134
140
|
## Atomic, compound, parallel, and history states
|
|
135
141
|
|
|
142
|
+
### Inline topology by default; extract only repeated states
|
|
143
|
+
|
|
144
|
+
Prefer writing the complete topology inline in `Machine.states`. A one-off
|
|
145
|
+
compound or parallel area is easier to understand in place, and extracting it
|
|
146
|
+
does not improve its types. Use `Machine.state` only when the same active state
|
|
147
|
+
definition is mounted more than once. Tagged schemas are already reusable and
|
|
148
|
+
do not need `Machine.state`.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
type TeamSlot = 1 | 2 | 3 | 4 | 5 | 6
|
|
152
|
+
|
|
153
|
+
const TradingSlot = Machine.state({
|
|
154
|
+
initial: "Idle",
|
|
155
|
+
states: {
|
|
156
|
+
Idle: {},
|
|
157
|
+
InSession: State.cases.InSession,
|
|
158
|
+
Applying: State.cases.Applying
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
const States = Machine.states({
|
|
163
|
+
root: {
|
|
164
|
+
type: "parallel",
|
|
165
|
+
states: {
|
|
166
|
+
trading: {
|
|
167
|
+
type: "parallel",
|
|
168
|
+
states: {
|
|
169
|
+
slot1: TradingSlot,
|
|
170
|
+
slot2: TradingSlot,
|
|
171
|
+
slot3: TradingSlot,
|
|
172
|
+
slot4: TradingSlot,
|
|
173
|
+
slot5: TradingSlot,
|
|
174
|
+
slot6: TradingSlot
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
// Other explicit regions stay visible here.
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`Machine.state` accepts one active atomic, compound, or parallel node. It
|
|
184
|
+
checks child keys and the compound `initial` at the reusable definition. It is
|
|
185
|
+
not a second model builder, does not define handlers, and does not accept
|
|
186
|
+
history or choice nodes as roots. `Machine.states` remains the complete model
|
|
187
|
+
boundary and captures every mount independently.
|
|
188
|
+
|
|
189
|
+
For a finite family of paths, bind the template to that definition instead of
|
|
190
|
+
maintaining a parallel string table:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
const inSessionPath = <const Slot extends TeamSlot>(slot: Slot) =>
|
|
194
|
+
States.path(`root.trading.slot${slot}.InSession`)
|
|
195
|
+
|
|
196
|
+
States.matches(snapshot, inSessionPath(slot))
|
|
197
|
+
AtomMachine.matches(machineAtom, inSessionPath(slot))
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`States.path` is a compile-time identity helper. It accepts a literal or a
|
|
201
|
+
finite template-literal union only when every member is an active path in this
|
|
202
|
+
tree. Renaming a slot or child therefore breaks the path helper at its
|
|
203
|
+
definition rather than leaving a stale catalog.
|
|
204
|
+
|
|
205
|
+
Use the definition-bound snapshot type when a query genuinely needs the full
|
|
206
|
+
machine snapshot:
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const offeredIfSlot = (
|
|
210
|
+
snapshot: Machine.Snapshot<typeof States>,
|
|
211
|
+
slot: TeamSlot
|
|
212
|
+
) =>
|
|
213
|
+
!States.matches(snapshot, inSessionPath(slot))
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Do not derive this type with `Parameters<typeof States.get>[0]`; that depends
|
|
217
|
+
on overload order and does not express ownership by the state definition.
|
|
218
|
+
|
|
136
219
|
An active state does not need a schema unless it owns data. Omit `schema` for
|
|
137
220
|
control-only atomic, compound, parallel, and final states:
|
|
138
221
|
|
|
139
222
|
```ts
|
|
140
|
-
const States = Machine.
|
|
223
|
+
const States = Machine.states({
|
|
141
224
|
Idle: {},
|
|
142
225
|
Form: {
|
|
143
226
|
initial: "Editing",
|
|
@@ -190,7 +273,7 @@ Use a compound state when exactly one child phase is active. It must declare an
|
|
|
190
273
|
```ts
|
|
191
274
|
const FormState = Schema.TaggedUnion({ Saving: { draft: Schema.String } })
|
|
192
275
|
|
|
193
|
-
const FormStates = Machine.
|
|
276
|
+
const FormStates = Machine.states({
|
|
194
277
|
Form: {
|
|
195
278
|
initial: "Editing",
|
|
196
279
|
states: {
|
|
@@ -204,7 +287,7 @@ const FormStates = Machine.defineStates({
|
|
|
204
287
|
Use a parallel state when every direct region is active:
|
|
205
288
|
|
|
206
289
|
```ts
|
|
207
|
-
const ParallelStates = Machine.
|
|
290
|
+
const ParallelStates = Machine.states({
|
|
208
291
|
Screen: {
|
|
209
292
|
type: "parallel",
|
|
210
293
|
states: {
|
|
@@ -231,13 +314,13 @@ Every parallel region needs an active state in initial and full snapshot
|
|
|
231
314
|
builders. The same rule applies when a local or branch target enters an
|
|
232
315
|
inactive nested parallel state.
|
|
233
316
|
|
|
234
|
-
Use `type: "final"` for a terminal leaf in `Machine.
|
|
317
|
+
Use `type: "final"` for a terminal leaf in `Machine.states`. A final
|
|
235
318
|
child completes its compound parent. Put `onDone` on that completed parent,
|
|
236
319
|
never on the final leaf. The definition owns the output schema and the handler
|
|
237
320
|
computes its value:
|
|
238
321
|
|
|
239
322
|
```ts
|
|
240
|
-
const States = Machine.
|
|
323
|
+
const States = Machine.states({
|
|
241
324
|
Done: {
|
|
242
325
|
schema: State.cases.Done,
|
|
243
326
|
type: "final",
|
|
@@ -311,7 +394,7 @@ should remember. It has no schema, is excluded from active state identifiers,
|
|
|
311
394
|
and is addressed only through `target.history`:
|
|
312
395
|
|
|
313
396
|
```ts
|
|
314
|
-
const States = Machine.
|
|
397
|
+
const States = Machine.states({
|
|
315
398
|
checkout: {
|
|
316
399
|
schema: Checkout,
|
|
317
400
|
initial: "shipping",
|
|
@@ -455,7 +538,7 @@ through `schema.makeEffect`, including refinements.
|
|
|
455
538
|
|
|
456
539
|
## Reading state and structural ancestors
|
|
457
540
|
|
|
458
|
-
`Machine.
|
|
541
|
+
`Machine.states` returns typed helpers:
|
|
459
542
|
|
|
460
543
|
```ts
|
|
461
544
|
States.get(snapshot, "Route.Ready")
|
|
@@ -496,21 +579,14 @@ microstep, before any selected transition is applied:
|
|
|
496
579
|
|
|
497
580
|
```ts
|
|
498
581
|
BufferReady: Machine.transition({
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
resolve: ({ target }) => target.from()
|
|
508
|
-
})
|
|
509
|
-
],
|
|
510
|
-
otherwise: {
|
|
511
|
-
target: (to) => to.none(),
|
|
512
|
-
resolve: () => undefined
|
|
513
|
-
}
|
|
582
|
+
branches: (to) => ({
|
|
583
|
+
online: { target: to.local.Playing() },
|
|
584
|
+
unchanged: { target: to.none() }
|
|
585
|
+
}),
|
|
586
|
+
resolve: ({ snapshot, select }) =>
|
|
587
|
+
States.matches(snapshot, "Player.Network.Online")
|
|
588
|
+
? select.online.from()
|
|
589
|
+
: select.unchanged()
|
|
514
590
|
})
|
|
515
591
|
```
|
|
516
592
|
|
|
@@ -566,30 +642,30 @@ synchronously:
|
|
|
566
642
|
|
|
567
643
|
```ts
|
|
568
644
|
Submit: Machine.transition({
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
],
|
|
577
|
-
otherwise: {
|
|
578
|
-
target: (to) => to.none(),
|
|
579
|
-
resolve: () => undefined
|
|
580
|
-
}
|
|
645
|
+
branches: (to) => ({
|
|
646
|
+
valid: { target: to.local.Saving() },
|
|
647
|
+
invalid: { target: to.none() }
|
|
648
|
+
}),
|
|
649
|
+
resolve: ({ state, select }) => state.valid
|
|
650
|
+
? select.valid.from({ draft: state.draft })
|
|
651
|
+
: select.invalid()
|
|
581
652
|
})
|
|
582
653
|
```
|
|
583
654
|
|
|
584
655
|
Every installed event, `always`, `onDone`, choice, and invoke lifecycle handler
|
|
585
|
-
must use `Machine.transition`.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
656
|
+
must use `Machine.transition`. A direct transition declares one `target`. A
|
|
657
|
+
branching transition declares every possible target in a named `branches`
|
|
658
|
+
record, then uses ordinary TypeScript control flow in `resolve` to return one
|
|
659
|
+
typed `select` builder. Branch keys are stable testing and inspection identities;
|
|
660
|
+
an optional `title` controls presentation and otherwise defaults to the key.
|
|
661
|
+
Selecting a branch whose target is `to.none()` handles the transition without a
|
|
662
|
+
destination while retaining queued commands, raised events, and emitted events.
|
|
663
|
+
|
|
664
|
+
The `branches` callback runs once when handlers are installed. Its record uses
|
|
665
|
+
the deterministic ECMAScript property order for presentation and `branchIndex`;
|
|
666
|
+
array-index and symbol keys are rejected. Treat the string key as semantic:
|
|
667
|
+
reordering named properties may change their display index, but visualizers,
|
|
668
|
+
coverage, and trace verification identify each branch by its key.
|
|
593
669
|
|
|
594
670
|
`reenter: true` remains meaningful with `to.none()`: the source exits and
|
|
595
671
|
enters again while its logical configuration is retained.
|
|
@@ -764,8 +840,17 @@ an event for the parent. Both operations validate their schemas.
|
|
|
764
840
|
union handled inside the statechart:
|
|
765
841
|
|
|
766
842
|
```ts
|
|
767
|
-
const Events = Machine.events(
|
|
768
|
-
|
|
843
|
+
const Events = Machine.events(
|
|
844
|
+
Schema.TaggedUnion({
|
|
845
|
+
Save: {}
|
|
846
|
+
})
|
|
847
|
+
)
|
|
848
|
+
const InternalEvents = Machine.internalEvents(
|
|
849
|
+
Schema.TaggedUnion({
|
|
850
|
+
Saved: { id: Schema.String },
|
|
851
|
+
SaveFailed: { message: Schema.String }
|
|
852
|
+
})
|
|
853
|
+
)
|
|
769
854
|
|
|
770
855
|
const definition = Machine.make({
|
|
771
856
|
states: States.states,
|
|
@@ -852,6 +937,38 @@ needs `state`, `containingState`, `ancestors`, or the entry `event`. Source cons
|
|
|
852
937
|
errors, defects, and interruption are machine failures rather than a second
|
|
853
938
|
phase in `onFailure`.
|
|
854
939
|
|
|
940
|
+
Use a Stream invocation for repeated values that are not themselves machine
|
|
941
|
+
events. `onElement` maps each value into an owner transition, while `onDone`
|
|
942
|
+
handles normal Stream completion and `onFailure` handles the typed Stream error:
|
|
943
|
+
|
|
944
|
+
```ts
|
|
945
|
+
invoke: Machine.invoke({
|
|
946
|
+
id: "broadcast-channel",
|
|
947
|
+
stream: () => messages,
|
|
948
|
+
onElement: {
|
|
949
|
+
target: Machine.targetless,
|
|
950
|
+
resolve: ({ element }, enqueue) => {
|
|
951
|
+
enqueue.raise(Events.MessageReceived({ message: element }))
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
onDone: { target: Machine.targetless },
|
|
955
|
+
onFailure: Machine.transition({
|
|
956
|
+
target: (to) => to.full.Disconnected(),
|
|
957
|
+
resolve: ({ error, target }) => target.from({ error })
|
|
958
|
+
})
|
|
959
|
+
})
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
Element delivery is owner-scoped and backpressured: the Stream pulls again only
|
|
963
|
+
after the selected parent macrostep commits. Exiting or reentering the owner
|
|
964
|
+
interrupts the Stream and runs its finalizers. A later entry starts a fresh
|
|
965
|
+
Stream. Stream defects and self-interruption fail the owning machine.
|
|
966
|
+
|
|
967
|
+
The direct `{ target: Machine.targetless, resolve }` shorthand is available
|
|
968
|
+
when a transition only enqueues commands. It is non-reentering and the resolver
|
|
969
|
+
must return `undefined`. Keep `Machine.transition(...)` for full state selection,
|
|
970
|
+
named branches, or reentry.
|
|
971
|
+
|
|
855
972
|
When a source function reads `state`, `containingState`, `ancestors`, or the entry `event`,
|
|
856
973
|
`Machine.invoke` infers that owner context and the returned Effect's output,
|
|
857
974
|
error, and service channels together. No return annotation is needed:
|
|
@@ -871,29 +988,30 @@ invoke: Machine.invoke({
|
|
|
871
988
|
})
|
|
872
989
|
```
|
|
873
990
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
through
|
|
991
|
+
Inside `.handle(...)`, the constructor receives the owning machine's public
|
|
992
|
+
input and `parentEvents` protocols contextually. Sources and lifecycle handlers
|
|
993
|
+
can send through `self` and `parent` without naming the definition:
|
|
877
994
|
|
|
878
995
|
```ts
|
|
879
|
-
const
|
|
996
|
+
const machine = Machine.make({
|
|
880
997
|
events: Commands,
|
|
881
998
|
internalEvents: InternalEvents,
|
|
882
999
|
parentEvents: ParentEvents,
|
|
883
1000
|
// ...
|
|
884
|
-
})
|
|
885
|
-
|
|
886
|
-
const machine = definition.handle({
|
|
1001
|
+
}).handle({
|
|
887
1002
|
Saving: {
|
|
888
|
-
invoke:
|
|
1003
|
+
invoke: Machine.invoke({
|
|
889
1004
|
id: "notify-parent",
|
|
890
|
-
effect: (
|
|
891
|
-
parent === undefined
|
|
892
|
-
? Effect.void
|
|
893
|
-
: parent.send(ParentEvents.SaveStarted()),
|
|
1005
|
+
effect: () => saveDocument,
|
|
894
1006
|
onDone: Machine.transition({
|
|
895
1007
|
target: (to) => to.none(),
|
|
896
|
-
resolve: () =>
|
|
1008
|
+
resolve: ({ parent, self }, enqueue) => {
|
|
1009
|
+
enqueue.sendTo(self, Commands.Save())
|
|
1010
|
+
if (parent !== undefined) {
|
|
1011
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
1012
|
+
}
|
|
1013
|
+
return undefined
|
|
1014
|
+
}
|
|
897
1015
|
}),
|
|
898
1016
|
onFailure: Machine.transition({
|
|
899
1017
|
target: (to) => to.none(),
|
|
@@ -904,8 +1022,9 @@ const machine = definition.handle({
|
|
|
904
1022
|
})
|
|
905
1023
|
```
|
|
906
1024
|
|
|
907
|
-
|
|
908
|
-
|
|
1025
|
+
The machine-bound `definition.invoke(...)` form remains equivalent when the
|
|
1026
|
+
definition is already named. A direct `invoke: { ... }` object remains available
|
|
1027
|
+
when lifecycle handlers do not need source-derived context.
|
|
909
1028
|
|
|
910
1029
|
A cancellable timer uses the same object:
|
|
911
1030
|
|
|
@@ -1314,7 +1433,7 @@ repeat the final marker in this handler.
|
|
|
1314
1433
|
|
|
1315
1434
|
### `type: "final"` is rejected by `handle`
|
|
1316
1435
|
|
|
1317
|
-
Move it to `Machine.
|
|
1436
|
+
Move it to `Machine.states`. Definitions own statechart topology;
|
|
1318
1437
|
handlers own behavior.
|
|
1319
1438
|
|
|
1320
1439
|
### Parent property does not exist
|