@typeonce/effect-machine 0.14.1 → 0.15.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 +68 -45
- package/dist/Machine.d.ts +178 -68
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +81 -24
- package/dist/Machine.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 +12 -7
- package/dist/internal/machine/machine.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/testing/machine/finiteModel.js +1 -1
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +7 -7
- package/dist/testing/MachineTest.js +7 -7
- package/dist/unstable/cluster/ClusterMachine.d.ts +1 -1
- package/dist/unstable/cluster/ClusterMachine.js +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +3 -3
- package/dist/unstable/reactivity/AtomMachine.js +3 -3
- package/docs/agent-guide.md +144 -51
- package/package.json +1 -1
- package/src/Machine.ts +279 -97
- package/src/internal/machine/machine.ts +27 -20
- package/src/internal/machine/stateDefinition.ts +41 -1
- package/src/internal/testing/machine/finiteModel.ts +1 -1
- package/src/testing/MachineTest.ts +7 -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
|
|
@@ -121,9 +126,9 @@ its extra control is required:
|
|
|
121
126
|
- Use one invocation object: `effect` for one-shot work, `after` for a timer,
|
|
122
127
|
`logic` for reusable process logic, and `child` for a complete child
|
|
123
128
|
statechart. `Machine.invoke({...})` preserves owner state and source channels
|
|
124
|
-
across sibling lifecycle handlers.
|
|
125
|
-
|
|
126
|
-
definition
|
|
129
|
+
across sibling lifecycle handlers. Inside `.handle(...)`, `self` and `parent`
|
|
130
|
+
use the owning definition's exact public input and `parentEvents` protocols.
|
|
131
|
+
The bound `definition.invoke({...})` form is equivalent, not required.
|
|
127
132
|
- Use `Machine.child(id, machine)` for a complete statechart descriptor and
|
|
128
133
|
`Machine.childAddress<Event>(id)` for a low-level process address. A logic
|
|
129
134
|
invocation is addressable only when `Machine.invoke` receives that
|
|
@@ -133,11 +138,88 @@ its extra control is required:
|
|
|
133
138
|
|
|
134
139
|
## Atomic, compound, parallel, and history states
|
|
135
140
|
|
|
141
|
+
### Inline topology by default; extract only repeated states
|
|
142
|
+
|
|
143
|
+
Prefer writing the complete topology inline in `Machine.states`. A one-off
|
|
144
|
+
compound or parallel area is easier to understand in place, and extracting it
|
|
145
|
+
does not improve its types. Use `Machine.state` only when the same active state
|
|
146
|
+
definition is mounted more than once. Tagged schemas are already reusable and
|
|
147
|
+
do not need `Machine.state`.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
type TeamSlot = 1 | 2 | 3 | 4 | 5 | 6
|
|
151
|
+
|
|
152
|
+
const TradingSlot = Machine.state({
|
|
153
|
+
initial: "Idle",
|
|
154
|
+
states: {
|
|
155
|
+
Idle: {},
|
|
156
|
+
InSession: State.cases.InSession,
|
|
157
|
+
Applying: State.cases.Applying
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const States = Machine.states({
|
|
162
|
+
root: {
|
|
163
|
+
type: "parallel",
|
|
164
|
+
states: {
|
|
165
|
+
trading: {
|
|
166
|
+
type: "parallel",
|
|
167
|
+
states: {
|
|
168
|
+
slot1: TradingSlot,
|
|
169
|
+
slot2: TradingSlot,
|
|
170
|
+
slot3: TradingSlot,
|
|
171
|
+
slot4: TradingSlot,
|
|
172
|
+
slot5: TradingSlot,
|
|
173
|
+
slot6: TradingSlot
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
// Other explicit regions stay visible here.
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`Machine.state` accepts one active atomic, compound, or parallel node. It
|
|
183
|
+
checks child keys and the compound `initial` at the reusable definition. It is
|
|
184
|
+
not a second model builder, does not define handlers, and does not accept
|
|
185
|
+
history or choice nodes as roots. `Machine.states` remains the complete model
|
|
186
|
+
boundary and captures every mount independently.
|
|
187
|
+
|
|
188
|
+
For a finite family of paths, bind the template to that definition instead of
|
|
189
|
+
maintaining a parallel string table:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const inSessionPath = <const Slot extends TeamSlot>(slot: Slot) =>
|
|
193
|
+
States.path(`root.trading.slot${slot}.InSession`)
|
|
194
|
+
|
|
195
|
+
States.matches(snapshot, inSessionPath(slot))
|
|
196
|
+
AtomMachine.matches(machineAtom, inSessionPath(slot))
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`States.path` is a compile-time identity helper. It accepts a literal or a
|
|
200
|
+
finite template-literal union only when every member is an active path in this
|
|
201
|
+
tree. Renaming a slot or child therefore breaks the path helper at its
|
|
202
|
+
definition rather than leaving a stale catalog.
|
|
203
|
+
|
|
204
|
+
Use the definition-bound snapshot type when a query genuinely needs the full
|
|
205
|
+
machine snapshot:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
const offeredIfSlot = (
|
|
209
|
+
snapshot: Machine.Snapshot<typeof States>,
|
|
210
|
+
slot: TeamSlot
|
|
211
|
+
) =>
|
|
212
|
+
!States.matches(snapshot, inSessionPath(slot))
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Do not derive this type with `Parameters<typeof States.get>[0]`; that depends
|
|
216
|
+
on overload order and does not express ownership by the state definition.
|
|
217
|
+
|
|
136
218
|
An active state does not need a schema unless it owns data. Omit `schema` for
|
|
137
219
|
control-only atomic, compound, parallel, and final states:
|
|
138
220
|
|
|
139
221
|
```ts
|
|
140
|
-
const States = Machine.
|
|
222
|
+
const States = Machine.states({
|
|
141
223
|
Idle: {},
|
|
142
224
|
Form: {
|
|
143
225
|
initial: "Editing",
|
|
@@ -190,7 +272,7 @@ Use a compound state when exactly one child phase is active. It must declare an
|
|
|
190
272
|
```ts
|
|
191
273
|
const FormState = Schema.TaggedUnion({ Saving: { draft: Schema.String } })
|
|
192
274
|
|
|
193
|
-
const FormStates = Machine.
|
|
275
|
+
const FormStates = Machine.states({
|
|
194
276
|
Form: {
|
|
195
277
|
initial: "Editing",
|
|
196
278
|
states: {
|
|
@@ -204,7 +286,7 @@ const FormStates = Machine.defineStates({
|
|
|
204
286
|
Use a parallel state when every direct region is active:
|
|
205
287
|
|
|
206
288
|
```ts
|
|
207
|
-
const ParallelStates = Machine.
|
|
289
|
+
const ParallelStates = Machine.states({
|
|
208
290
|
Screen: {
|
|
209
291
|
type: "parallel",
|
|
210
292
|
states: {
|
|
@@ -231,13 +313,13 @@ Every parallel region needs an active state in initial and full snapshot
|
|
|
231
313
|
builders. The same rule applies when a local or branch target enters an
|
|
232
314
|
inactive nested parallel state.
|
|
233
315
|
|
|
234
|
-
Use `type: "final"` for a terminal leaf in `Machine.
|
|
316
|
+
Use `type: "final"` for a terminal leaf in `Machine.states`. A final
|
|
235
317
|
child completes its compound parent. Put `onDone` on that completed parent,
|
|
236
318
|
never on the final leaf. The definition owns the output schema and the handler
|
|
237
319
|
computes its value:
|
|
238
320
|
|
|
239
321
|
```ts
|
|
240
|
-
const States = Machine.
|
|
322
|
+
const States = Machine.states({
|
|
241
323
|
Done: {
|
|
242
324
|
schema: State.cases.Done,
|
|
243
325
|
type: "final",
|
|
@@ -311,7 +393,7 @@ should remember. It has no schema, is excluded from active state identifiers,
|
|
|
311
393
|
and is addressed only through `target.history`:
|
|
312
394
|
|
|
313
395
|
```ts
|
|
314
|
-
const States = Machine.
|
|
396
|
+
const States = Machine.states({
|
|
315
397
|
checkout: {
|
|
316
398
|
schema: Checkout,
|
|
317
399
|
initial: "shipping",
|
|
@@ -455,7 +537,7 @@ through `schema.makeEffect`, including refinements.
|
|
|
455
537
|
|
|
456
538
|
## Reading state and structural ancestors
|
|
457
539
|
|
|
458
|
-
`Machine.
|
|
540
|
+
`Machine.states` returns typed helpers:
|
|
459
541
|
|
|
460
542
|
```ts
|
|
461
543
|
States.get(snapshot, "Route.Ready")
|
|
@@ -764,8 +846,17 @@ an event for the parent. Both operations validate their schemas.
|
|
|
764
846
|
union handled inside the statechart:
|
|
765
847
|
|
|
766
848
|
```ts
|
|
767
|
-
const Events = Machine.events(
|
|
768
|
-
|
|
849
|
+
const Events = Machine.events(
|
|
850
|
+
Schema.TaggedUnion({
|
|
851
|
+
Save: {}
|
|
852
|
+
})
|
|
853
|
+
)
|
|
854
|
+
const InternalEvents = Machine.internalEvents(
|
|
855
|
+
Schema.TaggedUnion({
|
|
856
|
+
Saved: { id: Schema.String },
|
|
857
|
+
SaveFailed: { message: Schema.String }
|
|
858
|
+
})
|
|
859
|
+
)
|
|
769
860
|
|
|
770
861
|
const definition = Machine.make({
|
|
771
862
|
states: States.states,
|
|
@@ -871,29 +962,30 @@ invoke: Machine.invoke({
|
|
|
871
962
|
})
|
|
872
963
|
```
|
|
873
964
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
through
|
|
965
|
+
Inside `.handle(...)`, the constructor receives the owning machine's public
|
|
966
|
+
input and `parentEvents` protocols contextually. Sources and lifecycle handlers
|
|
967
|
+
can send through `self` and `parent` without naming the definition:
|
|
877
968
|
|
|
878
969
|
```ts
|
|
879
|
-
const
|
|
970
|
+
const machine = Machine.make({
|
|
880
971
|
events: Commands,
|
|
881
972
|
internalEvents: InternalEvents,
|
|
882
973
|
parentEvents: ParentEvents,
|
|
883
974
|
// ...
|
|
884
|
-
})
|
|
885
|
-
|
|
886
|
-
const machine = definition.handle({
|
|
975
|
+
}).handle({
|
|
887
976
|
Saving: {
|
|
888
|
-
invoke:
|
|
977
|
+
invoke: Machine.invoke({
|
|
889
978
|
id: "notify-parent",
|
|
890
|
-
effect: (
|
|
891
|
-
parent === undefined
|
|
892
|
-
? Effect.void
|
|
893
|
-
: parent.send(ParentEvents.SaveStarted()),
|
|
979
|
+
effect: () => saveDocument,
|
|
894
980
|
onDone: Machine.transition({
|
|
895
981
|
target: (to) => to.none(),
|
|
896
|
-
resolve: () =>
|
|
982
|
+
resolve: ({ parent, self }, enqueue) => {
|
|
983
|
+
enqueue.sendTo(self, Commands.Save())
|
|
984
|
+
if (parent !== undefined) {
|
|
985
|
+
enqueue.sendTo(parent, ParentEvents.ChildFinished({ id: "job-1" }))
|
|
986
|
+
}
|
|
987
|
+
return undefined
|
|
988
|
+
}
|
|
897
989
|
}),
|
|
898
990
|
onFailure: Machine.transition({
|
|
899
991
|
target: (to) => to.none(),
|
|
@@ -904,8 +996,9 @@ const machine = definition.handle({
|
|
|
904
996
|
})
|
|
905
997
|
```
|
|
906
998
|
|
|
907
|
-
|
|
908
|
-
|
|
999
|
+
The machine-bound `definition.invoke(...)` form remains equivalent when the
|
|
1000
|
+
definition is already named. A direct `invoke: { ... }` object remains available
|
|
1001
|
+
when lifecycle handlers do not need source-derived context.
|
|
909
1002
|
|
|
910
1003
|
A cancellable timer uses the same object:
|
|
911
1004
|
|
|
@@ -1314,7 +1407,7 @@ repeat the final marker in this handler.
|
|
|
1314
1407
|
|
|
1315
1408
|
### `type: "final"` is rejected by `handle`
|
|
1316
1409
|
|
|
1317
|
-
Move it to `Machine.
|
|
1410
|
+
Move it to `Machine.states`. Definitions own statechart topology;
|
|
1318
1411
|
handlers own behavior.
|
|
1319
1412
|
|
|
1320
1413
|
### Parent property does not exist
|