@typeonce/effect-machine 0.17.0 → 0.19.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 +133 -80
- package/dist/Machine.d.ts +434 -297
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +13 -55
- package/dist/Machine.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 +2 -1
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/invocation.d.ts.map +1 -1
- package/dist/internal/machine/invocation.js +1 -1
- package/dist/internal/machine/invocation.js.map +1 -1
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +48 -10
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/serialization.d.ts.map +1 -1
- package/dist/internal/machine/serialization.js +75 -18
- package/dist/internal/machine/serialization.js.map +1 -1
- package/dist/internal/testing/machine/verification.d.ts +1 -1
- package/dist/internal/testing/machine/verification.d.ts.map +1 -1
- package/dist/internal/testing/machine/verification.js +9 -6
- package/dist/internal/testing/machine/verification.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +13 -10
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +5 -3
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +22 -2
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +4 -0
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/docs/agent-guide.md +189 -83
- package/package.json +4 -4
- package/src/Machine.ts +845 -1051
- package/src/internal/machine/cluster.ts +4 -1
- package/src/internal/machine/invocation.ts +1 -1
- package/src/internal/machine/machine.ts +64 -8
- package/src/internal/machine/serialization.ts +100 -25
- package/src/internal/testing/machine/exploration.ts +1 -1
- package/src/internal/testing/machine/trace.ts +1 -1
- package/src/internal/testing/machine/verification.ts +16 -11
- package/src/testing/MachineTest.ts +13 -10
- package/src/unstable/cluster/ClusterMachine.ts +51 -1
- package/src/unstable/reactivity/AtomMachine.ts +1 -1
package/docs/agent-guide.md
CHANGED
|
@@ -54,12 +54,15 @@ testing, or simulation variant.
|
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
56
|
const State = Schema.TaggedUnion({
|
|
57
|
-
Idle: {},
|
|
58
57
|
Saving: { draft: Draft },
|
|
59
58
|
Failed: { message: Schema.String }
|
|
60
59
|
})
|
|
61
60
|
|
|
62
|
-
const States = Machine.states(
|
|
61
|
+
const States = Machine.states({
|
|
62
|
+
Idle: {},
|
|
63
|
+
Saving: State.cases.Saving,
|
|
64
|
+
Failed: State.cases.Failed
|
|
65
|
+
})
|
|
63
66
|
export const Event = Machine.events(
|
|
64
67
|
Schema.TaggedUnion({
|
|
65
68
|
Save: {}
|
|
@@ -125,22 +128,38 @@ its extra control is required:
|
|
|
125
128
|
- Bind a shared Atom runtime once with `AtomMachine.bind(runtime)`, then use the
|
|
126
129
|
returned `make` or `resume`. Use `AtomMachine.make(machine)` and
|
|
127
130
|
`AtomMachine.resume(machine, snapshot)` for service-free machines.
|
|
128
|
-
- Use
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
across
|
|
133
|
-
declared `parent` use the owning definition's exact protocols
|
|
134
|
-
definition method is required.
|
|
131
|
+
- Use the state-local `invoke: (from) => ...` selector: `from.effect` for
|
|
132
|
+
one-shot work, `from.stream` for repeated externally produced values,
|
|
133
|
+
`from.timer` for a timer, `from.logic` for reusable process logic, and
|
|
134
|
+
`from.child` for a complete child statechart. Its chain preserves owner state
|
|
135
|
+
and source channels across lifecycle handlers. Inside `.handle(...)`, `self`
|
|
136
|
+
and any declared `parent` use the owning definition's exact protocols.
|
|
135
137
|
- Use `Machine.child(id, machine)` for a complete statechart descriptor and
|
|
136
138
|
`Machine.childAddress<Event>(id)` for a low-level process address. A logic
|
|
137
|
-
invocation is addressable only when `
|
|
138
|
-
|
|
139
|
+
invocation is addressable only when `from.logic` receives that address
|
|
140
|
+
explicitly.
|
|
139
141
|
- Use the callback's `enqueue` argument for `raise`, `emit`, `sendTo`, and
|
|
140
142
|
`stop`. These operations record closed machine commands and do not run Effects.
|
|
141
143
|
|
|
142
144
|
## Atomic, compound, parallel, and history states
|
|
143
145
|
|
|
146
|
+
### Topology is a validity boundary
|
|
147
|
+
|
|
148
|
+
Design the state tree so invalid domain situations cannot be constructed. A
|
|
149
|
+
parallel node is not merely a convenient grouping of related concepts: it
|
|
150
|
+
declares the Cartesian product of its regions. Every combination must be
|
|
151
|
+
meaningful in snapshots, explicit targets, decoding, and resume.
|
|
152
|
+
|
|
153
|
+
If a handler reads a sibling region to decide whether entering its target is
|
|
154
|
+
legal, treat that as a topology smell and try a compound hierarchy first. Do
|
|
155
|
+
not move the same invariant into a disabled UI control, redundant event field,
|
|
156
|
+
or invoked-service failure. Cross-region reads remain useful for coordinating
|
|
157
|
+
genuinely independent regions and for projecting snapshots into views.
|
|
158
|
+
|
|
159
|
+
Place an invoked Effect or resource-dependent state beneath the state that
|
|
160
|
+
guarantees the resource exists. Exiting the owner should structurally exit and
|
|
161
|
+
interrupt all dependent work.
|
|
162
|
+
|
|
144
163
|
### Inline topology by default; extract only repeated states
|
|
145
164
|
|
|
146
165
|
Prefer writing the complete topology inline in `Machine.states`. A one-off
|
|
@@ -218,8 +237,25 @@ const offeredIfSlot = (
|
|
|
218
237
|
Do not derive this type with `Parameters<typeof States.get>[0]`; that depends
|
|
219
238
|
on overload order and does not express ownership by the state definition.
|
|
220
239
|
|
|
240
|
+
The same extractor accepts a machine when that is the object exported at the
|
|
241
|
+
consumer boundary. Use `Value` for a decoded schema-backed state payload and
|
|
242
|
+
`SnapshotAt` for the snapshot rooted at one active path:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
type Complete = Machine.Snapshot<typeof machine>
|
|
246
|
+
type Session = Machine.Value<typeof States, "root.trading.InSession">
|
|
247
|
+
type Trading = Machine.SnapshotAt<typeof machine, "root.trading">
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`Value` accepts only paths that own a schema, matching `States.get`.
|
|
251
|
+
`SnapshotAt` also accepts structural paths, matching `States.getSnapshot`.
|
|
252
|
+
Both reject stale or misspelled paths. Prefer these definition- or
|
|
253
|
+
machine-bound forms over `.cases.Case.Type`, `typeof States.states`, or
|
|
254
|
+
composing `Machine.Machine.States` with raw-tree path extractors.
|
|
255
|
+
|
|
221
256
|
An active state does not need a schema unless it owns data. Omit `schema` for
|
|
222
|
-
control-only atomic, compound, parallel, and final states
|
|
257
|
+
control-only atomic, compound, parallel, and final states. In particular, use
|
|
258
|
+
`{}` instead of an empty tagged-union case or tagged class:
|
|
223
259
|
|
|
224
260
|
```ts
|
|
225
261
|
const States = Machine.states({
|
|
@@ -243,11 +279,7 @@ in snapshots. They do not have a state value:
|
|
|
243
279
|
```ts
|
|
244
280
|
Idle: {
|
|
245
281
|
on: {
|
|
246
|
-
Start: (to) =>
|
|
247
|
-
to.full.Form.initial.resolve(({ state, target }) => {
|
|
248
|
-
// state: undefined
|
|
249
|
-
return target.from((form) => form.Editing.from())
|
|
250
|
-
})
|
|
282
|
+
Start: (to) => to.full.Form.initial
|
|
251
283
|
}
|
|
252
284
|
}
|
|
253
285
|
|
|
@@ -262,6 +294,13 @@ also omitted from `ancestors`; an immediate structural containing state is
|
|
|
262
294
|
typed as `undefined`. Add `schema` when a state begins to own data or needs runtime
|
|
263
295
|
validation and persistence for that data.
|
|
264
296
|
|
|
297
|
+
Declare data-bearing states together with the named
|
|
298
|
+
`const State = Schema.TaggedUnion(...)` pattern, then reference `State.cases`
|
|
299
|
+
from the topology. This keeps the state value protocol visible and reusable.
|
|
300
|
+
Use a named `Schema.TaggedClass` instead when a standalone state benefits from
|
|
301
|
+
class identity. Do not bury one-off tagged schema declarations inside
|
|
302
|
+
`Machine.states`.
|
|
303
|
+
|
|
265
304
|
Use an atomic state when no child phase can be active beneath it.
|
|
266
305
|
|
|
267
306
|
Use a compound state when exactly one child phase is active. It must declare an
|
|
@@ -311,6 +350,11 @@ Every parallel region needs an active state in initial and full snapshot
|
|
|
311
350
|
builders. The same rule applies when a local or branch target enters an
|
|
312
351
|
inactive nested parallel state.
|
|
313
352
|
|
|
353
|
+
This is also a semantic product: `Online + Closed`, `Online + Open`,
|
|
354
|
+
`Offline + Closed`, and `Offline + Open` are all valid configurations in the
|
|
355
|
+
example above. If even one combination must be prevented for correctness, use
|
|
356
|
+
a compound hierarchy or redesign the regions.
|
|
357
|
+
|
|
314
358
|
Use `type: "final"` for a terminal leaf in `Machine.states`. A final
|
|
315
359
|
child completes its compound parent. Put `onDone` on that completed parent,
|
|
316
360
|
never on the final leaf. The definition owns the output schema and the handler
|
|
@@ -328,7 +372,7 @@ const States = Machine.states({
|
|
|
328
372
|
const machine = Machine.make({
|
|
329
373
|
states: States.states,
|
|
330
374
|
events: Machine.events(),
|
|
331
|
-
initial: (to) => to.Done()
|
|
375
|
+
initial: (to) => to.Done()
|
|
332
376
|
}).handle({
|
|
333
377
|
Done: {
|
|
334
378
|
output: () => "done"
|
|
@@ -348,6 +392,20 @@ with `.initial`. This is available on top-level state methods under
|
|
|
348
392
|
Open: (to) => to.full.opened.initial.resolve(({ target }) => target.from({ teamId: "team-1" }))
|
|
349
393
|
```
|
|
350
394
|
|
|
395
|
+
Return the `.initial` transition directly when the selected state owns no data
|
|
396
|
+
and the transition has no commands to enqueue:
|
|
397
|
+
|
|
398
|
+
```ts
|
|
399
|
+
Close: (to) => to.full.closed.initial
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Do not manually reconstruct the declared initial descendants at ordinary entry
|
|
403
|
+
transitions. Reserve explicit descendant builders for deliberately non-default
|
|
404
|
+
configurations and for replacing an already-active parallel root with one
|
|
405
|
+
complete canonical configuration. `Machine.make({ initial })` still constructs
|
|
406
|
+
the first complete snapshot through its initial selector; that selector
|
|
407
|
+
statically restricts a compound node to its declared initial child.
|
|
408
|
+
|
|
351
409
|
The definition-time `.initial` property is a topology value. The exact
|
|
352
410
|
resolver `target` is still a callable runtime builder.
|
|
353
411
|
|
|
@@ -425,11 +483,11 @@ checkout: {
|
|
|
425
483
|
Target it without a value:
|
|
426
484
|
|
|
427
485
|
```ts
|
|
428
|
-
Resume: (to) => to.history.checkout.exact
|
|
486
|
+
Resume: (to) => to.history.checkout.exact
|
|
429
487
|
```
|
|
430
488
|
|
|
431
|
-
Each declared history leaf is a topology value
|
|
432
|
-
|
|
489
|
+
Each declared history leaf is a topology value and can be returned directly
|
|
490
|
+
when no resolver work is needed.
|
|
433
491
|
|
|
434
492
|
Deep history restores the complete remembered subtree and its decoded values.
|
|
435
493
|
Shallow history restores only parent and direct-child values. If the remembered
|
|
@@ -511,8 +569,11 @@ Refresh: (to) =>
|
|
|
511
569
|
to.full.Ready().resolve(({ state, target }) => target.from({ value: state.value }), { reenter: true })
|
|
512
570
|
```
|
|
513
571
|
|
|
514
|
-
When no resolver is needed
|
|
515
|
-
|
|
572
|
+
When no resolver is needed and the selected builder supports zero-argument
|
|
573
|
+
construction, return the selected target directly. This applies the same
|
|
574
|
+
default construction as `target.from()`; the compiler rejects the shorthand
|
|
575
|
+
when state data or nested configuration is required. Append `.reenter()` only
|
|
576
|
+
when restart semantics are intentional:
|
|
516
577
|
|
|
517
578
|
```ts
|
|
518
579
|
Finish: (to) => to.full.Done()
|
|
@@ -609,6 +670,11 @@ Use the existing `States.matches`, `States.get`, `States.getWithParents`, and
|
|
|
609
670
|
selected in one microstep receive the same capture. Synchronous handlers use
|
|
610
671
|
that captured value and cannot consult live runtime state later.
|
|
611
672
|
|
|
673
|
+
Before using a cross-region read to permit or reject a target, verify that all
|
|
674
|
+
combinations of the parallel regions are valid. If the check excludes an
|
|
675
|
+
invalid combination, move the invariant into a compound hierarchy. Observer,
|
|
676
|
+
view, diagnostic, and test queries do not have this concern.
|
|
677
|
+
|
|
612
678
|
Do not expect `snapshot` in entry, exit, invoke, initializer, history-default,
|
|
613
679
|
or choice contexts. Choice is an important soundness boundary: a startup or
|
|
614
680
|
chained choice can run without a complete stable configuration containing the
|
|
@@ -886,7 +952,7 @@ const definition = Machine.make({
|
|
|
886
952
|
states: States.states,
|
|
887
953
|
events: Events,
|
|
888
954
|
internalEvents: InternalEvents,
|
|
889
|
-
initial: (to) => to.Idle()
|
|
955
|
+
initial: (to) => to.Idle()
|
|
890
956
|
})
|
|
891
957
|
```
|
|
892
958
|
|
|
@@ -919,8 +985,14 @@ Use the exported utility types when another API must preserve the boundary:
|
|
|
919
985
|
```ts
|
|
920
986
|
type PublicEvent = Machine.Machine.InputEvent<typeof definition>
|
|
921
987
|
type AnyHandledEvent = Machine.Machine.Event<typeof definition>
|
|
988
|
+
type StartupInput = Machine.Machine.Input<typeof definition>
|
|
989
|
+
type StartupInputSchema = Machine.Machine.InputSchema<typeof definition>
|
|
922
990
|
```
|
|
923
991
|
|
|
992
|
+
`Input` is the decoded value accepted at startup. It is `never` for a machine
|
|
993
|
+
whose input schema is `Schema.Void`; use `InputSchema` only when an API needs
|
|
994
|
+
the schema object itself.
|
|
995
|
+
|
|
924
996
|
`MachineRef.send`, `machineAtom.send`, and `Machine.plan` accept decoded public
|
|
925
997
|
events or constructions returned by `Machine.events`. Transition handlers
|
|
926
998
|
receive only decoded events. Raised events additionally accept constructions
|
|
@@ -933,18 +1005,28 @@ Cluster RPC payloads are additionally decoded against the public `events`
|
|
|
933
1005
|
schemas at the transport boundary. Never repeat an `_tag` within a list or
|
|
934
1006
|
across both configuration lists.
|
|
935
1007
|
|
|
1008
|
+
Do not extract `enqueue`, target builders, transition contexts, command or
|
|
1009
|
+
inspection unions, or event-construction `ReturnType`s into application helper
|
|
1010
|
+
APIs. Keep commands inside transition resolvers, where the owning state,
|
|
1011
|
+
protocols, references, and capabilities are inferred. Likewise, do not add
|
|
1012
|
+
Atom `State` or `Event` aliases: selectors infer from their bridge, while
|
|
1013
|
+
consumer props use `Snapshot`, `Value`, or `InputEvent` from the exported state
|
|
1014
|
+
definition or machine.
|
|
1015
|
+
|
|
936
1016
|
## Recoverable state-scoped work
|
|
937
1017
|
|
|
938
|
-
Use `
|
|
939
|
-
|
|
1018
|
+
Use `from.effect` for one-shot work. Lifecycle callbacks receive the typed
|
|
1019
|
+
Effect channels and can transition directly:
|
|
940
1020
|
|
|
941
1021
|
```ts
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1022
|
+
machine.handle({
|
|
1023
|
+
Saving: {
|
|
1024
|
+
invoke: (from) =>
|
|
1025
|
+
from.effect("save", () => SaveService.save(draft))
|
|
1026
|
+
.onDone((to) => to.full.Saved().resolve(({ output, target }) => target.from({ entry: output })))
|
|
1027
|
+
.onFailure((to) =>
|
|
1028
|
+
to.full.SaveFailed().resolve(({ error, target }) => target.from({ message: error.message })))
|
|
1029
|
+
}
|
|
948
1030
|
})
|
|
949
1031
|
```
|
|
950
1032
|
|
|
@@ -964,15 +1046,17 @@ events. `onElement` maps each value into an owner transition, while `onDone`
|
|
|
964
1046
|
handles normal Stream completion and `onFailure` handles the typed Stream error:
|
|
965
1047
|
|
|
966
1048
|
```ts
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1049
|
+
machine.handle({
|
|
1050
|
+
Listening: {
|
|
1051
|
+
invoke: (from) =>
|
|
1052
|
+
from.stream("broadcast-channel", () => messages)
|
|
1053
|
+
.onElement((to) =>
|
|
1054
|
+
to.none.resolve(({ element }, enqueue) => {
|
|
1055
|
+
enqueue.raise(Events.MessageReceived({ message: element }))
|
|
1056
|
+
}))
|
|
1057
|
+
.onDone((to) => to.none)
|
|
1058
|
+
.onFailure((to) => to.full.Disconnected().resolve(({ error, target }) => target.from({ error })))
|
|
1059
|
+
}
|
|
976
1060
|
})
|
|
977
1061
|
```
|
|
978
1062
|
|
|
@@ -985,16 +1069,18 @@ Use `to.none` when a transition keeps the current configuration. Call
|
|
|
985
1069
|
`to.none.resolve(...)` when it also enqueues commands; a block resolver may
|
|
986
1070
|
omit its return because it is contextually typed to return `undefined`.
|
|
987
1071
|
|
|
988
|
-
When a source function reads `state`, `containingState`, `ancestors`, or the
|
|
989
|
-
`
|
|
990
|
-
error, and service channels together. No return annotation is needed:
|
|
1072
|
+
When a source function reads `state`, `containingState`, `ancestors`, or the
|
|
1073
|
+
entry `event`, `from.effect` infers that owner context and the returned Effect's
|
|
1074
|
+
output, error, and service channels together. No return annotation is needed:
|
|
991
1075
|
|
|
992
1076
|
```ts
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1077
|
+
machine.handle({
|
|
1078
|
+
Loading: {
|
|
1079
|
+
invoke: (from) =>
|
|
1080
|
+
from.effect("load", ({ state }) => LoadService.load(state.userId))
|
|
1081
|
+
.onDone((to) => to.full.Loaded().resolve(({ output, target }) => target.from({ user: output })))
|
|
1082
|
+
.onFailure((to) => to.full.LoadFailed().resolve(({ error, target }) => target.from({ error })))
|
|
1083
|
+
}
|
|
998
1084
|
})
|
|
999
1085
|
```
|
|
1000
1086
|
|
|
@@ -1010,42 +1096,44 @@ const machine = Machine.make({
|
|
|
1010
1096
|
// ...
|
|
1011
1097
|
}).handle({
|
|
1012
1098
|
Saving: {
|
|
1013
|
-
invoke:
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
onFailure: (to) => to.none
|
|
1022
|
-
})
|
|
1099
|
+
invoke: (from) =>
|
|
1100
|
+
from.effect("notify-parent", () => saveDocument)
|
|
1101
|
+
.onDone((to) =>
|
|
1102
|
+
to.none.resolve(({ parent, self }, enqueue) => {
|
|
1103
|
+
enqueue.sendTo(self, Commands.Save())
|
|
1104
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
1105
|
+
}))
|
|
1106
|
+
.onFailure((to) => to.none)
|
|
1023
1107
|
}
|
|
1024
1108
|
})
|
|
1025
1109
|
```
|
|
1026
1110
|
|
|
1027
|
-
The
|
|
1028
|
-
|
|
1029
|
-
|
|
1111
|
+
The computation, logic, or child descriptor may be named separately. The
|
|
1112
|
+
invocation chain remains inline because it is bound to its owning state and
|
|
1113
|
+
machine protocols. Return an array of completed chains when a state owns more
|
|
1114
|
+
than one activity.
|
|
1030
1115
|
|
|
1031
|
-
A cancellable timer uses
|
|
1116
|
+
A cancellable timer uses its dedicated source selector:
|
|
1032
1117
|
|
|
1033
1118
|
```ts
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1119
|
+
machine.handle({
|
|
1120
|
+
Waiting: {
|
|
1121
|
+
invoke: (from) =>
|
|
1122
|
+
from.timer("clear-status", "3 seconds")
|
|
1123
|
+
.onDone((to) => to.full.Clear())
|
|
1124
|
+
}
|
|
1038
1125
|
})
|
|
1039
1126
|
```
|
|
1040
1127
|
|
|
1041
1128
|
The timer starts on state entry and is interrupted on exit. Its `onDone` is
|
|
1042
|
-
always required.
|
|
1043
|
-
cancellation behavior, but `
|
|
1044
|
-
duration through `Machine.activityDefinitions`. Effect sources are
|
|
1045
|
-
factories evaluated when their state is entered. For reusable process
|
|
1046
|
-
|
|
1047
|
-
checks the address protocol against the logic event
|
|
1048
|
-
addresses serve different purposes and must both
|
|
1129
|
+
always required. An Effect containing `Effect.sleep(...)` has the same scoped
|
|
1130
|
+
cancellation behavior, but `from.timer` records timer intent and exposes a
|
|
1131
|
+
static duration through `Machine.activityDefinitions`. Effect sources are
|
|
1132
|
+
always factories evaluated when their state is entered. For reusable process
|
|
1133
|
+
logic, pass a state-local lifecycle id plus `{ logic, address }` to
|
|
1134
|
+
`from.logic`. TypeScript checks the address protocol against the logic event
|
|
1135
|
+
protocol. Lifecycle ids and addresses serve different purposes and must both
|
|
1136
|
+
be explicit.
|
|
1049
1137
|
|
|
1050
1138
|
## Invoked child statecharts
|
|
1051
1139
|
|
|
@@ -1058,10 +1146,12 @@ const Editor = Machine.child("editor", EditorMachine)
|
|
|
1058
1146
|
Invoke it from its owning state:
|
|
1059
1147
|
|
|
1060
1148
|
```ts
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1149
|
+
machine.handle({
|
|
1150
|
+
Editing: {
|
|
1151
|
+
invoke: (from) =>
|
|
1152
|
+
from.child(Editor, { input: editorInput })
|
|
1153
|
+
.onDone((to) => to.full.EditorDone().resolve(({ output, target }) => target.from({ output })))
|
|
1154
|
+
}
|
|
1065
1155
|
})
|
|
1066
1156
|
```
|
|
1067
1157
|
|
|
@@ -1089,7 +1179,7 @@ logic that does not have a complete machine descriptor.
|
|
|
1089
1179
|
### Inspecting state-owned activities
|
|
1090
1180
|
|
|
1091
1181
|
Use `Machine.activityDefinitions(machine)` to inspect invokes without running
|
|
1092
|
-
them. Static inline
|
|
1182
|
+
them. Static inline fluent invocation definitions expose serializable ownership
|
|
1093
1183
|
metadata:
|
|
1094
1184
|
|
|
1095
1185
|
```ts
|
|
@@ -1189,6 +1279,22 @@ const decoded = yield* Machine.decodeSnapshot(machine, encoded)
|
|
|
1189
1279
|
const ref = yield* Machine.resume(machine, decoded)
|
|
1190
1280
|
```
|
|
1191
1281
|
|
|
1282
|
+
`Machine.Snapshot` is the decoded, process-local representation. It may retain
|
|
1283
|
+
`Schema.Class` instances and capabilities that cannot cross a JSON boundary.
|
|
1284
|
+
`Machine.EncodedSnapshot` is different: successful `encodeSnapshot` calls
|
|
1285
|
+
guarantee canonical `Schema.Json` for every active value, completion output,
|
|
1286
|
+
and history value. Schema codecs convert rich values such as `Date`, `bigint`,
|
|
1287
|
+
and `undefined` to their declared JSON forms. Cycles, functions, symbols, and
|
|
1288
|
+
opaque values without a JSON representation fail with the typed
|
|
1289
|
+
`MachineSchemaEncodeError`; they never become a later `JSON.stringify` defect.
|
|
1290
|
+
|
|
1291
|
+
Keep DOM nodes, open handles, services, and similar capabilities in an Effect
|
|
1292
|
+
service or UI adapter. Local events may carry those values when they stay inside
|
|
1293
|
+
one process. Cluster public input events, persisted state, and completion
|
|
1294
|
+
outputs are transport protocols and must instead declare JSON-compatible
|
|
1295
|
+
encoded forms; use an explicit transform or `Schema.toCodecJson` where the
|
|
1296
|
+
canonical codec is the intended wire contract.
|
|
1297
|
+
|
|
1192
1298
|
Pass only a decoded `Machine.Snapshot` to `resume`; encoded or arbitrary
|
|
1193
1299
|
transport data belongs at `decodeSnapshot`. Resumption validates and normalizes
|
|
1194
1300
|
the logical snapshot again, then publishes it as the fresh runtime's first
|
|
@@ -1382,7 +1488,7 @@ reference model when correctness of the expected behavior matters.
|
|
|
1382
1488
|
Select the initial root separately from constructing its value:
|
|
1383
1489
|
|
|
1384
1490
|
```ts
|
|
1385
|
-
initial: (to) => to.Idle()
|
|
1491
|
+
initial: (to) => to.Idle()
|
|
1386
1492
|
```
|
|
1387
1493
|
|
|
1388
1494
|
### Invoked child expects events not accepted by the parent
|
|
@@ -1467,6 +1573,6 @@ The current API does not include:
|
|
|
1467
1573
|
- declarative first-class guards;
|
|
1468
1574
|
- a complete inspectable graph for arbitrary transition Effects.
|
|
1469
1575
|
|
|
1470
|
-
Use ordinary TypeScript conditions for guards and
|
|
1471
|
-
|
|
1472
|
-
properties such as `guard`.
|
|
1576
|
+
Use ordinary TypeScript conditions for guards and inline
|
|
1577
|
+
`invoke: (from) => from.timer(...)` chains for state-scoped timers. Do not
|
|
1578
|
+
invent undocumented state-node 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.19.0",
|
|
4
4
|
"description": "Schema-first state machines and statecharts for Effect",
|
|
5
5
|
"author": "Sandro Maglione",
|
|
6
6
|
"repository": {
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
"provenance": true
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"effect": "4.0.0-rc.
|
|
49
|
+
"effect": "4.0.0-rc.110"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@changesets/cli": "2.31.0",
|
|
53
|
-
"@effect/vitest": "4.0.0-rc.
|
|
53
|
+
"@effect/vitest": "4.0.0-rc.110",
|
|
54
54
|
"@types/node": "25.7.0",
|
|
55
55
|
"dprint": "0.55.2",
|
|
56
|
-
"effect": "4.0.0-rc.
|
|
56
|
+
"effect": "4.0.0-rc.110",
|
|
57
57
|
"pagefind": "1.5.2",
|
|
58
58
|
"tinybench": "2.9.0",
|
|
59
59
|
"tstyche": "7.2.1",
|