@typeonce/effect-machine 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -106
- package/dist/Machine.d.ts +324 -367
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +61 -96
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/atom.d.ts +7 -7
- package/dist/internal/machine/atom.d.ts.map +1 -1
- package/dist/internal/machine/atom.js.map +1 -1
- package/dist/internal/machine/cluster.d.ts +2 -2
- package/dist/internal/machine/cluster.d.ts.map +1 -1
- package/dist/internal/machine/cluster.js +6 -5
- package/dist/internal/machine/cluster.js.map +1 -1
- package/dist/internal/machine/executionPlan.d.ts.map +1 -1
- package/dist/internal/machine/executionPlan.js +10 -3
- package/dist/internal/machine/executionPlan.js.map +1 -1
- package/dist/internal/machine/machine.d.ts +8 -6
- package/dist/internal/machine/machine.d.ts.map +1 -1
- package/dist/internal/machine/machine.js +191 -25
- package/dist/internal/machine/machine.js.map +1 -1
- package/dist/internal/machine/planner.d.ts +19 -4
- package/dist/internal/machine/planner.d.ts.map +1 -1
- package/dist/internal/machine/planner.js +56 -20
- package/dist/internal/machine/planner.js.map +1 -1
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -1
- package/dist/internal/machine/stateDefinition.js +14 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -1
- package/dist/internal/machine/topology.d.ts +9 -0
- package/dist/internal/machine/topology.d.ts.map +1 -1
- package/dist/internal/machine/topology.js +16 -0
- package/dist/internal/machine/topology.js.map +1 -1
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -1
- package/dist/internal/testing/machine/finiteModel.js +15 -20
- package/dist/internal/testing/machine/finiteModel.js.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.d.ts.map +1 -1
- package/dist/internal/testing/machine/transitionCoverage.js +2 -0
- package/dist/internal/testing/machine/transitionCoverage.js.map +1 -1
- package/dist/testing/MachineTest.d.ts +11 -11
- package/dist/testing/MachineTest.d.ts.map +1 -1
- package/dist/testing/MachineTest.js +5 -8
- package/dist/testing/MachineTest.js.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.d.ts +2 -2
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -1
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.d.ts +12 -12
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -1
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -1
- package/docs/agent-guide.md +144 -147
- package/package.json +1 -1
- package/src/Machine.ts +1118 -1178
- package/src/internal/machine/atom.ts +20 -15
- package/src/internal/machine/cluster.ts +14 -9
- package/src/internal/machine/executionPlan.ts +8 -3
- package/src/internal/machine/machine.ts +301 -40
- package/src/internal/machine/planner.ts +89 -27
- package/src/internal/machine/stateDefinition.ts +39 -0
- package/src/internal/machine/topology.ts +28 -0
- package/src/internal/testing/machine/finiteModel.ts +18 -21
- package/src/internal/testing/machine/transitionCoverage.ts +2 -0
- package/src/testing/MachineTest.ts +14 -11
- package/src/unstable/cluster/ClusterMachine.ts +8 -4
- package/src/unstable/reactivity/AtomMachine.ts +19 -12
|
@@ -7,13 +7,7 @@
|
|
|
7
7
|
import * as Cause from "effect/Cause"
|
|
8
8
|
import * as Effect from "effect/Effect"
|
|
9
9
|
import type * as Schema from "effect/Schema"
|
|
10
|
-
import type {
|
|
11
|
-
Enqueue,
|
|
12
|
-
InitialEvent as MachineInitialEvent,
|
|
13
|
-
Machine,
|
|
14
|
-
MachineReferences,
|
|
15
|
-
MachineTarget
|
|
16
|
-
} from "../../Machine.js"
|
|
10
|
+
import type { Enqueue, InitialEvent as MachineInitialEvent, Machine, MachineTarget } from "../../Machine.js"
|
|
17
11
|
import { getTargetBuilder, makeCollector, type RuntimeCommand } from "./command.js"
|
|
18
12
|
import {
|
|
19
13
|
type ActiveConfiguration,
|
|
@@ -53,6 +47,7 @@ import {
|
|
|
53
47
|
getNode,
|
|
54
48
|
type InitialTarget as InitialTargetInstruction,
|
|
55
49
|
isChoiceTarget,
|
|
50
|
+
isDeclined,
|
|
56
51
|
isHistoryTarget,
|
|
57
52
|
isInitialTarget,
|
|
58
53
|
isNoTarget,
|
|
@@ -104,10 +99,10 @@ export type MacrostepPlan<State, Event, E, R, Output> =
|
|
|
104
99
|
export type TransitionHandler<States extends Machine.StateSchemas, E, R, Context> = (
|
|
105
100
|
context: Context,
|
|
106
101
|
enqueue: Enqueue<any, any>
|
|
107
|
-
) => Machine.HandlerResult<States, E, R>
|
|
102
|
+
) => Machine.HandlerResult<States, E, R> | Machine.Declined
|
|
108
103
|
|
|
109
104
|
type TransitionEvaluation<States extends Machine.StateSchemas, E, R> = {
|
|
110
|
-
readonly result: Machine.HandlerResult<States, E, R>
|
|
105
|
+
readonly result: Machine.HandlerResult<States, E, R> | Machine.Declined
|
|
111
106
|
readonly branchIndex: number
|
|
112
107
|
readonly branchKey: string | undefined
|
|
113
108
|
}
|
|
@@ -117,24 +112,42 @@ type TransitionEvaluator<States extends Machine.StateSchemas, E, R, Context> = (
|
|
|
117
112
|
enqueue: Enqueue<any, any>
|
|
118
113
|
) => TransitionEvaluation<States, E, R>
|
|
119
114
|
|
|
120
|
-
|
|
121
|
-
|
|
115
|
+
type PlanningMachineReferences = {
|
|
116
|
+
readonly self: MachineTarget<any>
|
|
117
|
+
readonly parent: MachineTarget<any> | undefined
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type BehaviorMachineReferences = {
|
|
121
|
+
readonly self: MachineTarget<any>
|
|
122
|
+
readonly parent?: MachineTarget<any> | undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const rootMachineReferences = new WeakMap<Machine.Any, BehaviorMachineReferences>()
|
|
126
|
+
const scopedMachineReferences = new WeakMap<Machine.Any, BehaviorMachineReferences>()
|
|
127
|
+
|
|
128
|
+
const exposeMachineReferences = (
|
|
129
|
+
machine: Machine.Any,
|
|
130
|
+
machineReferences: PlanningMachineReferences
|
|
131
|
+
): BehaviorMachineReferences =>
|
|
132
|
+
machine.parent === undefined
|
|
133
|
+
? { self: machineReferences.self }
|
|
134
|
+
: { self: machineReferences.self, parent: machineReferences.parent }
|
|
122
135
|
|
|
123
136
|
export const withMachineReferences = (
|
|
124
137
|
machine: Machine.Any,
|
|
125
|
-
machineReferences:
|
|
138
|
+
machineReferences: PlanningMachineReferences
|
|
126
139
|
): Machine.Any => {
|
|
127
140
|
const scoped = Object.create(machine) as Machine.Any
|
|
128
|
-
scopedMachineReferences.set(scoped,
|
|
141
|
+
scopedMachineReferences.set(scoped, exposeMachineReferences(machine, machineReferences))
|
|
129
142
|
return scoped
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
const resolveMachineReferences = (
|
|
133
146
|
machine: Machine.Any,
|
|
134
147
|
configuration: ActiveConfiguration
|
|
135
|
-
):
|
|
148
|
+
): BehaviorMachineReferences => {
|
|
136
149
|
const scope = configuration.machineReferences
|
|
137
|
-
if (scope !== undefined) return scope
|
|
150
|
+
if (scope !== undefined) return exposeMachineReferences(machine, scope)
|
|
138
151
|
const machineScope = scopedMachineReferences.get(machine)
|
|
139
152
|
if (machineScope !== undefined) return machineScope
|
|
140
153
|
const cached = rootMachineReferences.get(machine)
|
|
@@ -144,7 +157,7 @@ const resolveMachineReferences = (
|
|
|
144
157
|
sessionId: "Machine.plan",
|
|
145
158
|
send: () => Effect.fail(new StoppedError())
|
|
146
159
|
}
|
|
147
|
-
const root = { self, parent: undefined }
|
|
160
|
+
const root = exposeMachineReferences(machine, { self, parent: undefined })
|
|
148
161
|
rootMachineReferences.set(machine, root)
|
|
149
162
|
return root
|
|
150
163
|
}
|
|
@@ -153,6 +166,7 @@ type EventTransition<States extends Machine.StateSchemas, E, R, Context> =
|
|
|
153
166
|
| TransitionHandler<States, E, R, Context>
|
|
154
167
|
| {
|
|
155
168
|
readonly reenter?: boolean
|
|
169
|
+
readonly declinable?: boolean
|
|
156
170
|
readonly targets?: ReadonlyArray<string>
|
|
157
171
|
readonly transition: TransitionHandler<States, E, R, Context>
|
|
158
172
|
readonly evaluate?: TransitionEvaluator<States, E, R, Context>
|
|
@@ -160,6 +174,7 @@ type EventTransition<States extends Machine.StateSchemas, E, R, Context> =
|
|
|
160
174
|
|
|
161
175
|
export type MicrostepTransition<States extends Machine.StateSchemas, E, R, Context> = {
|
|
162
176
|
readonly reenter: boolean
|
|
177
|
+
readonly declinable: boolean
|
|
163
178
|
readonly targets: ReadonlyArray<string> | undefined
|
|
164
179
|
readonly transition: TransitionHandler<States, E, R, Context>
|
|
165
180
|
readonly evaluate: TransitionEvaluator<States, E, R, Context> | undefined
|
|
@@ -172,9 +187,10 @@ export const normalizeTransition = <States extends Machine.StateSchemas, E, R, C
|
|
|
172
187
|
return undefined
|
|
173
188
|
}
|
|
174
189
|
return typeof transition === "function"
|
|
175
|
-
? { reenter: false, targets: undefined, transition, evaluate: undefined }
|
|
190
|
+
? { reenter: false, declinable: false, targets: undefined, transition, evaluate: undefined }
|
|
176
191
|
: {
|
|
177
192
|
reenter: transition.reenter === true,
|
|
193
|
+
declinable: transition.declinable === true,
|
|
178
194
|
targets: transition.targets,
|
|
179
195
|
transition: transition.transition,
|
|
180
196
|
evaluate: transition.evaluate
|
|
@@ -218,7 +234,11 @@ const collectTransition = <
|
|
|
218
234
|
const evaluated = evaluate === undefined
|
|
219
235
|
? { result: transition(context, collected.enqueue), branchIndex: 0, branchKey: undefined }
|
|
220
236
|
: evaluate(context, collected.enqueue)
|
|
237
|
+
if (isDeclined(evaluated.result)) {
|
|
238
|
+
return { declined: true as const }
|
|
239
|
+
}
|
|
221
240
|
return {
|
|
241
|
+
declined: false as const,
|
|
222
242
|
state: isNoTarget(evaluated.result) ? undefined : evaluated.result,
|
|
223
243
|
branchIndex: evaluated.branchIndex,
|
|
224
244
|
branchKey: evaluated.branchKey,
|
|
@@ -521,6 +541,29 @@ export type SelectedTransition<States extends Machine.StateSchemas, E, R, Contex
|
|
|
521
541
|
readonly trigger: Machine.TransitionTrigger
|
|
522
542
|
readonly transition: MicrostepTransition<States, E, R, Context>
|
|
523
543
|
readonly context: Context
|
|
544
|
+
readonly collected?: {
|
|
545
|
+
readonly declined: false
|
|
546
|
+
readonly state: unknown
|
|
547
|
+
readonly branchIndex: number
|
|
548
|
+
readonly branchKey: string | undefined
|
|
549
|
+
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
550
|
+
readonly raisedEvents: ReadonlyArray<unknown>
|
|
551
|
+
readonly emittedEvents: ReadonlyArray<unknown>
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const resolveDeclinableCandidate = <States extends Machine.StateSchemas, E, R, Context>(
|
|
556
|
+
machine: Machine.Any,
|
|
557
|
+
selection: SelectedTransition<States, E, R, Context>
|
|
558
|
+
): SelectedTransition<States, E, R, Context> | undefined => {
|
|
559
|
+
if (!selection.transition.declinable) return selection
|
|
560
|
+
const collected = collectTransition(
|
|
561
|
+
machine,
|
|
562
|
+
selection.transition.transition,
|
|
563
|
+
selection.context,
|
|
564
|
+
selection.transition.evaluate
|
|
565
|
+
)
|
|
566
|
+
return collected.declined ? undefined : { ...selection, collected }
|
|
524
567
|
}
|
|
525
568
|
|
|
526
569
|
export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R, Context> = {
|
|
@@ -759,15 +802,16 @@ const selectAlwaysTransitions = <
|
|
|
759
802
|
>
|
|
760
803
|
> = []
|
|
761
804
|
const selectedSources = new Set<string>()
|
|
805
|
+
const evaluatedSources = new Map<string, SelectedTransition<States, E, R, any> | undefined>()
|
|
762
806
|
let snapshot: Machine.Snapshot<States> | undefined
|
|
763
807
|
const capturedSnapshot = () => snapshot ??= snapshotFromConfiguration<States>(machine, configuration)
|
|
764
808
|
for (const leaf of getActiveLeafPaths(machine, configuration)) {
|
|
765
809
|
for (const path of getLeafCandidatePaths(machine, leaf)) {
|
|
766
810
|
const always = normalizeTransition(machine.handlers[path]?.always)
|
|
767
811
|
if (always !== undefined) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
812
|
+
let candidate = evaluatedSources.get(path)
|
|
813
|
+
if (!evaluatedSources.has(path)) {
|
|
814
|
+
candidate = resolveDeclinableCandidate(machine, {
|
|
771
815
|
sourcePath: path,
|
|
772
816
|
leafPath: leaf,
|
|
773
817
|
trigger: { type: "always" },
|
|
@@ -796,6 +840,12 @@ const selectAlwaysTransitions = <
|
|
|
796
840
|
target: getTargetBuilder(machine, path)
|
|
797
841
|
}
|
|
798
842
|
})
|
|
843
|
+
evaluatedSources.set(path, candidate)
|
|
844
|
+
}
|
|
845
|
+
if (candidate === undefined) continue
|
|
846
|
+
if (!selectedSources.has(path)) {
|
|
847
|
+
selectedSources.add(path)
|
|
848
|
+
selected.push(candidate as (typeof selected)[number])
|
|
799
849
|
}
|
|
800
850
|
break
|
|
801
851
|
}
|
|
@@ -838,7 +888,7 @@ const selectDoneTransitions = <
|
|
|
838
888
|
const onDone = normalizeTransition(machine.handlers[completion.path]?.onDone)
|
|
839
889
|
if (onDone !== undefined && !selectedSources.has(completion.path)) {
|
|
840
890
|
selectedSources.add(completion.path)
|
|
841
|
-
|
|
891
|
+
const candidate = resolveDeclinableCandidate(machine, {
|
|
842
892
|
sourcePath: completion.path,
|
|
843
893
|
leafPath: getActiveLeafPathFrom(machine, configuration, completion.path),
|
|
844
894
|
trigger: { type: "done" },
|
|
@@ -857,6 +907,7 @@ const selectDoneTransitions = <
|
|
|
857
907
|
capturedSnapshot()
|
|
858
908
|
)
|
|
859
909
|
})
|
|
910
|
+
if (candidate !== undefined) selected.push(candidate)
|
|
860
911
|
}
|
|
861
912
|
}
|
|
862
913
|
return selected
|
|
@@ -897,15 +948,16 @@ const selectEventTransitions = <
|
|
|
897
948
|
>
|
|
898
949
|
> = []
|
|
899
950
|
const selectedSources = new Set<string>()
|
|
951
|
+
const evaluatedSources = new Map<string, SelectedTransition<States, E, R, any> | undefined>()
|
|
900
952
|
let snapshot: Machine.Snapshot<States> | undefined
|
|
901
953
|
const capturedSnapshot = () => snapshot ??= snapshotFromConfiguration<States>(machine, configuration)
|
|
902
954
|
for (const leaf of getActiveLeafPaths(machine, configuration)) {
|
|
903
955
|
for (const path of getLeafCandidatePaths(machine, leaf)) {
|
|
904
956
|
const transition = normalizeTransition(machine.handlers[path]?.on?.[event._tag])
|
|
905
957
|
if (transition !== undefined) {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
958
|
+
let candidate = evaluatedSources.get(path)
|
|
959
|
+
if (!evaluatedSources.has(path)) {
|
|
960
|
+
candidate = resolveDeclinableCandidate(machine, {
|
|
909
961
|
sourcePath: path,
|
|
910
962
|
leafPath: leaf,
|
|
911
963
|
trigger: { type: "event", event: event._tag },
|
|
@@ -931,6 +983,12 @@ const selectEventTransitions = <
|
|
|
931
983
|
Machine.TagOf<Events[number]>
|
|
932
984
|
>(machine as any, configuration, path, event, capturedSnapshot())
|
|
933
985
|
})
|
|
986
|
+
evaluatedSources.set(path, candidate)
|
|
987
|
+
}
|
|
988
|
+
if (candidate === undefined) continue
|
|
989
|
+
if (!selectedSources.has(path)) {
|
|
990
|
+
selectedSources.add(path)
|
|
991
|
+
selected.push(candidate as (typeof selected)[number])
|
|
934
992
|
}
|
|
935
993
|
break
|
|
936
994
|
}
|
|
@@ -983,13 +1041,14 @@ const selectInvocationTransition = <
|
|
|
983
1041
|
? { error: event.error }
|
|
984
1042
|
: { snapshot: event.snapshot })
|
|
985
1043
|
}
|
|
986
|
-
|
|
1044
|
+
const candidate = resolveDeclinableCandidate(machine, {
|
|
987
1045
|
sourcePath: event.path,
|
|
988
1046
|
leafPath: getActiveLeafPathFrom(machine, configuration, event.path),
|
|
989
1047
|
trigger: { type: "invoke", id: event.id, outcome: event.type },
|
|
990
1048
|
transition: transition as unknown as MicrostepTransition<States, E, R, any>,
|
|
991
1049
|
context
|
|
992
|
-
}
|
|
1050
|
+
})
|
|
1051
|
+
return candidate === undefined ? [] : [candidate]
|
|
993
1052
|
}
|
|
994
1053
|
|
|
995
1054
|
export const getTargetNodePath = <const States extends Machine.StateSchemas>(
|
|
@@ -1275,12 +1334,15 @@ const collectEvaluatedTransition = <
|
|
|
1275
1334
|
selection: SelectedTransition<States, E, R, Context>
|
|
1276
1335
|
) => {
|
|
1277
1336
|
const stateIdentifier = selection.leafPath
|
|
1278
|
-
const transitionResult = collectTransition<States, Event, E, R, Context>(
|
|
1337
|
+
const transitionResult = selection.collected ?? collectTransition<States, Event, E, R, Context>(
|
|
1279
1338
|
machine,
|
|
1280
1339
|
selection.transition.transition,
|
|
1281
1340
|
selection.context,
|
|
1282
1341
|
selection.transition.evaluate
|
|
1283
1342
|
)
|
|
1343
|
+
if (transitionResult.declined) {
|
|
1344
|
+
throw new Error("Machine transition returned decline without declaring declinable: true")
|
|
1345
|
+
}
|
|
1284
1346
|
const unresolvedTarget = transitionResult.state === undefined
|
|
1285
1347
|
? undefined
|
|
1286
1348
|
: transitionResult.state as
|
|
@@ -138,6 +138,22 @@ const assertPlainRecord: (
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
const validateTargetSelectorChildKey = (
|
|
142
|
+
boundary: StateDefinitionBoundary,
|
|
143
|
+
path: string,
|
|
144
|
+
states: unknown,
|
|
145
|
+
key: "initial" | "with",
|
|
146
|
+
message: string
|
|
147
|
+
): void => {
|
|
148
|
+
if (typeof states !== "object" || states === null || Array.isArray(states) || !hasOwn(states, key)) return
|
|
149
|
+
const child = (states as Readonly<Record<string, unknown>>)[key]
|
|
150
|
+
if (
|
|
151
|
+
typeof child === "object" && child !== null && !Schema.isSchema(child) &&
|
|
152
|
+
(child as Readonly<Record<string, unknown>>).type === "history"
|
|
153
|
+
) return
|
|
154
|
+
fail(boundary, `${path}.${key}`, message)
|
|
155
|
+
}
|
|
156
|
+
|
|
141
157
|
const assertAllowedProperties = (
|
|
142
158
|
boundary: StateDefinitionBoundary,
|
|
143
159
|
path: string,
|
|
@@ -252,6 +268,22 @@ const validateStateTree = (
|
|
|
252
268
|
if (typeof node.initial !== "string") {
|
|
253
269
|
fail(boundary, `${path}.initial`, "compound states must declare an initial child key")
|
|
254
270
|
}
|
|
271
|
+
validateTargetSelectorChildKey(
|
|
272
|
+
boundary,
|
|
273
|
+
path,
|
|
274
|
+
node.states,
|
|
275
|
+
"initial",
|
|
276
|
+
"active and choice child states cannot use the reserved target selector key \"initial\""
|
|
277
|
+
)
|
|
278
|
+
if (valued) {
|
|
279
|
+
validateTargetSelectorChildKey(
|
|
280
|
+
boundary,
|
|
281
|
+
path,
|
|
282
|
+
node.states,
|
|
283
|
+
"with",
|
|
284
|
+
"schema-backed compound child states cannot use the reserved local target selector key \"with\""
|
|
285
|
+
)
|
|
286
|
+
}
|
|
255
287
|
const initialKey = node.initial as string
|
|
256
288
|
validateStateTree(boundary, node.states, path, true)
|
|
257
289
|
if (!hasOwn(node.states as object, initialKey)) {
|
|
@@ -270,6 +302,13 @@ const validateStateTree = (
|
|
|
270
302
|
if (!hasOwn(node, "states")) {
|
|
271
303
|
fail(boundary, `${path}.states`, "parallel states must declare child regions")
|
|
272
304
|
}
|
|
305
|
+
validateTargetSelectorChildKey(
|
|
306
|
+
boundary,
|
|
307
|
+
path,
|
|
308
|
+
node.states,
|
|
309
|
+
"initial",
|
|
310
|
+
"active and choice child states cannot use the reserved target selector key \"initial\""
|
|
311
|
+
)
|
|
273
312
|
validateStateTree(boundary, node.states, path, true)
|
|
274
313
|
}
|
|
275
314
|
}
|
|
@@ -25,6 +25,8 @@ export const ChoiceTargetTypeId: unique symbol = Symbol("effect/Machine/ChoiceTa
|
|
|
25
25
|
|
|
26
26
|
export const NoTargetTypeId: unique symbol = Symbol("effect/Machine/NoTarget")
|
|
27
27
|
|
|
28
|
+
export const DeclinedTypeId: unique symbol = Symbol("effect/Machine/Declined")
|
|
29
|
+
|
|
28
30
|
export const TargetSelectionTypeId: unique symbol = Symbol("effect/Machine/TargetSelection")
|
|
29
31
|
|
|
30
32
|
export const SelectedBranchTypeId: unique symbol = Symbol("effect/Machine/SelectedBranch")
|
|
@@ -66,6 +68,11 @@ export interface NoTarget {
|
|
|
66
68
|
readonly [NoTargetTypeId]: typeof NoTargetTypeId
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
/** Internal marker returned when a declinable transition is not enabled. */
|
|
72
|
+
export interface Declined {
|
|
73
|
+
readonly [DeclinedTypeId]: typeof DeclinedTypeId
|
|
74
|
+
}
|
|
75
|
+
|
|
69
76
|
export type TargetSelectionKind = "state" | "initial" | "history" | "choice" | "none"
|
|
70
77
|
|
|
71
78
|
export type TargetSelectionScope = "local" | "branch" | "full" | "initial"
|
|
@@ -99,6 +106,9 @@ export const makeTargetSelection = (
|
|
|
99
106
|
path
|
|
100
107
|
})
|
|
101
108
|
|
|
109
|
+
/** Source-independent definition-time selection for an explicitly targetless transition. */
|
|
110
|
+
export const noneTargetSelection: TargetSelection = makeTargetSelection("none", undefined, "local")
|
|
111
|
+
|
|
102
112
|
export const isTargetSelection = (u: unknown): u is TargetSelection => hasProperty(u, TargetSelectionTypeId)
|
|
103
113
|
|
|
104
114
|
export const makeSelectedBranch = (
|
|
@@ -125,6 +135,14 @@ export const makeNoTarget = (): Machine.NoTarget => noTarget
|
|
|
125
135
|
|
|
126
136
|
export const isNoTarget = (u: unknown): u is Machine.NoTarget => hasProperty(u, NoTargetTypeId)
|
|
127
137
|
|
|
138
|
+
const declined = Object.freeze({
|
|
139
|
+
[DeclinedTypeId]: DeclinedTypeId
|
|
140
|
+
}) as Declined
|
|
141
|
+
|
|
142
|
+
export const makeDeclined = (): Machine.Declined => declined
|
|
143
|
+
|
|
144
|
+
export const isDeclined = (u: unknown): u is Machine.Declined => hasProperty(u, DeclinedTypeId)
|
|
145
|
+
|
|
128
146
|
export const makeHistoryTarget = (path: string, parent: string): HistoryTarget => ({
|
|
129
147
|
[HistoryTargetTypeId]: HistoryTargetTypeId,
|
|
130
148
|
path,
|
|
@@ -441,6 +459,11 @@ const transitionBranches = (handler: unknown): ReadonlyArray<Machine.TransitionB
|
|
|
441
459
|
? Array.from(handler.branches as ReadonlyArray<Machine.TransitionBranch>)
|
|
442
460
|
: []
|
|
443
461
|
|
|
462
|
+
const transitionAcceptance = (handler: unknown): Machine.TransitionAcceptance =>
|
|
463
|
+
typeof handler === "object" && handler !== null && "declinable" in handler && handler.declinable === true
|
|
464
|
+
? "declinable"
|
|
465
|
+
: "required"
|
|
466
|
+
|
|
444
467
|
export const transitionDefinitions = (
|
|
445
468
|
machine: Machine.Any
|
|
446
469
|
): ReadonlyArray<Machine.TransitionDefinition> => {
|
|
@@ -457,6 +480,7 @@ export const transitionDefinitions = (
|
|
|
457
480
|
source: node.path,
|
|
458
481
|
trigger: { type: "choice" },
|
|
459
482
|
reenter: false,
|
|
483
|
+
acceptance: "required",
|
|
460
484
|
branches: transitionBranches(choice)
|
|
461
485
|
})
|
|
462
486
|
}
|
|
@@ -468,6 +492,7 @@ export const transitionDefinitions = (
|
|
|
468
492
|
source: node.path,
|
|
469
493
|
trigger: { type: "event", event },
|
|
470
494
|
reenter: hasProperty(handler, "reenter") && handler.reenter === true,
|
|
495
|
+
acceptance: transitionAcceptance(handler),
|
|
471
496
|
branches: transitionBranches(handler)
|
|
472
497
|
})
|
|
473
498
|
}
|
|
@@ -476,6 +501,7 @@ export const transitionDefinitions = (
|
|
|
476
501
|
source: node.path,
|
|
477
502
|
trigger: { type: "always" },
|
|
478
503
|
reenter: false,
|
|
504
|
+
acceptance: transitionAcceptance(config.always),
|
|
479
505
|
branches: transitionBranches(config.always)
|
|
480
506
|
})
|
|
481
507
|
}
|
|
@@ -484,6 +510,7 @@ export const transitionDefinitions = (
|
|
|
484
510
|
source: node.path,
|
|
485
511
|
trigger: { type: "done" },
|
|
486
512
|
reenter: false,
|
|
513
|
+
acceptance: transitionAcceptance(config.onDone),
|
|
487
514
|
branches: transitionBranches(config.onDone)
|
|
488
515
|
})
|
|
489
516
|
}
|
|
@@ -507,6 +534,7 @@ export const transitionDefinitions = (
|
|
|
507
534
|
source: node.path,
|
|
508
535
|
trigger: { type: "invoke", id, outcome },
|
|
509
536
|
reenter: typeof handler === "object" && handler !== null && handler.reenter === true,
|
|
537
|
+
acceptance: transitionAcceptance(handler),
|
|
510
538
|
branches: transitionBranches(handler)
|
|
511
539
|
})
|
|
512
540
|
}
|
|
@@ -1316,7 +1316,7 @@ const selectHistoryTarget = (builder: Record<string, any>, path: string): unknow
|
|
|
1316
1316
|
const parts = path.split(".")
|
|
1317
1317
|
let current: any = builder
|
|
1318
1318
|
for (let index = 0; index < parts.length - 1; index++) current = current[parts[index]!]
|
|
1319
|
-
return current[parts[parts.length - 1]!]
|
|
1319
|
+
return current[parts[parts.length - 1]!]
|
|
1320
1320
|
}
|
|
1321
1321
|
|
|
1322
1322
|
const selectableDefinitionTarget = (
|
|
@@ -1356,7 +1356,7 @@ const selectDefinitionTarget = (
|
|
|
1356
1356
|
const parts = selectable.split(".")
|
|
1357
1357
|
for (const part of parts) current = current[part]
|
|
1358
1358
|
if (typeof current === "function") return current()
|
|
1359
|
-
return current.initial
|
|
1359
|
+
return current.initial
|
|
1360
1360
|
}
|
|
1361
1361
|
|
|
1362
1362
|
const resolveDefinitionTarget = (
|
|
@@ -1415,11 +1415,10 @@ const makeHandlers = (
|
|
|
1415
1415
|
if (node._tag === "History") continue
|
|
1416
1416
|
if (node._tag === "Choice") {
|
|
1417
1417
|
handlers[node.key] = {
|
|
1418
|
-
choice: (
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
} as any)
|
|
1418
|
+
choice: (to: Record<string, any>) =>
|
|
1419
|
+
(selectDefinitionTarget(to, path, node.selected, byPath) as any).resolve(
|
|
1420
|
+
({ target }: { readonly target: any }) => resolveDefinitionTarget(target, path, node.selected, byPath)
|
|
1421
|
+
)
|
|
1423
1422
|
}
|
|
1424
1423
|
continue
|
|
1425
1424
|
}
|
|
@@ -1432,17 +1431,16 @@ const makeHandlers = (
|
|
|
1432
1431
|
let onDone: unknown
|
|
1433
1432
|
for (const transition of transitions.values()) {
|
|
1434
1433
|
if (transition.source !== path) continue
|
|
1435
|
-
const config = (
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
transition.target
|
|
1439
|
-
|
|
1440
|
-
: selectDefinitionTarget(to, path, transition.target, byPath),
|
|
1441
|
-
resolve: transition.target === undefined
|
|
1434
|
+
const config = (to: Record<string, any>) => {
|
|
1435
|
+
const selected = transition.target === undefined
|
|
1436
|
+
? to.none
|
|
1437
|
+
: selectDefinitionTarget(to, path, transition.target, byPath)
|
|
1438
|
+
const resolve = transition.target === undefined
|
|
1442
1439
|
? () => undefined
|
|
1443
1440
|
: ({ target }: { readonly target: any }) =>
|
|
1444
1441
|
resolveDefinitionTarget(target, path, transition.target!, byPath, transition.targetValue)
|
|
1445
|
-
|
|
1442
|
+
return selected.resolve(resolve, "reenter" in transition ? { reenter: transition.reenter } : undefined)
|
|
1443
|
+
}
|
|
1446
1444
|
if (transition.trigger.type === "event") on[transition.trigger.event] = config
|
|
1447
1445
|
else if (transition.trigger.type === "always") always = config
|
|
1448
1446
|
else onDone = config
|
|
@@ -1515,13 +1513,12 @@ export const compileModel = (model: FiniteModel): Machine.Machine.Any => {
|
|
|
1515
1513
|
const machine = Machine.make({
|
|
1516
1514
|
states: defined.states as any,
|
|
1517
1515
|
events: Machine.events(...eventSchemas) as any,
|
|
1518
|
-
initial: {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
},
|
|
1523
|
-
resolve: ({ target }: { readonly target: any }) =>
|
|
1516
|
+
initial: (to: Record<string, any>) => {
|
|
1517
|
+
const selected = to[initial.path]
|
|
1518
|
+
const selection = typeof selected === "function" ? selected() : selected.initial
|
|
1519
|
+
return selection.resolve(({ target }: { readonly target: any }) =>
|
|
1524
1520
|
selectSnapshot({ [initial.path]: target }, initial.path, byPath, [initial.path], 0) as any
|
|
1521
|
+
)
|
|
1525
1522
|
}
|
|
1526
1523
|
} as any)
|
|
1527
1524
|
const transitions = new Map(model.transitions.map((transition) => [
|
|
@@ -66,6 +66,7 @@ export const makeTransitionCoverageCollector = <M extends AnyMachine>(
|
|
|
66
66
|
source: definition.source,
|
|
67
67
|
trigger: definition.trigger,
|
|
68
68
|
reenter: definition.reenter,
|
|
69
|
+
acceptance: definition.acceptance,
|
|
69
70
|
branches: definition.branches
|
|
70
71
|
})
|
|
71
72
|
)
|
|
@@ -85,6 +86,7 @@ export const makeTransitionCoverageCollector = <M extends AnyMachine>(
|
|
|
85
86
|
source: definition.source,
|
|
86
87
|
trigger: definition.trigger,
|
|
87
88
|
reenter: definition.reenter,
|
|
89
|
+
acceptance: definition.acceptance,
|
|
88
90
|
branch
|
|
89
91
|
})
|
|
90
92
|
})
|
|
@@ -138,6 +138,10 @@ type ReadyMachine<M extends AnyMachine> =
|
|
|
138
138
|
Machine.Machine.OutputStates<M>
|
|
139
139
|
>
|
|
140
140
|
|
|
141
|
+
type RootReadyMachine<M extends AnyMachine> =
|
|
142
|
+
& ReadyMachine<M>
|
|
143
|
+
& Machine.Machine.RootCompatible<Machine.Machine.ParentEvents<M>>
|
|
144
|
+
|
|
141
145
|
/**
|
|
142
146
|
* A generated public-input scenario for a machine.
|
|
143
147
|
*
|
|
@@ -238,10 +242,8 @@ export interface Scenarios<M extends AnyMachine> {
|
|
|
238
242
|
* }).handle({
|
|
239
243
|
* Idle: {
|
|
240
244
|
* on: {
|
|
241
|
-
* Reset:
|
|
242
|
-
* target
|
|
243
|
-
* resolve: ({ target }) => target.from()
|
|
244
|
-
* })
|
|
245
|
+
* Reset: (to) =>
|
|
246
|
+
* to.full.Idle().resolve(({ target }) => target.from())
|
|
245
247
|
* }
|
|
246
248
|
* }
|
|
247
249
|
* })
|
|
@@ -881,7 +883,7 @@ export { PlannerRuntimeAgreementError } from "../internal/testing/machine/verifi
|
|
|
881
883
|
* @since 0.4.0
|
|
882
884
|
*/
|
|
883
885
|
export const assertPlannerRuntimeAgreement: <M extends AnyMachine, Error, Output>(
|
|
884
|
-
machine:
|
|
886
|
+
machine: RootReadyMachine<M>,
|
|
885
887
|
transcript: CausalRuntimeEvidence<M, Error, Output>
|
|
886
888
|
) => Effect.Effect<void, internal.PlannerRuntimeAgreementError<M>, RunServices<M>> =
|
|
887
889
|
internal.assertPlannerRuntimeAgreement
|
|
@@ -1447,10 +1449,9 @@ export type ExploreOptions<M extends AnyMachine, Key extends ExplorationKey = Ex
|
|
|
1447
1449
|
* }).handle({
|
|
1448
1450
|
* Count: {
|
|
1449
1451
|
* on: {
|
|
1450
|
-
* Increment:
|
|
1451
|
-
*
|
|
1452
|
-
*
|
|
1453
|
-
* })
|
|
1452
|
+
* Increment: (to) =>
|
|
1453
|
+
* to.full.Count().resolve(({ state, target }) =>
|
|
1454
|
+
* target(new Count({ value: state.value + 1 })))
|
|
1454
1455
|
* }
|
|
1455
1456
|
* }
|
|
1456
1457
|
* })
|
|
@@ -1465,7 +1466,7 @@ export type ExploreOptions<M extends AnyMachine, Key extends ExplorationKey = Ex
|
|
|
1465
1466
|
* @since 0.4.0
|
|
1466
1467
|
*/
|
|
1467
1468
|
export const explore: <M extends AnyMachine, Key extends ExplorationKey>(
|
|
1468
|
-
machine:
|
|
1469
|
+
machine: RootReadyMachine<M>,
|
|
1469
1470
|
options: ExploreOptions<M, Key>
|
|
1470
1471
|
) => Effect.Effect<
|
|
1471
1472
|
Exploration<M, Key>,
|
|
@@ -1646,7 +1647,7 @@ export type RunServices<M extends AnyMachine> = IsAny<
|
|
|
1646
1647
|
* @since 0.4.0
|
|
1647
1648
|
*/
|
|
1648
1649
|
export const run: <M extends AnyMachine>(
|
|
1649
|
-
machine:
|
|
1650
|
+
machine: RootReadyMachine<M>,
|
|
1650
1651
|
scenario: Scenario<M>
|
|
1651
1652
|
) => Effect.Effect<Trace<M>, RunFailure<RunError<M>, M>, RunServices<M>> = internal.run
|
|
1652
1653
|
|
|
@@ -1703,6 +1704,7 @@ export interface TransitionDefinitionCoverageItem<
|
|
|
1703
1704
|
readonly source: SourcePath
|
|
1704
1705
|
readonly trigger: Machine.Machine.TransitionTrigger<EventTag>
|
|
1705
1706
|
readonly reenter: boolean
|
|
1707
|
+
readonly acceptance: Machine.Machine.TransitionAcceptance
|
|
1706
1708
|
readonly branches: ReadonlyArray<Machine.Machine.TransitionBranch<TargetPath>>
|
|
1707
1709
|
}
|
|
1708
1710
|
|
|
@@ -1725,6 +1727,7 @@ export interface TransitionBranchCoverageItem<
|
|
|
1725
1727
|
readonly source: SourcePath
|
|
1726
1728
|
readonly trigger: Machine.Machine.TransitionTrigger<EventTag>
|
|
1727
1729
|
readonly reenter: boolean
|
|
1730
|
+
readonly acceptance: Machine.Machine.TransitionAcceptance
|
|
1728
1731
|
readonly branch: Machine.Machine.TransitionBranch<TargetPath>
|
|
1729
1732
|
}
|
|
1730
1733
|
|
|
@@ -342,7 +342,8 @@ export const make: <
|
|
|
342
342
|
Output,
|
|
343
343
|
Emits extends ReadonlyArray<Machine.Machine.TaggedSchema>,
|
|
344
344
|
OutputStates extends Machine.Machine.StateIdentifier<States>,
|
|
345
|
-
InputEvents extends ReadonlyArray<Machine.Machine.TaggedSchema> = Events
|
|
345
|
+
InputEvents extends ReadonlyArray<Machine.Machine.TaggedSchema> = Events,
|
|
346
|
+
ParentEvents extends ReadonlyArray<Machine.Machine.TaggedSchema> = readonly []
|
|
346
347
|
>(
|
|
347
348
|
type: Type,
|
|
348
349
|
machine:
|
|
@@ -359,9 +360,11 @@ export const make: <
|
|
|
359
360
|
Output,
|
|
360
361
|
Emits,
|
|
361
362
|
OutputStates,
|
|
362
|
-
InputEvents
|
|
363
|
+
InputEvents,
|
|
364
|
+
ParentEvents
|
|
363
365
|
>
|
|
364
|
-
& EnsureExecutable<States, UnhandledStates, OutputStates
|
|
366
|
+
& EnsureExecutable<States, UnhandledStates, OutputStates>
|
|
367
|
+
& Machine.Machine.RootCompatible<ParentEvents>,
|
|
365
368
|
options: {
|
|
366
369
|
readonly version: string
|
|
367
370
|
},
|
|
@@ -381,7 +384,8 @@ export const make: <
|
|
|
381
384
|
Output,
|
|
382
385
|
Emits,
|
|
383
386
|
OutputStates,
|
|
384
|
-
InputEvents
|
|
387
|
+
InputEvents,
|
|
388
|
+
ParentEvents
|
|
385
389
|
>,
|
|
386
390
|
| ExcludeCompatibleRuntime<
|
|
387
391
|
Machine.ExecutionServices<R | InitialR>,
|