@typeonce/effect-machine 0.6.0 → 0.6.1
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/package.json +5 -5
- package/src/Machine.ts +6873 -0
- package/src/index.ts +1 -0
- package/src/internal/machine/activities.ts +108 -0
- package/src/internal/machine/atom.ts +636 -0
- package/src/internal/machine/cluster.ts +394 -0
- package/src/internal/machine/command.ts +58 -0
- package/src/internal/machine/commandRuntime.ts +43 -0
- package/src/internal/machine/configuration.ts +1331 -0
- package/src/internal/machine/errors.ts +87 -0
- package/src/internal/machine/executionPlan.ts +996 -0
- package/src/internal/machine/invocation.ts +119 -0
- package/src/internal/machine/machine.ts +1747 -0
- package/src/internal/machine/planner.ts +1933 -0
- package/src/internal/machine/process.ts +906 -0
- package/src/internal/machine/protocol.ts +322 -0
- package/src/internal/machine/readiness.ts +10 -0
- package/src/internal/machine/runtime.ts +2512 -0
- package/src/internal/machine/serialization.ts +498 -0
- package/src/internal/machine/stateDefinition.ts +270 -0
- package/src/internal/machine/symbols.ts +2 -0
- package/src/internal/machine/topology.ts +479 -0
- package/src/internal/testing/machine/arbitrary.ts +102 -0
- package/src/internal/testing/machine/exploration.ts +331 -0
- package/src/internal/testing/machine/finiteModel.ts +1498 -0
- package/src/internal/testing/machine/invariant.ts +372 -0
- package/src/internal/testing/machine/probe.ts +79 -0
- package/src/internal/testing/machine/referenceModel.ts +1505 -0
- package/src/internal/testing/machine/runtime.ts +1710 -0
- package/src/internal/testing/machine/runtimeInvariant.ts +486 -0
- package/src/internal/testing/machine/trace.ts +150 -0
- package/src/internal/testing/machine/verification.ts +1890 -0
- package/src/testing/MachineTest.ts +2067 -0
- package/src/testing/index.ts +7 -0
- package/src/unstable/cluster/ClusterMachine.ts +390 -0
- package/src/unstable/cluster/index.ts +1 -0
- package/src/unstable/reactivity/AtomMachine.ts +649 -0
- package/src/unstable/reactivity/index.ts +1 -0
|
@@ -0,0 +1,1933 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal machine planning helpers.
|
|
3
|
+
*
|
|
4
|
+
* @since 0.4.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as Cause from "effect/Cause"
|
|
8
|
+
import * as Effect from "effect/Effect"
|
|
9
|
+
import type * as Schema from "effect/Schema"
|
|
10
|
+
import type { Enqueue, InitialEvent as MachineInitialEvent, Machine } from "../../Machine.js"
|
|
11
|
+
import { getTargetBuilder, makeCollector, type RuntimeCommand } from "./command.js"
|
|
12
|
+
import {
|
|
13
|
+
type ActiveConfiguration,
|
|
14
|
+
captureHistory,
|
|
15
|
+
compareDocumentOrder,
|
|
16
|
+
completeConfigurationEffect,
|
|
17
|
+
completeConfigurationSync,
|
|
18
|
+
configurationFromHistoryRecord,
|
|
19
|
+
getActiveLeafPathFrom,
|
|
20
|
+
getActiveLeafPaths,
|
|
21
|
+
getActiveValue,
|
|
22
|
+
getHistoryRecord,
|
|
23
|
+
getInitialEntryPaths,
|
|
24
|
+
getLeafPath,
|
|
25
|
+
getParentValue,
|
|
26
|
+
getParentValues,
|
|
27
|
+
getPathToRoot,
|
|
28
|
+
getRootPath,
|
|
29
|
+
isActiveFinalConfiguration,
|
|
30
|
+
isDescendantOf,
|
|
31
|
+
isPathInSubtree,
|
|
32
|
+
normalizeConfiguration,
|
|
33
|
+
normalizeConfigurationEffect,
|
|
34
|
+
normalizeConfigurationSync,
|
|
35
|
+
normalizeTargetConfigurationSync,
|
|
36
|
+
pathDepth,
|
|
37
|
+
snapshotFromConfiguration,
|
|
38
|
+
snapshotFromConfigurationAtPath,
|
|
39
|
+
validateInitialConfiguration
|
|
40
|
+
} from "./configuration.js"
|
|
41
|
+
import { InfiniteTransitionError, MachineSchemaDecodeError, StartupError } from "./errors.js"
|
|
42
|
+
import { decodeEventSync, decodeInputSync, decodeStateValueSync } from "./protocol.js"
|
|
43
|
+
import { InitialEventTypeId } from "./symbols.js"
|
|
44
|
+
import {
|
|
45
|
+
getNode,
|
|
46
|
+
isChoiceTarget,
|
|
47
|
+
isHistoryTarget,
|
|
48
|
+
isSnapshot,
|
|
49
|
+
isTarget,
|
|
50
|
+
makeChoiceTarget,
|
|
51
|
+
makeTarget,
|
|
52
|
+
TargetSnapshotTypeId
|
|
53
|
+
} from "./topology.js"
|
|
54
|
+
|
|
55
|
+
export type MicrostepPlan<State, Event, E, R> = {
|
|
56
|
+
readonly next: State
|
|
57
|
+
readonly event: Event | MachineInitialEvent
|
|
58
|
+
readonly transitions: ReadonlyArray<{
|
|
59
|
+
readonly source: string
|
|
60
|
+
readonly trigger: Machine.TransitionTrigger
|
|
61
|
+
readonly reenter: boolean
|
|
62
|
+
readonly target: string | undefined
|
|
63
|
+
readonly resolvedTarget: string | undefined
|
|
64
|
+
}>
|
|
65
|
+
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
66
|
+
readonly raisedEvents: ReadonlyArray<Event>
|
|
67
|
+
readonly emittedEvents: ReadonlyArray<unknown>
|
|
68
|
+
readonly exitPaths: ReadonlyArray<string>
|
|
69
|
+
readonly entryPaths: ReadonlyArray<string>
|
|
70
|
+
readonly changed: boolean
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type MacrostepPlan<State, Event, E, R, Output> =
|
|
74
|
+
& {
|
|
75
|
+
readonly next: State
|
|
76
|
+
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
77
|
+
readonly microsteps: ReadonlyArray<MicrostepPlan<State, Event, E, R>>
|
|
78
|
+
readonly emittedEvents: ReadonlyArray<unknown>
|
|
79
|
+
}
|
|
80
|
+
& (
|
|
81
|
+
| {
|
|
82
|
+
readonly done: true
|
|
83
|
+
readonly output: Output
|
|
84
|
+
}
|
|
85
|
+
| {
|
|
86
|
+
readonly done: false
|
|
87
|
+
readonly output: undefined
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
export type TransitionHandler<States extends Machine.StateSchemas, E, R, Context> = (
|
|
92
|
+
context: Context,
|
|
93
|
+
enqueue: Enqueue<any, any>
|
|
94
|
+
) => Machine.HandlerResult<States, E, R>
|
|
95
|
+
|
|
96
|
+
type EventTransition<States extends Machine.StateSchemas, E, R, Context> =
|
|
97
|
+
| TransitionHandler<States, E, R, Context>
|
|
98
|
+
| {
|
|
99
|
+
readonly reenter?: boolean
|
|
100
|
+
readonly targets?: ReadonlyArray<string>
|
|
101
|
+
readonly transition: TransitionHandler<States, E, R, Context>
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type MicrostepTransition<States extends Machine.StateSchemas, E, R, Context> = {
|
|
105
|
+
readonly reenter: boolean
|
|
106
|
+
readonly targets: ReadonlyArray<string> | undefined
|
|
107
|
+
readonly transition: TransitionHandler<States, E, R, Context>
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const normalizeTransition = <States extends Machine.StateSchemas, E, R, Context>(
|
|
111
|
+
transition: EventTransition<States, E, R, Context> | undefined
|
|
112
|
+
): MicrostepTransition<States, E, R, Context> | undefined => {
|
|
113
|
+
if (transition === undefined) {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
116
|
+
return typeof transition === "function"
|
|
117
|
+
? { reenter: false, targets: undefined, transition }
|
|
118
|
+
: {
|
|
119
|
+
reenter: transition.reenter === true,
|
|
120
|
+
targets: transition.targets,
|
|
121
|
+
transition: transition.transition
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const collectStateAction = <Context, Event, E, R>(
|
|
126
|
+
machine: Machine.Any,
|
|
127
|
+
handler: ((context: Context, enqueue: Enqueue<any, any>) => Machine.StateActionResult<E, R>) | undefined,
|
|
128
|
+
context: Context
|
|
129
|
+
) => {
|
|
130
|
+
const collected = makeCollector<Event>(machine)
|
|
131
|
+
if (handler === undefined) {
|
|
132
|
+
return {
|
|
133
|
+
commands: collected.commands,
|
|
134
|
+
raisedEvents: collected.raisedEvents,
|
|
135
|
+
emittedEvents: collected.emittedEvents
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
handler(context, collected.enqueue)
|
|
139
|
+
return {
|
|
140
|
+
commands: collected.commands,
|
|
141
|
+
raisedEvents: collected.raisedEvents,
|
|
142
|
+
emittedEvents: collected.emittedEvents
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const collectTransition = <
|
|
147
|
+
const States extends Machine.StateSchemas,
|
|
148
|
+
Event,
|
|
149
|
+
E,
|
|
150
|
+
R,
|
|
151
|
+
Context
|
|
152
|
+
>(
|
|
153
|
+
machine: Machine.Any,
|
|
154
|
+
transition: TransitionHandler<States, E, R, Context>,
|
|
155
|
+
context: Context
|
|
156
|
+
) => {
|
|
157
|
+
const collected = makeCollector<Event>(machine)
|
|
158
|
+
const state = transition(context, collected.enqueue)
|
|
159
|
+
return {
|
|
160
|
+
state,
|
|
161
|
+
commands: collected.commands,
|
|
162
|
+
raisedEvents: collected.raisedEvents,
|
|
163
|
+
emittedEvents: collected.emittedEvents
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const collectStateInitializer = (
|
|
168
|
+
machine: Machine.Any,
|
|
169
|
+
handler: (context: any, enqueue: Enqueue<any, any>) => unknown,
|
|
170
|
+
context: any
|
|
171
|
+
) => {
|
|
172
|
+
const collected = makeCollector<unknown>(machine)
|
|
173
|
+
return {
|
|
174
|
+
value: handler(context, collected.enqueue),
|
|
175
|
+
commands: collected.commands,
|
|
176
|
+
raisedEvents: collected.raisedEvents,
|
|
177
|
+
emittedEvents: collected.emittedEvents
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Completes the intentionally partial configuration held by shallow history.
|
|
182
|
+
* Only a compound node with no remembered child invokes an initializer; deep
|
|
183
|
+
* history never reaches this path. */
|
|
184
|
+
const completeHistoryConfiguration = (
|
|
185
|
+
machine: Machine.Any,
|
|
186
|
+
configuration: ActiveConfiguration,
|
|
187
|
+
event: unknown
|
|
188
|
+
) => {
|
|
189
|
+
const active = new Set(configuration.active)
|
|
190
|
+
const values = new Map(configuration.values)
|
|
191
|
+
const commands: Array<RuntimeCommand> = []
|
|
192
|
+
const raisedEvents: Array<unknown> = []
|
|
193
|
+
const emittedEvents: Array<unknown> = []
|
|
194
|
+
|
|
195
|
+
let changed = true
|
|
196
|
+
while (changed) {
|
|
197
|
+
changed = false
|
|
198
|
+
for (const path of Array.from(active).sort((left, right) => compareDocumentOrder(machine, left, right))) {
|
|
199
|
+
const node = getNode(machine, path)
|
|
200
|
+
if (node.type === "compound" && !node.children.some((child) => active.has(child))) {
|
|
201
|
+
if (node.initial === undefined) {
|
|
202
|
+
throw new Error(`Machine shallow history expected compound state "${path}" to have an initial child`)
|
|
203
|
+
}
|
|
204
|
+
const initializer = machine.handlers[path]?.initial
|
|
205
|
+
if (initializer === undefined) {
|
|
206
|
+
throw new Error(`Machine shallow history requires an initial value implementation for state "${path}"`)
|
|
207
|
+
}
|
|
208
|
+
const current = {
|
|
209
|
+
active,
|
|
210
|
+
values,
|
|
211
|
+
outputs: configuration.outputs,
|
|
212
|
+
history: configuration.history
|
|
213
|
+
} as ActiveConfiguration
|
|
214
|
+
const initialized = collectStateInitializer(machine, initializer, {
|
|
215
|
+
state: getActiveValue(current, path),
|
|
216
|
+
parent: getParentValue(machine, current, path),
|
|
217
|
+
parents: getParentValues(machine, current, path),
|
|
218
|
+
event
|
|
219
|
+
})
|
|
220
|
+
const child = getNode(machine, node.initial)
|
|
221
|
+
active.add(child.path)
|
|
222
|
+
values.set(child.path, decodeStateValueSync(machine, child, initialized.value))
|
|
223
|
+
commands.push(...initialized.commands)
|
|
224
|
+
raisedEvents.push(...initialized.raisedEvents)
|
|
225
|
+
emittedEvents.push(...initialized.emittedEvents)
|
|
226
|
+
changed = true
|
|
227
|
+
}
|
|
228
|
+
if (node.type === "parallel") {
|
|
229
|
+
const missing = node.children.filter((childPath) => !active.has(childPath))
|
|
230
|
+
if (missing.length > 0) {
|
|
231
|
+
const initializer = machine.handlers[path]?.initial
|
|
232
|
+
if (initializer === undefined) {
|
|
233
|
+
throw new Error(`Machine shallow history requires an initial value implementation for state "${path}"`)
|
|
234
|
+
}
|
|
235
|
+
const current = {
|
|
236
|
+
active,
|
|
237
|
+
values,
|
|
238
|
+
outputs: configuration.outputs,
|
|
239
|
+
history: configuration.history
|
|
240
|
+
} as ActiveConfiguration
|
|
241
|
+
const initialized = collectStateInitializer(machine, initializer, {
|
|
242
|
+
state: getActiveValue(current, path),
|
|
243
|
+
parent: getParentValue(machine, current, path),
|
|
244
|
+
parents: getParentValues(machine, current, path),
|
|
245
|
+
event
|
|
246
|
+
})
|
|
247
|
+
if (typeof initialized.value !== "object" || initialized.value === null) {
|
|
248
|
+
throw new Error(`Machine parallel state initializer for "${path}" must return its region values`)
|
|
249
|
+
}
|
|
250
|
+
for (const childPath of missing) {
|
|
251
|
+
const child = getNode(machine, childPath)
|
|
252
|
+
if (!Object.prototype.hasOwnProperty.call(initialized.value, child.key)) {
|
|
253
|
+
throw new Error(`Machine parallel state initializer for "${path}" must return region "${child.key}"`)
|
|
254
|
+
}
|
|
255
|
+
active.add(child.path)
|
|
256
|
+
values.set(
|
|
257
|
+
child.path,
|
|
258
|
+
decodeStateValueSync(machine, child, (initialized.value as Record<string, unknown>)[child.key])
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
commands.push(...initialized.commands)
|
|
262
|
+
raisedEvents.push(...initialized.raisedEvents)
|
|
263
|
+
emittedEvents.push(...initialized.emittedEvents)
|
|
264
|
+
changed = true
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
configuration: {
|
|
271
|
+
active,
|
|
272
|
+
values,
|
|
273
|
+
outputs: new Map<string, unknown>(),
|
|
274
|
+
history: configuration.history
|
|
275
|
+
} as ActiveConfiguration,
|
|
276
|
+
commands,
|
|
277
|
+
raisedEvents,
|
|
278
|
+
emittedEvents
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const resolveHistoryTarget = (
|
|
283
|
+
machine: Machine.Any,
|
|
284
|
+
configuration: ActiveConfiguration,
|
|
285
|
+
target: { readonly path: string; readonly parent: string },
|
|
286
|
+
event: unknown
|
|
287
|
+
) => {
|
|
288
|
+
const node = getNode(machine, target.path)
|
|
289
|
+
if (node.type !== "history" || node.parent !== target.parent) {
|
|
290
|
+
throw new Error(`Machine expected history target "${target.path}" to resolve to its declared parent`)
|
|
291
|
+
}
|
|
292
|
+
const record = getHistoryRecord(configuration, target.path)
|
|
293
|
+
if (record !== undefined) {
|
|
294
|
+
const restored = configurationFromHistoryRecord(machine, configuration, record)
|
|
295
|
+
// Deep records are already complete below the history parent. This pass is
|
|
296
|
+
// still required for parallel ancestors outside that parent whose other
|
|
297
|
+
// regions must be initialized when the ancestor is re-entered.
|
|
298
|
+
const completed = completeHistoryConfiguration(machine, restored, event)
|
|
299
|
+
const snapshot = snapshotFromConfigurationAtPath(machine, completed.configuration, target.parent)
|
|
300
|
+
const values = Object.fromEntries(completed.configuration.values)
|
|
301
|
+
return {
|
|
302
|
+
target: makeTarget(target.parent as any, snapshot.value as any, {
|
|
303
|
+
snapshot: snapshot as any,
|
|
304
|
+
values: values as any
|
|
305
|
+
}),
|
|
306
|
+
commands: completed.commands,
|
|
307
|
+
raisedEvents: completed.raisedEvents,
|
|
308
|
+
emittedEvents: completed.emittedEvents,
|
|
309
|
+
transitions: []
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const key = node.key
|
|
314
|
+
const fallback = machine.handlers[target.parent]?.history?.[key]?.default
|
|
315
|
+
if (fallback === undefined) {
|
|
316
|
+
throw new Error(`Machine history state "${target.path}" requires a default implementation`)
|
|
317
|
+
}
|
|
318
|
+
const collected = collectTransition(machine, fallback, {
|
|
319
|
+
event,
|
|
320
|
+
target: getTargetBuilder(machine, target.parent).full,
|
|
321
|
+
parent: target.parent
|
|
322
|
+
})
|
|
323
|
+
if (collected.state === undefined || isHistoryTarget(collected.state) || !isSnapshot(collected.state)) {
|
|
324
|
+
throw new Error(`Machine history default for "${target.path}" must return a complete snapshot containing its owner`)
|
|
325
|
+
}
|
|
326
|
+
const fallbackChoice = choiceFromTarget(collected.state)
|
|
327
|
+
const choiceResolution = fallbackChoice === undefined
|
|
328
|
+
? undefined
|
|
329
|
+
: resolveChoiceTarget(
|
|
330
|
+
machine,
|
|
331
|
+
{
|
|
332
|
+
active: new Set(),
|
|
333
|
+
values: new Map(),
|
|
334
|
+
outputs: new Map(),
|
|
335
|
+
history: configuration.history
|
|
336
|
+
},
|
|
337
|
+
collected.state,
|
|
338
|
+
event
|
|
339
|
+
)
|
|
340
|
+
let fallbackConfiguration = choiceResolution === undefined
|
|
341
|
+
? normalizeConfigurationSync(machine, collected.state as any)
|
|
342
|
+
: normalizeTargetConfigurationSync(machine, {
|
|
343
|
+
active: new Set(),
|
|
344
|
+
values: new Map(),
|
|
345
|
+
outputs: new Map(),
|
|
346
|
+
history: configuration.history
|
|
347
|
+
}, choiceResolution.target as any)
|
|
348
|
+
for (const additionalTarget of choiceResolution?.additionalTargets ?? []) {
|
|
349
|
+
fallbackConfiguration = normalizeTargetConfigurationSync(
|
|
350
|
+
machine,
|
|
351
|
+
fallbackConfiguration,
|
|
352
|
+
additionalTarget as
|
|
353
|
+
| Machine.Snapshot<any>
|
|
354
|
+
| Machine.Target<any, string>
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
if (!fallbackConfiguration.active.has(target.parent)) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Machine history default for "${target.path}" returned a configuration that does not contain owner state "${target.parent}"`
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
const snapshot = snapshotFromConfigurationAtPath(machine, fallbackConfiguration, target.parent)
|
|
363
|
+
const values = Object.fromEntries(fallbackConfiguration.values)
|
|
364
|
+
return {
|
|
365
|
+
target: makeTarget(target.parent as any, snapshot.value as any, {
|
|
366
|
+
snapshot: snapshot as any,
|
|
367
|
+
values: values as any
|
|
368
|
+
}),
|
|
369
|
+
commands: [...collected.commands, ...(choiceResolution?.commands ?? [])],
|
|
370
|
+
raisedEvents: [...collected.raisedEvents, ...(choiceResolution?.raisedEvents ?? [])],
|
|
371
|
+
emittedEvents: [...collected.emittedEvents, ...(choiceResolution?.emittedEvents ?? [])],
|
|
372
|
+
transitions: choiceResolution?.transitions ?? []
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export type SelectedTransition<States extends Machine.StateSchemas, E, R, Context> = {
|
|
377
|
+
readonly sourcePath: string
|
|
378
|
+
readonly leafPath: string
|
|
379
|
+
readonly trigger: Machine.TransitionTrigger
|
|
380
|
+
readonly transition: MicrostepTransition<States, E, R, Context>
|
|
381
|
+
readonly context: Context
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export type EvaluatedTransition<States extends Machine.StateSchemas, Event, E, R, Context> = {
|
|
385
|
+
readonly selection: SelectedTransition<States, E, R, Context>
|
|
386
|
+
readonly unresolvedTarget:
|
|
387
|
+
| Machine.Snapshot<States>
|
|
388
|
+
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
389
|
+
| Machine.HistoryTarget<States, Machine.HistoryIdentifier<States>>
|
|
390
|
+
| Machine.ChoiceTarget<States, Machine.ChoiceIdentifier<States>>
|
|
391
|
+
| undefined
|
|
392
|
+
readonly target:
|
|
393
|
+
| Machine.Snapshot<States>
|
|
394
|
+
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
395
|
+
| undefined
|
|
396
|
+
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
397
|
+
readonly raisedEvents: ReadonlyArray<Event>
|
|
398
|
+
readonly emittedEvents: ReadonlyArray<unknown>
|
|
399
|
+
readonly changed: boolean
|
|
400
|
+
readonly exitPaths: ReadonlyArray<string>
|
|
401
|
+
readonly entryPaths: ReadonlyArray<string>
|
|
402
|
+
readonly choiceTransitions: ReadonlyArray<{
|
|
403
|
+
readonly source: string
|
|
404
|
+
readonly trigger: Machine.TransitionTrigger
|
|
405
|
+
readonly reenter: false
|
|
406
|
+
readonly target: string
|
|
407
|
+
readonly resolvedTarget: string
|
|
408
|
+
}>
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const getCandidatePaths = (machine: Machine.Any, configuration: ActiveConfiguration): ReadonlyArray<string> =>
|
|
412
|
+
Array.from(configuration.active)
|
|
413
|
+
.sort((left, right) => {
|
|
414
|
+
const depth = pathDepth(machine, right) - pathDepth(machine, left)
|
|
415
|
+
return depth === 0 ? compareDocumentOrder(machine, left, right) : depth
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
const getLeafCandidatePaths = (machine: Machine.Any, leaf: string): ReadonlyArray<string> =>
|
|
419
|
+
[...getPathToRoot(machine, leaf)].reverse()
|
|
420
|
+
|
|
421
|
+
export const getLeastCommonAncestor = (
|
|
422
|
+
machine: Machine.Any,
|
|
423
|
+
left: string,
|
|
424
|
+
right: string
|
|
425
|
+
): string | undefined => {
|
|
426
|
+
const leftPath = getPathToRoot(machine, left)
|
|
427
|
+
const rightPath = getPathToRoot(machine, right)
|
|
428
|
+
let ancestor: string | undefined = undefined
|
|
429
|
+
const length = Math.min(leftPath.length, rightPath.length)
|
|
430
|
+
for (let index = 0; index < length; index++) {
|
|
431
|
+
if (leftPath[index] !== rightPath[index]) {
|
|
432
|
+
break
|
|
433
|
+
}
|
|
434
|
+
ancestor = leftPath[index]
|
|
435
|
+
}
|
|
436
|
+
return ancestor
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export const broadenTransitionBoundary = (
|
|
440
|
+
naturalBoundary: string | undefined,
|
|
441
|
+
reentryBoundary: string | undefined
|
|
442
|
+
): string | undefined => {
|
|
443
|
+
if (naturalBoundary === undefined || reentryBoundary === undefined) return undefined
|
|
444
|
+
return naturalBoundary === reentryBoundary || isDescendantOf(naturalBoundary, reentryBoundary)
|
|
445
|
+
? reentryBoundary
|
|
446
|
+
: naturalBoundary
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export const getExitPaths = (
|
|
450
|
+
machine: Machine.Any,
|
|
451
|
+
configuration: ActiveConfiguration,
|
|
452
|
+
boundary: string | undefined
|
|
453
|
+
): ReadonlyArray<string> =>
|
|
454
|
+
sortExitPaths(
|
|
455
|
+
machine,
|
|
456
|
+
Array.from(configuration.active)
|
|
457
|
+
.filter((path) => boundary === undefined || isDescendantOf(path, boundary))
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
export const getEntryPaths = (
|
|
461
|
+
machine: Machine.Any,
|
|
462
|
+
configuration: ActiveConfiguration,
|
|
463
|
+
boundary: string | undefined
|
|
464
|
+
): ReadonlyArray<string> =>
|
|
465
|
+
sortEntryPaths(
|
|
466
|
+
machine,
|
|
467
|
+
Array.from(configuration.active).filter((path) => boundary === undefined || isDescendantOf(path, boundary))
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
const hasSameActivePaths = (left: ActiveConfiguration, right: ActiveConfiguration): boolean =>
|
|
471
|
+
left.active.size === right.active.size && Array.from(left.active).every((path) => right.active.has(path))
|
|
472
|
+
|
|
473
|
+
export const sortExitPaths = (machine: Machine.Any, paths: Iterable<string>): ReadonlyArray<string> =>
|
|
474
|
+
Array.from(new Set(paths))
|
|
475
|
+
.sort((left, right) => {
|
|
476
|
+
const depth = getPathToRoot(machine, right).length - getPathToRoot(machine, left).length
|
|
477
|
+
return depth === 0 ? getNode(machine, right).order - getNode(machine, left).order : depth
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
export const sortEntryPaths = (machine: Machine.Any, paths: Iterable<string>): ReadonlyArray<string> =>
|
|
481
|
+
Array.from(new Set(paths))
|
|
482
|
+
.sort((left, right) => {
|
|
483
|
+
const depth = getPathToRoot(machine, left).length - getPathToRoot(machine, right).length
|
|
484
|
+
return depth === 0 ? compareDocumentOrder(machine, left, right) : depth
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
const makeStateActionContext = <
|
|
488
|
+
const States extends Machine.StateSchemas,
|
|
489
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
490
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
491
|
+
StateId extends Machine.StateIdentifier<States>
|
|
492
|
+
>(
|
|
493
|
+
machine: Machine.Any,
|
|
494
|
+
configuration: ActiveConfiguration,
|
|
495
|
+
path: string,
|
|
496
|
+
event: Machine.LifecycleEvent<Events>
|
|
497
|
+
): Machine.StateActionContext<States, Events, Emits, StateId> => ({
|
|
498
|
+
state: getActiveValue(configuration, path) as Machine.StateByIdentifier<States, StateId>,
|
|
499
|
+
parent: getParentValue(machine, configuration, path) as Machine.ParentStateValue<States, StateId>,
|
|
500
|
+
parents: getParentValues(machine, configuration, path) as Machine.ParentStateValues<States, StateId>,
|
|
501
|
+
event
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
const makeTransitionContext = <
|
|
505
|
+
const States extends Machine.StateSchemas,
|
|
506
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
507
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
508
|
+
StateId extends Machine.StateIdentifier<States>,
|
|
509
|
+
EventTag extends Machine.TagOf<Events[number]>
|
|
510
|
+
>(
|
|
511
|
+
machine: Machine<States, Events, any, any, any, any, any, any, any, any, Emits>,
|
|
512
|
+
configuration: ActiveConfiguration,
|
|
513
|
+
path: string,
|
|
514
|
+
event: Machine.EventByTag<Events, EventTag>,
|
|
515
|
+
snapshot: Machine.Snapshot<States>
|
|
516
|
+
): Machine.HandlerContext<States, Events, Emits, StateId, EventTag, any, any> => ({
|
|
517
|
+
state: getActiveValue(configuration, path) as Machine.StateByIdentifier<States, StateId>,
|
|
518
|
+
parent: getParentValue(machine, configuration, path) as Machine.ParentStateValue<States, StateId>,
|
|
519
|
+
parents: getParentValues(machine, configuration, path) as Machine.ParentStateValues<States, StateId>,
|
|
520
|
+
event,
|
|
521
|
+
snapshot,
|
|
522
|
+
target: getTargetBuilder(machine, path)
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
const makeDoneContext = <
|
|
526
|
+
const States extends Machine.StateSchemas,
|
|
527
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
528
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
529
|
+
StateId extends Machine.StateIdentifier<States>
|
|
530
|
+
>(
|
|
531
|
+
machine: Machine.Any,
|
|
532
|
+
configuration: ActiveConfiguration,
|
|
533
|
+
path: string,
|
|
534
|
+
event: Machine.LifecycleEvent<Events>,
|
|
535
|
+
output: unknown,
|
|
536
|
+
snapshot: Machine.Snapshot<States>
|
|
537
|
+
): Machine.DoneContext<States, Events, Emits, StateId> => ({
|
|
538
|
+
state: getActiveValue(configuration, path) as Machine.StateByIdentifier<States, StateId>,
|
|
539
|
+
parent: getParentValue(machine, configuration, path) as Machine.ParentStateValue<States, StateId>,
|
|
540
|
+
parents: getParentValues(machine, configuration, path) as Machine.ParentStateValues<States, StateId>,
|
|
541
|
+
event,
|
|
542
|
+
output: output as Machine.CompletionOutputByIdentifier<States, StateId>,
|
|
543
|
+
snapshot,
|
|
544
|
+
target: getTargetBuilder(machine, path)
|
|
545
|
+
})
|
|
546
|
+
|
|
547
|
+
const collectStateActions = <
|
|
548
|
+
const States extends Machine.StateSchemas,
|
|
549
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
550
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
551
|
+
E,
|
|
552
|
+
R
|
|
553
|
+
>(
|
|
554
|
+
machine: Machine.Any,
|
|
555
|
+
configuration: ActiveConfiguration,
|
|
556
|
+
paths: ReadonlyArray<string>,
|
|
557
|
+
event: Machine.LifecycleEvent<Events>,
|
|
558
|
+
key: "entry" | "exit"
|
|
559
|
+
) => {
|
|
560
|
+
const commands: Array<RuntimeCommand> = []
|
|
561
|
+
const raisedEvents: Array<Machine.EventOf<Events>> = []
|
|
562
|
+
const emittedEvents: Array<Machine.EmitOf<Emits>> = []
|
|
563
|
+
for (const path of paths) {
|
|
564
|
+
const collected = collectStateAction<
|
|
565
|
+
Machine.StateActionContext<States, Events, Emits, Machine.StateIdentifier<States>>,
|
|
566
|
+
Machine.EventOf<Events>,
|
|
567
|
+
E,
|
|
568
|
+
R
|
|
569
|
+
>(
|
|
570
|
+
machine,
|
|
571
|
+
machine.handlers[path]?.[key],
|
|
572
|
+
makeStateActionContext<States, Events, Emits, Machine.StateIdentifier<States>>(
|
|
573
|
+
machine,
|
|
574
|
+
configuration,
|
|
575
|
+
path,
|
|
576
|
+
event
|
|
577
|
+
)
|
|
578
|
+
)
|
|
579
|
+
commands.push(...collected.commands)
|
|
580
|
+
raisedEvents.push(...collected.raisedEvents)
|
|
581
|
+
emittedEvents.push(...collected.emittedEvents as ReadonlyArray<Machine.EmitOf<Emits>>)
|
|
582
|
+
}
|
|
583
|
+
return { commands, emittedEvents, raisedEvents }
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const selectAlwaysTransitions = <
|
|
587
|
+
const States extends Machine.StateSchemas,
|
|
588
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
589
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
590
|
+
E,
|
|
591
|
+
R
|
|
592
|
+
>(
|
|
593
|
+
machine: Machine.Any,
|
|
594
|
+
configuration: ActiveConfiguration,
|
|
595
|
+
event: Machine.LifecycleEvent<Events>
|
|
596
|
+
): ReadonlyArray<
|
|
597
|
+
SelectedTransition<
|
|
598
|
+
States,
|
|
599
|
+
E,
|
|
600
|
+
R,
|
|
601
|
+
Machine.AlwaysContext<States, Events, Emits, Machine.StateIdentifier<States>>
|
|
602
|
+
>
|
|
603
|
+
> => {
|
|
604
|
+
const selected: Array<
|
|
605
|
+
SelectedTransition<
|
|
606
|
+
States,
|
|
607
|
+
E,
|
|
608
|
+
R,
|
|
609
|
+
Machine.AlwaysContext<States, Events, Emits, Machine.StateIdentifier<States>>
|
|
610
|
+
>
|
|
611
|
+
> = []
|
|
612
|
+
const selectedSources = new Set<string>()
|
|
613
|
+
let snapshot: Machine.Snapshot<States> | undefined
|
|
614
|
+
const capturedSnapshot = () => snapshot ??= snapshotFromConfiguration<States>(machine, configuration)
|
|
615
|
+
for (const leaf of getActiveLeafPaths(machine, configuration)) {
|
|
616
|
+
for (const path of getLeafCandidatePaths(machine, leaf)) {
|
|
617
|
+
const always = normalizeTransition(machine.handlers[path]?.always)
|
|
618
|
+
if (always !== undefined) {
|
|
619
|
+
if (!selectedSources.has(path)) {
|
|
620
|
+
selectedSources.add(path)
|
|
621
|
+
selected.push({
|
|
622
|
+
sourcePath: path,
|
|
623
|
+
leafPath: leaf,
|
|
624
|
+
trigger: { type: "always" },
|
|
625
|
+
transition: always as unknown as MicrostepTransition<
|
|
626
|
+
States,
|
|
627
|
+
E,
|
|
628
|
+
R,
|
|
629
|
+
Machine.AlwaysContext<States, Events, Emits, Machine.StateIdentifier<States>>
|
|
630
|
+
>,
|
|
631
|
+
context: {
|
|
632
|
+
state: getActiveValue(configuration, path) as Machine.StateByIdentifier<
|
|
633
|
+
States,
|
|
634
|
+
Machine.StateIdentifier<States>
|
|
635
|
+
>,
|
|
636
|
+
parent: getParentValue(machine, configuration, path) as Machine.ParentStateValue<
|
|
637
|
+
States,
|
|
638
|
+
Machine.StateIdentifier<States>
|
|
639
|
+
>,
|
|
640
|
+
parents: getParentValues(machine, configuration, path) as Machine.ParentStateValues<
|
|
641
|
+
States,
|
|
642
|
+
Machine.StateIdentifier<States>
|
|
643
|
+
>,
|
|
644
|
+
event,
|
|
645
|
+
snapshot: capturedSnapshot(),
|
|
646
|
+
target: getTargetBuilder(machine, path)
|
|
647
|
+
}
|
|
648
|
+
})
|
|
649
|
+
}
|
|
650
|
+
break
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return selected
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const selectDoneTransitions = <
|
|
658
|
+
const States extends Machine.StateSchemas,
|
|
659
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
660
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
661
|
+
E,
|
|
662
|
+
R
|
|
663
|
+
>(
|
|
664
|
+
machine: Machine.Any,
|
|
665
|
+
configuration: ActiveConfiguration,
|
|
666
|
+
event: Machine.LifecycleEvent<Events>,
|
|
667
|
+
completions: ReadonlyArray<{ readonly path: string; readonly output: unknown }>
|
|
668
|
+
): ReadonlyArray<
|
|
669
|
+
SelectedTransition<
|
|
670
|
+
States,
|
|
671
|
+
E,
|
|
672
|
+
R,
|
|
673
|
+
Machine.DoneContext<States, Events, Emits, Machine.StateIdentifier<States>>
|
|
674
|
+
>
|
|
675
|
+
> => {
|
|
676
|
+
const selected: Array<
|
|
677
|
+
SelectedTransition<
|
|
678
|
+
States,
|
|
679
|
+
E,
|
|
680
|
+
R,
|
|
681
|
+
Machine.DoneContext<States, Events, Emits, Machine.StateIdentifier<States>>
|
|
682
|
+
>
|
|
683
|
+
> = []
|
|
684
|
+
const selectedSources = new Set<string>()
|
|
685
|
+
let snapshot: Machine.Snapshot<States> | undefined
|
|
686
|
+
const capturedSnapshot = () => snapshot ??= snapshotFromConfiguration<States>(machine, configuration)
|
|
687
|
+
for (const completion of completions) {
|
|
688
|
+
const onDone = normalizeTransition(machine.handlers[completion.path]?.onDone)
|
|
689
|
+
if (onDone !== undefined && !selectedSources.has(completion.path)) {
|
|
690
|
+
selectedSources.add(completion.path)
|
|
691
|
+
selected.push({
|
|
692
|
+
sourcePath: completion.path,
|
|
693
|
+
leafPath: getActiveLeafPathFrom(machine, configuration, completion.path),
|
|
694
|
+
trigger: { type: "done" },
|
|
695
|
+
transition: onDone as unknown as MicrostepTransition<
|
|
696
|
+
States,
|
|
697
|
+
E,
|
|
698
|
+
R,
|
|
699
|
+
Machine.DoneContext<States, Events, Emits, Machine.StateIdentifier<States>>
|
|
700
|
+
>,
|
|
701
|
+
context: makeDoneContext<States, Events, Emits, Machine.StateIdentifier<States>>(
|
|
702
|
+
machine,
|
|
703
|
+
configuration,
|
|
704
|
+
completion.path,
|
|
705
|
+
event,
|
|
706
|
+
completion.output,
|
|
707
|
+
capturedSnapshot()
|
|
708
|
+
)
|
|
709
|
+
})
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return selected
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const selectEventTransitions = <
|
|
716
|
+
const States extends Machine.StateSchemas,
|
|
717
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
718
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
719
|
+
E,
|
|
720
|
+
R
|
|
721
|
+
>(
|
|
722
|
+
machine: Machine.Any,
|
|
723
|
+
configuration: ActiveConfiguration,
|
|
724
|
+
event: Machine.EventByTag<Events, Machine.TagOf<Events[number]>>
|
|
725
|
+
): ReadonlyArray<
|
|
726
|
+
SelectedTransition<
|
|
727
|
+
States,
|
|
728
|
+
E,
|
|
729
|
+
R,
|
|
730
|
+
Machine.HandlerContext<States, Events, Emits, Machine.StateIdentifier<States>, Machine.TagOf<Events[number]>, E, R>
|
|
731
|
+
>
|
|
732
|
+
> => {
|
|
733
|
+
const selected: Array<
|
|
734
|
+
SelectedTransition<
|
|
735
|
+
States,
|
|
736
|
+
E,
|
|
737
|
+
R,
|
|
738
|
+
Machine.HandlerContext<
|
|
739
|
+
States,
|
|
740
|
+
Events,
|
|
741
|
+
Emits,
|
|
742
|
+
Machine.StateIdentifier<States>,
|
|
743
|
+
Machine.TagOf<Events[number]>,
|
|
744
|
+
E,
|
|
745
|
+
R
|
|
746
|
+
>
|
|
747
|
+
>
|
|
748
|
+
> = []
|
|
749
|
+
const selectedSources = new Set<string>()
|
|
750
|
+
let snapshot: Machine.Snapshot<States> | undefined
|
|
751
|
+
const capturedSnapshot = () => snapshot ??= snapshotFromConfiguration<States>(machine, configuration)
|
|
752
|
+
for (const leaf of getActiveLeafPaths(machine, configuration)) {
|
|
753
|
+
for (const path of getLeafCandidatePaths(machine, leaf)) {
|
|
754
|
+
const transition = normalizeTransition(machine.handlers[path]?.on?.[event._tag])
|
|
755
|
+
if (transition !== undefined) {
|
|
756
|
+
if (!selectedSources.has(path)) {
|
|
757
|
+
selectedSources.add(path)
|
|
758
|
+
selected.push({
|
|
759
|
+
sourcePath: path,
|
|
760
|
+
leafPath: leaf,
|
|
761
|
+
trigger: { type: "event", event: event._tag },
|
|
762
|
+
transition: transition as unknown as MicrostepTransition<
|
|
763
|
+
States,
|
|
764
|
+
E,
|
|
765
|
+
R,
|
|
766
|
+
Machine.HandlerContext<
|
|
767
|
+
States,
|
|
768
|
+
Events,
|
|
769
|
+
Emits,
|
|
770
|
+
Machine.StateIdentifier<States>,
|
|
771
|
+
Machine.TagOf<Events[number]>,
|
|
772
|
+
E,
|
|
773
|
+
R
|
|
774
|
+
>
|
|
775
|
+
>,
|
|
776
|
+
context: makeTransitionContext<
|
|
777
|
+
States,
|
|
778
|
+
Events,
|
|
779
|
+
Emits,
|
|
780
|
+
Machine.StateIdentifier<States>,
|
|
781
|
+
Machine.TagOf<Events[number]>
|
|
782
|
+
>(machine as any, configuration, path, event, capturedSnapshot())
|
|
783
|
+
})
|
|
784
|
+
}
|
|
785
|
+
break
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return selected
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
export const getTargetNodePath = <const States extends Machine.StateSchemas>(
|
|
793
|
+
target:
|
|
794
|
+
| Machine.Snapshot<States>
|
|
795
|
+
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
796
|
+
| Machine.HistoryTarget<States, Machine.HistoryIdentifier<States>>
|
|
797
|
+
| Machine.ChoiceTarget<States, Machine.ChoiceIdentifier<States>>
|
|
798
|
+
): string => {
|
|
799
|
+
if (isHistoryTarget(target) || isChoiceTarget(target)) {
|
|
800
|
+
return String(target.path)
|
|
801
|
+
}
|
|
802
|
+
if (isTarget(target)) {
|
|
803
|
+
return String(target.path)
|
|
804
|
+
}
|
|
805
|
+
if (isSnapshot(target)) {
|
|
806
|
+
return String(target.path)
|
|
807
|
+
}
|
|
808
|
+
throw new Error("Machine expected transition target to be a snapshot or target builder result")
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
export const validateDeclaredTransitionTarget = (
|
|
812
|
+
sourcePath: string,
|
|
813
|
+
trigger: Machine.TransitionTrigger,
|
|
814
|
+
declaredTargets: ReadonlyArray<string> | undefined,
|
|
815
|
+
target: unknown
|
|
816
|
+
): void => {
|
|
817
|
+
if (declaredTargets === undefined || target === undefined) {
|
|
818
|
+
return
|
|
819
|
+
}
|
|
820
|
+
const actual = typeof target === "object" && target !== null && "path" in target
|
|
821
|
+
? String(target.path)
|
|
822
|
+
: "<unknown>"
|
|
823
|
+
if (!declaredTargets.some((path) => actual === path || actual.startsWith(`${path}.`))) {
|
|
824
|
+
const triggerLabel = trigger.type === "event" ? String(trigger.event) : trigger.type
|
|
825
|
+
throw new Error(
|
|
826
|
+
`Machine transition from "${sourcePath}" on "${triggerLabel}" returned target "${actual}" outside declared targets: ${
|
|
827
|
+
declaredTargets.length === 0 ? "none" : declaredTargets.map((path) => `"${path}"`).join(", ")
|
|
828
|
+
}`
|
|
829
|
+
)
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const hasPathIntersection = (left: ReadonlyArray<string>, right: ReadonlyArray<string>): boolean => {
|
|
834
|
+
for (const path of left) {
|
|
835
|
+
if (right.includes(path)) {
|
|
836
|
+
return true
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return false
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
export const sortEvaluatedTransitions = <
|
|
843
|
+
const States extends Machine.StateSchemas,
|
|
844
|
+
Event,
|
|
845
|
+
E,
|
|
846
|
+
R,
|
|
847
|
+
Context
|
|
848
|
+
>(
|
|
849
|
+
machine: Machine.Any,
|
|
850
|
+
transitions: Iterable<EvaluatedTransition<States, Event, E, R, Context>>
|
|
851
|
+
): ReadonlyArray<EvaluatedTransition<States, Event, E, R, Context>> =>
|
|
852
|
+
Array.from(transitions)
|
|
853
|
+
.sort((left, right) => compareDocumentOrder(machine, left.selection.sourcePath, right.selection.sourcePath))
|
|
854
|
+
|
|
855
|
+
const removePreemptedAncestorSelections = <
|
|
856
|
+
const States extends Machine.StateSchemas,
|
|
857
|
+
E,
|
|
858
|
+
R,
|
|
859
|
+
Context
|
|
860
|
+
>(
|
|
861
|
+
selections: ReadonlyArray<SelectedTransition<States, E, R, Context>>
|
|
862
|
+
): ReadonlyArray<SelectedTransition<States, E, R, Context>> =>
|
|
863
|
+
selections.filter((selection) =>
|
|
864
|
+
!selections.some((other) =>
|
|
865
|
+
other.sourcePath !== selection.sourcePath && isDescendantOf(other.sourcePath, selection.sourcePath)
|
|
866
|
+
)
|
|
867
|
+
)
|
|
868
|
+
|
|
869
|
+
export const removeConflictingTransitions = <
|
|
870
|
+
const States extends Machine.StateSchemas,
|
|
871
|
+
Event,
|
|
872
|
+
E,
|
|
873
|
+
R,
|
|
874
|
+
Context
|
|
875
|
+
>(
|
|
876
|
+
machine: Machine.Any,
|
|
877
|
+
transitions: ReadonlyArray<EvaluatedTransition<States, Event, E, R, Context>>
|
|
878
|
+
): ReadonlyArray<EvaluatedTransition<States, Event, E, R, Context>> => {
|
|
879
|
+
const filtered: Array<EvaluatedTransition<States, Event, E, R, Context>> = []
|
|
880
|
+
for (const transition of sortEvaluatedTransitions(machine, transitions)) {
|
|
881
|
+
let preempted = false
|
|
882
|
+
const transitionsToRemove = new Set<EvaluatedTransition<States, Event, E, R, Context>>()
|
|
883
|
+
for (const selected of filtered) {
|
|
884
|
+
if (hasPathIntersection(transition.exitPaths, selected.exitPaths)) {
|
|
885
|
+
if (isDescendantOf(transition.selection.sourcePath, selected.selection.sourcePath)) {
|
|
886
|
+
transitionsToRemove.add(selected)
|
|
887
|
+
} else {
|
|
888
|
+
preempted = true
|
|
889
|
+
break
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (!preempted) {
|
|
894
|
+
for (const removed of transitionsToRemove) {
|
|
895
|
+
const index = filtered.indexOf(removed)
|
|
896
|
+
if (index >= 0) {
|
|
897
|
+
filtered.splice(index, 1)
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
filtered.push(transition)
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
return filtered
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
const choicesFromTarget = (
|
|
907
|
+
target: unknown,
|
|
908
|
+
inheritedValues: Readonly<Record<string, unknown>> = {}
|
|
909
|
+
): ReadonlyArray<{
|
|
910
|
+
readonly target: ReturnType<typeof makeChoiceTarget>
|
|
911
|
+
readonly values: Readonly<Record<string, unknown>>
|
|
912
|
+
}> => {
|
|
913
|
+
const values: Record<string, unknown> = { ...inheritedValues }
|
|
914
|
+
const choices: Array<ReturnType<typeof makeChoiceTarget>> = []
|
|
915
|
+
const visit = (current: unknown): void => {
|
|
916
|
+
if (isChoiceTarget(current)) {
|
|
917
|
+
Object.assign(values, current.values ?? {})
|
|
918
|
+
choices.push(current)
|
|
919
|
+
return
|
|
920
|
+
}
|
|
921
|
+
if (typeof current !== "object" || current === null || !("path" in current) || !("value" in current)) return
|
|
922
|
+
values[String(current.path)] = current.value
|
|
923
|
+
if ("state" in current) visit(current.state)
|
|
924
|
+
if ("states" in current && typeof current.states === "object" && current.states !== null) {
|
|
925
|
+
for (const state of Object.values(current.states)) visit(state)
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
visit(target)
|
|
929
|
+
return choices.map((choice) => ({
|
|
930
|
+
target: makeChoiceTarget(choice.path, choice.parent, values),
|
|
931
|
+
values
|
|
932
|
+
}))
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const choiceFromTarget = (
|
|
936
|
+
target: unknown,
|
|
937
|
+
inheritedValues: Readonly<Record<string, unknown>> = {}
|
|
938
|
+
) => choicesFromTarget(target, inheritedValues)[0]
|
|
939
|
+
|
|
940
|
+
const withChoiceValues = (target: unknown, values: Readonly<Record<string, unknown>>): unknown => {
|
|
941
|
+
if (isChoiceTarget(target)) {
|
|
942
|
+
return makeChoiceTarget(target.path, target.parent, { ...values, ...(target.values ?? {}) })
|
|
943
|
+
}
|
|
944
|
+
if (!isTarget(target) || Object.keys(values).length === 0) return target
|
|
945
|
+
const snapshot = target[TargetSnapshotTypeId]
|
|
946
|
+
return makeTarget(target.path, target.value, {
|
|
947
|
+
...(snapshot === undefined ? {} : { snapshot }),
|
|
948
|
+
values: { ...values, ...(target.values ?? {}) }
|
|
949
|
+
})
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
interface ResolvedChoiceTransition {
|
|
953
|
+
readonly source: string
|
|
954
|
+
readonly trigger: Machine.TransitionTrigger
|
|
955
|
+
readonly reenter: false
|
|
956
|
+
readonly target: string
|
|
957
|
+
readonly resolvedTarget: string
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const resolveChoiceTarget = (
|
|
961
|
+
machine: Machine.Any,
|
|
962
|
+
configuration: ActiveConfiguration,
|
|
963
|
+
initialTarget: unknown,
|
|
964
|
+
event: unknown
|
|
965
|
+
) => {
|
|
966
|
+
const pending: Array<unknown> = [initialTarget]
|
|
967
|
+
const resolvedTargets: Array<unknown> = []
|
|
968
|
+
const commands: Array<RuntimeCommand> = []
|
|
969
|
+
const raisedEvents: Array<unknown> = []
|
|
970
|
+
const emittedEvents: Array<unknown> = []
|
|
971
|
+
const transitions: Array<ResolvedChoiceTransition> = []
|
|
972
|
+
let iterations = 0
|
|
973
|
+
|
|
974
|
+
while (pending.length > 0) {
|
|
975
|
+
let current: unknown = pending.shift()
|
|
976
|
+
while (true) {
|
|
977
|
+
const extractedChoices = choicesFromTarget(current)
|
|
978
|
+
const extracted = extractedChoices[0]
|
|
979
|
+
if (extracted === undefined) break
|
|
980
|
+
pending.unshift(...extractedChoices.slice(1).map(({ target }) => target))
|
|
981
|
+
iterations += 1
|
|
982
|
+
if (iterations > MaxMacrostepIterations) {
|
|
983
|
+
throw new InfiniteTransitionError({
|
|
984
|
+
machineId: machine.id,
|
|
985
|
+
state: extracted.target.path,
|
|
986
|
+
maxIterations: MaxMacrostepIterations
|
|
987
|
+
})
|
|
988
|
+
}
|
|
989
|
+
const node = getNode(machine, extracted.target.path)
|
|
990
|
+
if (node.type !== "choice" || node.parent !== extracted.target.parent) {
|
|
991
|
+
throw new Error(`Machine expected choice target "${extracted.target.path}" to resolve to its declared parent`)
|
|
992
|
+
}
|
|
993
|
+
const choice = machine.handlers[node.path]?.choice
|
|
994
|
+
if (choice === undefined || typeof choice.transition !== "function") {
|
|
995
|
+
throw new Error(`Machine choice state "${node.path}" requires an implementation`)
|
|
996
|
+
}
|
|
997
|
+
const provisional: ActiveConfiguration = {
|
|
998
|
+
active: new Set([
|
|
999
|
+
...configuration.active,
|
|
1000
|
+
...Object.keys(extracted.values)
|
|
1001
|
+
]),
|
|
1002
|
+
values: new Map([
|
|
1003
|
+
...configuration.values,
|
|
1004
|
+
...Object.entries(extracted.values)
|
|
1005
|
+
]),
|
|
1006
|
+
outputs: configuration.outputs,
|
|
1007
|
+
history: configuration.history
|
|
1008
|
+
}
|
|
1009
|
+
const collected = collectTransition(machine, choice.transition, {
|
|
1010
|
+
parent: getParentValue(machine, provisional, node.path),
|
|
1011
|
+
parents: getParentValues(machine, provisional, node.path),
|
|
1012
|
+
event,
|
|
1013
|
+
target: getTargetBuilder(machine, node.path)
|
|
1014
|
+
})
|
|
1015
|
+
if (collected.state === undefined) {
|
|
1016
|
+
throw new Error(`Machine choice resolver for "${node.path}" must return a target`)
|
|
1017
|
+
}
|
|
1018
|
+
validateDeclaredTransitionTarget(node.path, { type: "choice" }, choice.targets, collected.state)
|
|
1019
|
+
const returnedPath = getTargetNodePath(collected.state as any)
|
|
1020
|
+
current = withChoiceValues(collected.state, extracted.values)
|
|
1021
|
+
const nested = choiceFromTarget(current)
|
|
1022
|
+
transitions.push({
|
|
1023
|
+
source: node.path,
|
|
1024
|
+
trigger: { type: "choice" },
|
|
1025
|
+
reenter: false,
|
|
1026
|
+
target: returnedPath,
|
|
1027
|
+
resolvedTarget: nested?.target.path ?? returnedPath
|
|
1028
|
+
})
|
|
1029
|
+
commands.push(...collected.commands)
|
|
1030
|
+
raisedEvents.push(...collected.raisedEvents)
|
|
1031
|
+
emittedEvents.push(...collected.emittedEvents)
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (!isTarget(current) && !isSnapshot(current) && !isHistoryTarget(current)) {
|
|
1035
|
+
throw new Error("Machine choice resolver must return a concrete typed target")
|
|
1036
|
+
}
|
|
1037
|
+
resolvedTargets.push(current)
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
return {
|
|
1041
|
+
target: resolvedTargets[0],
|
|
1042
|
+
additionalTargets: resolvedTargets.slice(1),
|
|
1043
|
+
commands,
|
|
1044
|
+
raisedEvents,
|
|
1045
|
+
emittedEvents,
|
|
1046
|
+
transitions
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
const collectEvaluatedTransition = <
|
|
1051
|
+
const States extends Machine.StateSchemas,
|
|
1052
|
+
Event,
|
|
1053
|
+
E,
|
|
1054
|
+
R,
|
|
1055
|
+
Context
|
|
1056
|
+
>(
|
|
1057
|
+
machine: Machine.Any,
|
|
1058
|
+
state: ActiveConfiguration,
|
|
1059
|
+
selection: SelectedTransition<States, E, R, Context>
|
|
1060
|
+
) => {
|
|
1061
|
+
const stateIdentifier = selection.leafPath
|
|
1062
|
+
const transitionResult = collectTransition<States, Event, E, R, Context>(
|
|
1063
|
+
machine,
|
|
1064
|
+
selection.transition.transition,
|
|
1065
|
+
selection.context
|
|
1066
|
+
)
|
|
1067
|
+
const unresolvedTarget = transitionResult.state === undefined
|
|
1068
|
+
? undefined
|
|
1069
|
+
: transitionResult.state as
|
|
1070
|
+
| Machine.Snapshot<States>
|
|
1071
|
+
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
1072
|
+
| Machine.HistoryTarget<States, Machine.HistoryIdentifier<States>>
|
|
1073
|
+
| Machine.ChoiceTarget<States, Machine.ChoiceIdentifier<States>>
|
|
1074
|
+
validateDeclaredTransitionTarget(
|
|
1075
|
+
selection.sourcePath,
|
|
1076
|
+
selection.trigger,
|
|
1077
|
+
selection.transition.targets,
|
|
1078
|
+
unresolvedTarget
|
|
1079
|
+
)
|
|
1080
|
+
const choiceResolution = unresolvedTarget === undefined
|
|
1081
|
+
? undefined
|
|
1082
|
+
: resolveChoiceTarget(
|
|
1083
|
+
machine,
|
|
1084
|
+
state,
|
|
1085
|
+
unresolvedTarget,
|
|
1086
|
+
(selection.context as any).event
|
|
1087
|
+
)
|
|
1088
|
+
const choiceResolvedTarget = choiceResolution?.target ?? unresolvedTarget
|
|
1089
|
+
let historyResolution: {
|
|
1090
|
+
readonly target: unknown
|
|
1091
|
+
readonly commands: ReadonlyArray<RuntimeCommand>
|
|
1092
|
+
readonly raisedEvents: ReadonlyArray<unknown>
|
|
1093
|
+
readonly emittedEvents: ReadonlyArray<unknown>
|
|
1094
|
+
readonly transitions: ReadonlyArray<ResolvedChoiceTransition>
|
|
1095
|
+
} | undefined
|
|
1096
|
+
const reenteredHistoryTarget = isHistoryTarget(choiceResolvedTarget) && selection.transition.reenter &&
|
|
1097
|
+
state.active.has(choiceResolvedTarget.parent)
|
|
1098
|
+
? choiceResolvedTarget
|
|
1099
|
+
: undefined
|
|
1100
|
+
if (choiceResolvedTarget !== undefined && isHistoryTarget(choiceResolvedTarget)) {
|
|
1101
|
+
// A reentering transition may exit the history node's own parent. SCXML
|
|
1102
|
+
// history observes that same exit, so resolve against a provisional
|
|
1103
|
+
// capture rather than an older record (or the default).
|
|
1104
|
+
const provisionalBoundary = selection.transition.reenter
|
|
1105
|
+
? getNode(machine, selection.sourcePath).parent
|
|
1106
|
+
: getLeastCommonAncestor(machine, stateIdentifier, choiceResolvedTarget.parent)
|
|
1107
|
+
const provisionalExitPaths = reenteredHistoryTarget !== undefined
|
|
1108
|
+
? sortExitPaths(
|
|
1109
|
+
machine,
|
|
1110
|
+
Array.from(state.active).filter((path) => isPathInSubtree(path, choiceResolvedTarget.parent))
|
|
1111
|
+
)
|
|
1112
|
+
: getExitPaths(machine, state, provisionalBoundary)
|
|
1113
|
+
const stateAtHistoryResolution = provisionalExitPaths.includes(choiceResolvedTarget.parent)
|
|
1114
|
+
? captureHistory(machine, state, state, provisionalExitPaths)
|
|
1115
|
+
: state
|
|
1116
|
+
historyResolution = resolveHistoryTarget(
|
|
1117
|
+
machine,
|
|
1118
|
+
stateAtHistoryResolution,
|
|
1119
|
+
choiceResolvedTarget,
|
|
1120
|
+
(selection.context as any).event
|
|
1121
|
+
)
|
|
1122
|
+
}
|
|
1123
|
+
const target: Machine.Snapshot<States> | Machine.Target<States, Machine.StateIdentifier<States>> | undefined =
|
|
1124
|
+
historyResolution === undefined
|
|
1125
|
+
? choiceResolvedTarget as
|
|
1126
|
+
| Machine.Snapshot<States>
|
|
1127
|
+
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
1128
|
+
| undefined
|
|
1129
|
+
: historyResolution.target as
|
|
1130
|
+
| Machine.Snapshot<States>
|
|
1131
|
+
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
1132
|
+
const additionalHistoryActions: Array<RuntimeCommand> = []
|
|
1133
|
+
const additionalHistoryRaisedEvents: Array<unknown> = []
|
|
1134
|
+
const additionalHistoryEmittedEvents: Array<unknown> = []
|
|
1135
|
+
const additionalHistoryChoiceTransitions: Array<ResolvedChoiceTransition> = []
|
|
1136
|
+
const additionalChoiceTargets: Array<
|
|
1137
|
+
Machine.Snapshot<States> | Machine.Target<States, Machine.StateIdentifier<States>>
|
|
1138
|
+
> = []
|
|
1139
|
+
for (const additionalTarget of choiceResolution?.additionalTargets ?? []) {
|
|
1140
|
+
if (!isHistoryTarget(additionalTarget)) {
|
|
1141
|
+
additionalChoiceTargets.push(additionalTarget as any)
|
|
1142
|
+
continue
|
|
1143
|
+
}
|
|
1144
|
+
const resolved = resolveHistoryTarget(
|
|
1145
|
+
machine,
|
|
1146
|
+
state,
|
|
1147
|
+
additionalTarget,
|
|
1148
|
+
(selection.context as any).event
|
|
1149
|
+
)
|
|
1150
|
+
additionalChoiceTargets.push(resolved.target as any)
|
|
1151
|
+
additionalHistoryActions.push(...resolved.commands)
|
|
1152
|
+
additionalHistoryRaisedEvents.push(...resolved.raisedEvents)
|
|
1153
|
+
additionalHistoryEmittedEvents.push(...resolved.emittedEvents)
|
|
1154
|
+
additionalHistoryChoiceTransitions.push(...resolved.transitions)
|
|
1155
|
+
}
|
|
1156
|
+
const targetPath = target === undefined
|
|
1157
|
+
? undefined
|
|
1158
|
+
: additionalChoiceTargets.length === 0 || unresolvedTarget === undefined
|
|
1159
|
+
? getTargetNodePath(target)
|
|
1160
|
+
: getTargetNodePath(unresolvedTarget)
|
|
1161
|
+
let stateAfterTransition = target === undefined
|
|
1162
|
+
? state
|
|
1163
|
+
: normalizeTargetConfigurationSync<States>(machine, state, target)
|
|
1164
|
+
for (const additionalTarget of additionalChoiceTargets) {
|
|
1165
|
+
stateAfterTransition = normalizeTargetConfigurationSync<States>(
|
|
1166
|
+
machine,
|
|
1167
|
+
stateAfterTransition,
|
|
1168
|
+
additionalTarget
|
|
1169
|
+
)
|
|
1170
|
+
}
|
|
1171
|
+
const changed = selection.transition.reenter || !hasSameActivePaths(state, stateAfterTransition)
|
|
1172
|
+
|
|
1173
|
+
if (!changed) {
|
|
1174
|
+
return {
|
|
1175
|
+
selection,
|
|
1176
|
+
unresolvedTarget,
|
|
1177
|
+
target,
|
|
1178
|
+
commands: [
|
|
1179
|
+
...transitionResult.commands,
|
|
1180
|
+
...(choiceResolution?.commands ?? []),
|
|
1181
|
+
...(historyResolution?.commands ?? []),
|
|
1182
|
+
...additionalHistoryActions
|
|
1183
|
+
],
|
|
1184
|
+
raisedEvents: [
|
|
1185
|
+
...transitionResult.raisedEvents,
|
|
1186
|
+
...(choiceResolution?.raisedEvents ?? []),
|
|
1187
|
+
...(historyResolution?.raisedEvents ?? []),
|
|
1188
|
+
...additionalHistoryRaisedEvents
|
|
1189
|
+
],
|
|
1190
|
+
emittedEvents: [
|
|
1191
|
+
...transitionResult.emittedEvents,
|
|
1192
|
+
...(choiceResolution?.emittedEvents ?? []),
|
|
1193
|
+
...(historyResolution?.emittedEvents ?? []),
|
|
1194
|
+
...additionalHistoryEmittedEvents
|
|
1195
|
+
],
|
|
1196
|
+
changed,
|
|
1197
|
+
exitPaths: [],
|
|
1198
|
+
entryPaths: [],
|
|
1199
|
+
choiceTransitions: [
|
|
1200
|
+
...(choiceResolution?.transitions ?? []),
|
|
1201
|
+
...(historyResolution?.transitions ?? []),
|
|
1202
|
+
...additionalHistoryChoiceTransitions
|
|
1203
|
+
]
|
|
1204
|
+
} as EvaluatedTransition<States, Event, E, R, Context>
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const naturalBoundary = targetPath === undefined
|
|
1208
|
+
? getNode(machine, selection.sourcePath).parent
|
|
1209
|
+
: getLeastCommonAncestor(machine, stateIdentifier, targetPath)
|
|
1210
|
+
const reentryBoundary = getNode(machine, selection.sourcePath).parent
|
|
1211
|
+
const boundary = selection.transition.reenter
|
|
1212
|
+
? broadenTransitionBoundary(naturalBoundary, reentryBoundary)
|
|
1213
|
+
: naturalBoundary
|
|
1214
|
+
|
|
1215
|
+
return {
|
|
1216
|
+
selection,
|
|
1217
|
+
unresolvedTarget,
|
|
1218
|
+
target,
|
|
1219
|
+
commands: [
|
|
1220
|
+
...transitionResult.commands,
|
|
1221
|
+
...(choiceResolution?.commands ?? []),
|
|
1222
|
+
...(historyResolution?.commands ?? []),
|
|
1223
|
+
...additionalHistoryActions
|
|
1224
|
+
],
|
|
1225
|
+
raisedEvents: [
|
|
1226
|
+
...transitionResult.raisedEvents,
|
|
1227
|
+
...(choiceResolution?.raisedEvents ?? []),
|
|
1228
|
+
...(historyResolution?.raisedEvents ?? []),
|
|
1229
|
+
...additionalHistoryRaisedEvents
|
|
1230
|
+
],
|
|
1231
|
+
emittedEvents: [
|
|
1232
|
+
...transitionResult.emittedEvents,
|
|
1233
|
+
...(choiceResolution?.emittedEvents ?? []),
|
|
1234
|
+
...(historyResolution?.emittedEvents ?? []),
|
|
1235
|
+
...additionalHistoryEmittedEvents
|
|
1236
|
+
],
|
|
1237
|
+
changed,
|
|
1238
|
+
exitPaths: reenteredHistoryTarget !== undefined
|
|
1239
|
+
? sortExitPaths(
|
|
1240
|
+
machine,
|
|
1241
|
+
Array.from(state.active).filter((path) => isPathInSubtree(path, reenteredHistoryTarget.parent))
|
|
1242
|
+
)
|
|
1243
|
+
: getExitPaths(machine, state, boundary),
|
|
1244
|
+
entryPaths: reenteredHistoryTarget !== undefined
|
|
1245
|
+
? sortEntryPaths(
|
|
1246
|
+
machine,
|
|
1247
|
+
Array.from(stateAfterTransition.active).filter((path) => isPathInSubtree(path, reenteredHistoryTarget.parent))
|
|
1248
|
+
)
|
|
1249
|
+
: getEntryPaths(machine, stateAfterTransition, boundary),
|
|
1250
|
+
choiceTransitions: [
|
|
1251
|
+
...(choiceResolution?.transitions ?? []),
|
|
1252
|
+
...(historyResolution?.transitions ?? []),
|
|
1253
|
+
...additionalHistoryChoiceTransitions
|
|
1254
|
+
]
|
|
1255
|
+
} as EvaluatedTransition<States, Event, E, R, Context>
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
export const MaxMacrostepIterations = 1000
|
|
1259
|
+
export const InitialEvent: MachineInitialEvent = { _tag: InitialEventTypeId }
|
|
1260
|
+
|
|
1261
|
+
export const isFinalState = (
|
|
1262
|
+
machine: Machine.Any,
|
|
1263
|
+
state: Machine.Snapshot<any>
|
|
1264
|
+
): boolean => isActiveFinalConfiguration(machine, normalizeConfiguration(machine, state))
|
|
1265
|
+
|
|
1266
|
+
export const getFinalOutputEffect = <
|
|
1267
|
+
const States extends Machine.StateSchemas,
|
|
1268
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1269
|
+
Output
|
|
1270
|
+
>(
|
|
1271
|
+
machine: Machine.Any,
|
|
1272
|
+
state: Machine.Snapshot<States>,
|
|
1273
|
+
event: Machine.LifecycleEvent<Events>
|
|
1274
|
+
): Effect.Effect<Output, MachineSchemaDecodeError> =>
|
|
1275
|
+
normalizeConfigurationEffect(machine, state).pipe(
|
|
1276
|
+
Effect.flatMap((configuration) => completeConfigurationEffect(machine, configuration, event)),
|
|
1277
|
+
Effect.flatMap((completed): Effect.Effect<Output> => {
|
|
1278
|
+
const root = getRootPath(machine, completed.configuration)
|
|
1279
|
+
if (
|
|
1280
|
+
!isActiveFinalConfiguration(machine, completed.configuration)
|
|
1281
|
+
|| !completed.configuration.outputs.has(root)
|
|
1282
|
+
) {
|
|
1283
|
+
return Effect.die(
|
|
1284
|
+
new Error("Machine attempted to read terminal output from a non-terminal configuration")
|
|
1285
|
+
)
|
|
1286
|
+
}
|
|
1287
|
+
return Effect.succeed(completed.configuration.outputs.get(root) as Output)
|
|
1288
|
+
})
|
|
1289
|
+
)
|
|
1290
|
+
|
|
1291
|
+
export const isFinal = <
|
|
1292
|
+
const States extends Machine.StateSchemas,
|
|
1293
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1294
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1295
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
1296
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
1297
|
+
E = never,
|
|
1298
|
+
R = never,
|
|
1299
|
+
InitialE = never,
|
|
1300
|
+
InitialR = never,
|
|
1301
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
1302
|
+
Output = never,
|
|
1303
|
+
OutputStates extends Machine.StateIdentifier<States> = never
|
|
1304
|
+
>(
|
|
1305
|
+
machine: Machine<
|
|
1306
|
+
States,
|
|
1307
|
+
Events,
|
|
1308
|
+
Input,
|
|
1309
|
+
UnhandledStates,
|
|
1310
|
+
E,
|
|
1311
|
+
R,
|
|
1312
|
+
InitialE,
|
|
1313
|
+
InitialR,
|
|
1314
|
+
FinalStates,
|
|
1315
|
+
Output,
|
|
1316
|
+
Emits,
|
|
1317
|
+
OutputStates
|
|
1318
|
+
>,
|
|
1319
|
+
state: Machine.Snapshot<States>
|
|
1320
|
+
): state is Machine.SnapshotContainingFinal<States, FinalStates> => isFinalState(machine, state)
|
|
1321
|
+
|
|
1322
|
+
export const planInitialSync = <
|
|
1323
|
+
const States extends Machine.StateSchemas,
|
|
1324
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1325
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
|
|
1326
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
1327
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
1328
|
+
E = never,
|
|
1329
|
+
R = never,
|
|
1330
|
+
InitialE = never,
|
|
1331
|
+
InitialR = never,
|
|
1332
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
1333
|
+
Output = never
|
|
1334
|
+
>(
|
|
1335
|
+
machine: Machine<States, Events, Input, UnhandledStates, E, R, InitialE, InitialR, FinalStates, Output, Emits>,
|
|
1336
|
+
...args: [...Machine.InputArgs<Input>]
|
|
1337
|
+
) => {
|
|
1338
|
+
const inputArgs = machine.input === undefined
|
|
1339
|
+
? args
|
|
1340
|
+
: args.length === 0
|
|
1341
|
+
? (decodeInputSync(machine, machine.input, undefined), args)
|
|
1342
|
+
: [decodeInputSync(machine, machine.input, args[0])] as [...Machine.InputArgs<Input>]
|
|
1343
|
+
const state = machine.initial(...inputArgs)
|
|
1344
|
+
const emptyConfiguration: ActiveConfiguration = {
|
|
1345
|
+
active: new Set(),
|
|
1346
|
+
values: new Map(),
|
|
1347
|
+
outputs: new Map(),
|
|
1348
|
+
history: new Map()
|
|
1349
|
+
}
|
|
1350
|
+
const initialChoice = choiceFromTarget(state)
|
|
1351
|
+
const choiceResolution = initialChoice === undefined
|
|
1352
|
+
? undefined
|
|
1353
|
+
: resolveChoiceTarget(
|
|
1354
|
+
machine,
|
|
1355
|
+
emptyConfiguration,
|
|
1356
|
+
state,
|
|
1357
|
+
InitialEvent
|
|
1358
|
+
)
|
|
1359
|
+
const initialHistoryActions: Array<RuntimeCommand> = []
|
|
1360
|
+
const initialHistoryRaisedEvents: Array<unknown> = []
|
|
1361
|
+
const initialHistoryEmittedEvents: Array<unknown> = []
|
|
1362
|
+
const initialHistoryChoiceTransitions: Array<ResolvedChoiceTransition> = []
|
|
1363
|
+
const resolvedInitialTargets: Array<unknown> = []
|
|
1364
|
+
for (
|
|
1365
|
+
const target of choiceResolution === undefined
|
|
1366
|
+
? []
|
|
1367
|
+
: [choiceResolution.target, ...choiceResolution.additionalTargets]
|
|
1368
|
+
) {
|
|
1369
|
+
if (!isHistoryTarget(target)) {
|
|
1370
|
+
resolvedInitialTargets.push(target)
|
|
1371
|
+
continue
|
|
1372
|
+
}
|
|
1373
|
+
const history = resolveHistoryTarget(machine, emptyConfiguration, target, InitialEvent)
|
|
1374
|
+
resolvedInitialTargets.push(history.target)
|
|
1375
|
+
initialHistoryActions.push(...history.commands)
|
|
1376
|
+
initialHistoryRaisedEvents.push(...history.raisedEvents)
|
|
1377
|
+
initialHistoryEmittedEvents.push(...history.emittedEvents)
|
|
1378
|
+
initialHistoryChoiceTransitions.push(...history.transitions)
|
|
1379
|
+
}
|
|
1380
|
+
let resolvedConfiguration = choiceResolution === undefined
|
|
1381
|
+
? normalizeConfigurationSync<States>(machine, state as Machine.Snapshot<States>)
|
|
1382
|
+
: normalizeTargetConfigurationSync<States>(
|
|
1383
|
+
machine,
|
|
1384
|
+
emptyConfiguration,
|
|
1385
|
+
resolvedInitialTargets[0] as
|
|
1386
|
+
| Machine.Snapshot<States>
|
|
1387
|
+
| Machine.Target<States, Machine.StateIdentifier<States>>
|
|
1388
|
+
)
|
|
1389
|
+
for (const additionalTarget of resolvedInitialTargets.slice(1)) {
|
|
1390
|
+
resolvedConfiguration = normalizeTargetConfigurationSync<States>(
|
|
1391
|
+
machine,
|
|
1392
|
+
resolvedConfiguration,
|
|
1393
|
+
additionalTarget as Machine.Snapshot<States> | Machine.Target<States, Machine.StateIdentifier<States>>
|
|
1394
|
+
)
|
|
1395
|
+
}
|
|
1396
|
+
const configuration: ActiveConfiguration = resolvedConfiguration
|
|
1397
|
+
validateInitialConfiguration(machine, configuration)
|
|
1398
|
+
const startingState = snapshotFromConfiguration<States>(machine, configuration)
|
|
1399
|
+
const initialEntryPaths = getInitialEntryPaths(machine, configuration)
|
|
1400
|
+
const commands = [
|
|
1401
|
+
...(choiceResolution?.commands ?? []),
|
|
1402
|
+
...initialHistoryActions
|
|
1403
|
+
]
|
|
1404
|
+
const raisedEvents = [
|
|
1405
|
+
...(choiceResolution?.raisedEvents ?? []),
|
|
1406
|
+
...initialHistoryRaisedEvents
|
|
1407
|
+
]
|
|
1408
|
+
const emittedEvents = [
|
|
1409
|
+
...(choiceResolution?.emittedEvents ?? []),
|
|
1410
|
+
...initialHistoryEmittedEvents
|
|
1411
|
+
]
|
|
1412
|
+
const entry = collectStateActions<States, Events, Emits, E, R>(
|
|
1413
|
+
machine,
|
|
1414
|
+
configuration,
|
|
1415
|
+
initialEntryPaths,
|
|
1416
|
+
InitialEvent,
|
|
1417
|
+
"entry"
|
|
1418
|
+
)
|
|
1419
|
+
const settled = settle(
|
|
1420
|
+
machine,
|
|
1421
|
+
configuration,
|
|
1422
|
+
InitialEvent,
|
|
1423
|
+
[...entry.commands],
|
|
1424
|
+
[...raisedEvents, ...entry.raisedEvents] as Array<Machine.EventOf<Events>>,
|
|
1425
|
+
[...emittedEvents, ...entry.emittedEvents],
|
|
1426
|
+
choiceResolution === undefined ? [] : [{
|
|
1427
|
+
next: configuration,
|
|
1428
|
+
event: InitialEvent,
|
|
1429
|
+
transitions: [...choiceResolution.transitions, ...initialHistoryChoiceTransitions],
|
|
1430
|
+
commands: [...choiceResolution.commands, ...initialHistoryActions],
|
|
1431
|
+
raisedEvents: [
|
|
1432
|
+
...choiceResolution.raisedEvents,
|
|
1433
|
+
...initialHistoryRaisedEvents
|
|
1434
|
+
] as ReadonlyArray<Machine.EventOf<Events>>,
|
|
1435
|
+
emittedEvents: [...choiceResolution.emittedEvents, ...initialHistoryEmittedEvents],
|
|
1436
|
+
exitPaths: [],
|
|
1437
|
+
entryPaths: [],
|
|
1438
|
+
changed: false
|
|
1439
|
+
}]
|
|
1440
|
+
)
|
|
1441
|
+
|
|
1442
|
+
const planned = {
|
|
1443
|
+
startingState,
|
|
1444
|
+
initialEntryPaths,
|
|
1445
|
+
state: snapshotFromConfiguration<States>(machine, settled.next),
|
|
1446
|
+
commands: [
|
|
1447
|
+
...commands,
|
|
1448
|
+
...settled.commands
|
|
1449
|
+
],
|
|
1450
|
+
emittedEvents: settled.emittedEvents as ReadonlyArray<Machine.EmitOf<Emits>>,
|
|
1451
|
+
microsteps: settled.microsteps.map((step) => ({
|
|
1452
|
+
next: snapshotFromConfiguration<States>(machine, step.next),
|
|
1453
|
+
event: step.event,
|
|
1454
|
+
transitions: step.transitions,
|
|
1455
|
+
commands: step.commands,
|
|
1456
|
+
raisedEvents: step.raisedEvents,
|
|
1457
|
+
emittedEvents: step.emittedEvents,
|
|
1458
|
+
exitPaths: step.exitPaths,
|
|
1459
|
+
entryPaths: step.entryPaths,
|
|
1460
|
+
changed: step.changed
|
|
1461
|
+
}))
|
|
1462
|
+
}
|
|
1463
|
+
return settled.done
|
|
1464
|
+
? { ...planned, done: true as const, output: settled.output }
|
|
1465
|
+
: { ...planned, done: false as const, output: undefined }
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
export const enabled = <
|
|
1469
|
+
const States extends Machine.StateSchemas,
|
|
1470
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1471
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1472
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
1473
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
1474
|
+
E = never,
|
|
1475
|
+
R = never,
|
|
1476
|
+
InitialE = never,
|
|
1477
|
+
InitialR = never,
|
|
1478
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
1479
|
+
Output = never,
|
|
1480
|
+
OutputStates extends Machine.StateIdentifier<States> = never
|
|
1481
|
+
>(
|
|
1482
|
+
machine: Machine<
|
|
1483
|
+
States,
|
|
1484
|
+
Events,
|
|
1485
|
+
Input,
|
|
1486
|
+
UnhandledStates,
|
|
1487
|
+
E,
|
|
1488
|
+
R,
|
|
1489
|
+
InitialE,
|
|
1490
|
+
InitialR,
|
|
1491
|
+
FinalStates,
|
|
1492
|
+
Output,
|
|
1493
|
+
Emits,
|
|
1494
|
+
OutputStates
|
|
1495
|
+
>,
|
|
1496
|
+
state: Machine.Snapshot<States>
|
|
1497
|
+
): ReadonlyArray<Machine.TagOf<Events[number]>> => {
|
|
1498
|
+
if (isFinalState(machine, state)) {
|
|
1499
|
+
return []
|
|
1500
|
+
}
|
|
1501
|
+
const configuration = normalizeConfiguration(machine, state)
|
|
1502
|
+
const tags: Array<Machine.TagOf<Events[number]>> = []
|
|
1503
|
+
const seen = new Set<PropertyKey>()
|
|
1504
|
+
for (const path of getCandidatePaths(machine, configuration)) {
|
|
1505
|
+
for (const tag of Reflect.ownKeys(machine.handlers[path]?.on ?? {})) {
|
|
1506
|
+
if (!seen.has(tag)) {
|
|
1507
|
+
seen.add(tag)
|
|
1508
|
+
tags.push(tag as Machine.TagOf<Events[number]>)
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return tags
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
const microstep = <
|
|
1516
|
+
const States extends Machine.StateSchemas,
|
|
1517
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1518
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
|
|
1519
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
1520
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
1521
|
+
E = never,
|
|
1522
|
+
R = never,
|
|
1523
|
+
InitialE = never,
|
|
1524
|
+
InitialR = never,
|
|
1525
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
1526
|
+
Output = never,
|
|
1527
|
+
Context = never
|
|
1528
|
+
>(
|
|
1529
|
+
machine: Machine<States, Events, Input, UnhandledStates, E, R, InitialE, InitialR, FinalStates, Output, Emits>,
|
|
1530
|
+
state: ActiveConfiguration,
|
|
1531
|
+
event: Machine.LifecycleEvent<Events>,
|
|
1532
|
+
selections: ReadonlyArray<SelectedTransition<States, E, R, Context>>
|
|
1533
|
+
) => {
|
|
1534
|
+
if (selections.length === 0) {
|
|
1535
|
+
return {
|
|
1536
|
+
next: state,
|
|
1537
|
+
event,
|
|
1538
|
+
transitions: [],
|
|
1539
|
+
commands: [],
|
|
1540
|
+
raisedEvents: [],
|
|
1541
|
+
emittedEvents: [],
|
|
1542
|
+
exitPaths: [],
|
|
1543
|
+
entryPaths: [],
|
|
1544
|
+
changed: false
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
const activeSelections = removePreemptedAncestorSelections(selections)
|
|
1549
|
+
const evaluatedTransitions: Array<EvaluatedTransition<States, Machine.EventOf<Events>, E, R, Context>> = []
|
|
1550
|
+
for (const selection of activeSelections) {
|
|
1551
|
+
evaluatedTransitions.push(
|
|
1552
|
+
collectEvaluatedTransition<States, Machine.EventOf<Events>, E, R, Context>(
|
|
1553
|
+
machine,
|
|
1554
|
+
state,
|
|
1555
|
+
selection
|
|
1556
|
+
)
|
|
1557
|
+
)
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
const transitions = removeConflictingTransitions(machine, evaluatedTransitions)
|
|
1561
|
+
const sortedTransitions = sortEvaluatedTransitions(machine, transitions)
|
|
1562
|
+
const retainedTransitions = sortedTransitions.flatMap((transition) => [
|
|
1563
|
+
{
|
|
1564
|
+
source: transition.selection.sourcePath,
|
|
1565
|
+
trigger: transition.selection.trigger,
|
|
1566
|
+
reenter: transition.selection.transition.reenter,
|
|
1567
|
+
target: transition.unresolvedTarget === undefined ? undefined : getTargetNodePath(transition.unresolvedTarget),
|
|
1568
|
+
resolvedTarget: transition.target === undefined ? undefined : getTargetNodePath(transition.target)
|
|
1569
|
+
},
|
|
1570
|
+
...transition.choiceTransitions
|
|
1571
|
+
])
|
|
1572
|
+
let stateAfterTransition = state
|
|
1573
|
+
// Value-only targets are evaluated against the original configuration. If
|
|
1574
|
+
// one is applied after a control-changing transition, it can resurrect a
|
|
1575
|
+
// branch that the changing transition exited. Apply value-only updates
|
|
1576
|
+
// first so later control targets remain authoritative while still
|
|
1577
|
+
// preserving updates made in unaffected parallel regions.
|
|
1578
|
+
const targetApplicationOrder = [
|
|
1579
|
+
...sortedTransitions.filter((transition) => !transition.changed),
|
|
1580
|
+
...sortedTransitions.filter((transition) => transition.changed)
|
|
1581
|
+
]
|
|
1582
|
+
for (const transition of targetApplicationOrder) {
|
|
1583
|
+
if (transition.target !== undefined) {
|
|
1584
|
+
stateAfterTransition = normalizeTargetConfigurationSync<States>(
|
|
1585
|
+
machine,
|
|
1586
|
+
stateAfterTransition,
|
|
1587
|
+
transition.target
|
|
1588
|
+
)
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
const changed = transitions.some((transition) => transition.changed)
|
|
1593
|
+
const transitionActions = sortedTransitions
|
|
1594
|
+
.flatMap((transition) => transition.commands)
|
|
1595
|
+
const transitionRaisedEvents = sortedTransitions
|
|
1596
|
+
.flatMap((transition) => transition.raisedEvents)
|
|
1597
|
+
const transitionEmittedEvents = sortedTransitions
|
|
1598
|
+
.flatMap((transition) => transition.emittedEvents)
|
|
1599
|
+
|
|
1600
|
+
if (!changed) {
|
|
1601
|
+
return {
|
|
1602
|
+
next: stateAfterTransition,
|
|
1603
|
+
event,
|
|
1604
|
+
transitions: retainedTransitions,
|
|
1605
|
+
commands: transitionActions,
|
|
1606
|
+
raisedEvents: transitionRaisedEvents,
|
|
1607
|
+
emittedEvents: transitionEmittedEvents,
|
|
1608
|
+
exitPaths: [],
|
|
1609
|
+
entryPaths: [],
|
|
1610
|
+
changed: false
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
const exitPaths = sortExitPaths(machine, sortedTransitions.flatMap((transition) => transition.exitPaths))
|
|
1615
|
+
const entryPaths = sortEntryPaths(machine, sortedTransitions.flatMap((transition) => transition.entryPaths))
|
|
1616
|
+
stateAfterTransition = captureHistory(machine, state, stateAfterTransition, exitPaths)
|
|
1617
|
+
const exit = collectStateActions<States, Events, Emits, E, R>(
|
|
1618
|
+
machine,
|
|
1619
|
+
state,
|
|
1620
|
+
exitPaths,
|
|
1621
|
+
event,
|
|
1622
|
+
"exit"
|
|
1623
|
+
)
|
|
1624
|
+
const entry = collectStateActions<States, Events, Emits, E, R>(
|
|
1625
|
+
machine,
|
|
1626
|
+
stateAfterTransition,
|
|
1627
|
+
entryPaths,
|
|
1628
|
+
event,
|
|
1629
|
+
"entry"
|
|
1630
|
+
)
|
|
1631
|
+
|
|
1632
|
+
return {
|
|
1633
|
+
next: stateAfterTransition,
|
|
1634
|
+
event,
|
|
1635
|
+
transitions: retainedTransitions,
|
|
1636
|
+
commands: [...exit.commands, ...transitionActions, ...entry.commands],
|
|
1637
|
+
raisedEvents: [...exit.raisedEvents, ...transitionRaisedEvents, ...entry.raisedEvents] as ReadonlyArray<
|
|
1638
|
+
Machine.EventOf<Events>
|
|
1639
|
+
>,
|
|
1640
|
+
emittedEvents: [...exit.emittedEvents, ...transitionEmittedEvents, ...entry.emittedEvents],
|
|
1641
|
+
exitPaths,
|
|
1642
|
+
entryPaths,
|
|
1643
|
+
changed: true
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
const settle = <
|
|
1648
|
+
const States extends Machine.StateSchemas,
|
|
1649
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1650
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
|
|
1651
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
1652
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
1653
|
+
E = never,
|
|
1654
|
+
R = never,
|
|
1655
|
+
InitialE = never,
|
|
1656
|
+
InitialR = never,
|
|
1657
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
1658
|
+
Output = never
|
|
1659
|
+
>(
|
|
1660
|
+
machine: Machine<States, Events, Input, UnhandledStates, E, R, InitialE, InitialR, FinalStates, Output, Emits>,
|
|
1661
|
+
state: ActiveConfiguration,
|
|
1662
|
+
event: Machine.LifecycleEvent<Events>,
|
|
1663
|
+
commands: Array<RuntimeCommand>,
|
|
1664
|
+
raisedEvents: Array<Machine.EventOf<Events>>,
|
|
1665
|
+
emittedEvents: Array<unknown>,
|
|
1666
|
+
microsteps: Array<MicrostepPlan<ActiveConfiguration, Machine.EventOf<Events>, E, R>>
|
|
1667
|
+
) => {
|
|
1668
|
+
let currentState = state
|
|
1669
|
+
let currentEvent = event
|
|
1670
|
+
let shouldRunAlways = true
|
|
1671
|
+
let iterations = 0
|
|
1672
|
+
let raisedEventIndex = 0
|
|
1673
|
+
let completedTerminal = false
|
|
1674
|
+
let finalOutput: Output | undefined = undefined
|
|
1675
|
+
const pendingCompletions: Array<{ readonly path: string; readonly output: unknown }> = []
|
|
1676
|
+
|
|
1677
|
+
while (true) {
|
|
1678
|
+
iterations += 1
|
|
1679
|
+
if (iterations > MaxMacrostepIterations) {
|
|
1680
|
+
throw new InfiniteTransitionError({
|
|
1681
|
+
machineId: machine.id,
|
|
1682
|
+
state: String(getLeafPath(machine, currentState)),
|
|
1683
|
+
maxIterations: MaxMacrostepIterations
|
|
1684
|
+
})
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
const completed = completeConfigurationSync(machine, currentState, currentEvent)
|
|
1688
|
+
currentState = completed.configuration
|
|
1689
|
+
pendingCompletions.push(
|
|
1690
|
+
...completed.completions.filter((completion) => machine.handlers[completion.path]?.onDone !== undefined)
|
|
1691
|
+
)
|
|
1692
|
+
while (pendingCompletions.length > 0 && !currentState.active.has(pendingCompletions[0]!.path)) {
|
|
1693
|
+
pendingCompletions.shift()
|
|
1694
|
+
}
|
|
1695
|
+
const done = selectDoneTransitions<States, Events, Emits, E, R>(
|
|
1696
|
+
machine,
|
|
1697
|
+
currentState,
|
|
1698
|
+
currentEvent,
|
|
1699
|
+
pendingCompletions.length === 0 ? [] : [pendingCompletions.shift()!]
|
|
1700
|
+
)
|
|
1701
|
+
if (done.length > 0) {
|
|
1702
|
+
const doneStep: MicrostepPlan<ActiveConfiguration, Machine.EventOf<Events>, E, R> = microstep(
|
|
1703
|
+
machine,
|
|
1704
|
+
currentState,
|
|
1705
|
+
currentEvent,
|
|
1706
|
+
done
|
|
1707
|
+
)
|
|
1708
|
+
commands.push(...doneStep.commands)
|
|
1709
|
+
raisedEvents.push(...doneStep.raisedEvents)
|
|
1710
|
+
emittedEvents.push(...doneStep.emittedEvents)
|
|
1711
|
+
microsteps.push(doneStep)
|
|
1712
|
+
currentState = doneStep.next
|
|
1713
|
+
shouldRunAlways = doneStep.changed
|
|
1714
|
+
continue
|
|
1715
|
+
}
|
|
1716
|
+
if (isActiveFinalConfiguration(machine, currentState)) {
|
|
1717
|
+
const root = getRootPath(machine, currentState)
|
|
1718
|
+
if (!currentState.outputs.has(root)) {
|
|
1719
|
+
throw new Error("Machine reached a terminal configuration without a completed root output")
|
|
1720
|
+
}
|
|
1721
|
+
completedTerminal = true
|
|
1722
|
+
finalOutput = currentState.outputs.get(root) as Output
|
|
1723
|
+
break
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
const always = shouldRunAlways
|
|
1727
|
+
? selectAlwaysTransitions<States, Events, Emits, E, R>(machine, currentState, currentEvent)
|
|
1728
|
+
: []
|
|
1729
|
+
if (always.length > 0) {
|
|
1730
|
+
const alwaysStep: MicrostepPlan<ActiveConfiguration, Machine.EventOf<Events>, E, R> = microstep(
|
|
1731
|
+
machine,
|
|
1732
|
+
currentState,
|
|
1733
|
+
currentEvent,
|
|
1734
|
+
always
|
|
1735
|
+
)
|
|
1736
|
+
commands.push(...alwaysStep.commands)
|
|
1737
|
+
raisedEvents.push(...alwaysStep.raisedEvents)
|
|
1738
|
+
emittedEvents.push(...alwaysStep.emittedEvents)
|
|
1739
|
+
microsteps.push(alwaysStep)
|
|
1740
|
+
currentState = alwaysStep.next
|
|
1741
|
+
shouldRunAlways = alwaysStep.changed
|
|
1742
|
+
continue
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
const raisedEventValue = raisedEvents[raisedEventIndex]
|
|
1746
|
+
if (raisedEventValue === undefined) {
|
|
1747
|
+
break
|
|
1748
|
+
}
|
|
1749
|
+
raisedEventIndex += 1
|
|
1750
|
+
|
|
1751
|
+
// Planning runtime validates and normalizes every event before it enters this
|
|
1752
|
+
// internal queue, so decoding it again here only repeats schema work.
|
|
1753
|
+
const raisedEvent = raisedEventValue
|
|
1754
|
+
currentEvent = raisedEvent
|
|
1755
|
+
const raisedSelections = selectEventTransitions<States, Events, Emits, E, R>(
|
|
1756
|
+
machine,
|
|
1757
|
+
currentState,
|
|
1758
|
+
raisedEvent as Machine.EventByTag<Events, Machine.TagOf<Events[number]>>
|
|
1759
|
+
)
|
|
1760
|
+
if (raisedSelections.length === 0) {
|
|
1761
|
+
shouldRunAlways = true
|
|
1762
|
+
continue
|
|
1763
|
+
}
|
|
1764
|
+
const raisedStep = microstep(
|
|
1765
|
+
machine,
|
|
1766
|
+
currentState,
|
|
1767
|
+
raisedEvent,
|
|
1768
|
+
raisedSelections
|
|
1769
|
+
)
|
|
1770
|
+
commands.push(...raisedStep.commands)
|
|
1771
|
+
raisedEvents.push(...raisedStep.raisedEvents)
|
|
1772
|
+
emittedEvents.push(...raisedStep.emittedEvents)
|
|
1773
|
+
microsteps.push(raisedStep)
|
|
1774
|
+
currentState = raisedStep.next
|
|
1775
|
+
shouldRunAlways = true
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
const result = {
|
|
1779
|
+
next: currentState,
|
|
1780
|
+
commands,
|
|
1781
|
+
emittedEvents,
|
|
1782
|
+
microsteps
|
|
1783
|
+
}
|
|
1784
|
+
return completedTerminal
|
|
1785
|
+
? { ...result, done: true as const, output: finalOutput as Output }
|
|
1786
|
+
: { ...result, done: false as const, output: undefined }
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
const macrostepConfiguration = <
|
|
1790
|
+
const States extends Machine.StateSchemas,
|
|
1791
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1792
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
|
|
1793
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
1794
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
1795
|
+
E = never,
|
|
1796
|
+
R = never,
|
|
1797
|
+
InitialE = never,
|
|
1798
|
+
InitialR = never,
|
|
1799
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
1800
|
+
Output = never
|
|
1801
|
+
>(
|
|
1802
|
+
machine: Machine<States, Events, Input, UnhandledStates, E, R, InitialE, InitialR, FinalStates, Output, Emits>,
|
|
1803
|
+
configuration: ActiveConfiguration,
|
|
1804
|
+
event: Machine.EventOf<Events>
|
|
1805
|
+
) => {
|
|
1806
|
+
const decodedEvent = decodeEventSync<Events>(machine, event)
|
|
1807
|
+
if (isActiveFinalConfiguration(machine, configuration)) {
|
|
1808
|
+
const completed = completeConfigurationSync(machine, configuration, decodedEvent)
|
|
1809
|
+
const root = getRootPath(machine, completed.configuration)
|
|
1810
|
+
if (!completed.configuration.outputs.has(root)) {
|
|
1811
|
+
throw new Error("Machine reached a terminal configuration without a completed root output")
|
|
1812
|
+
}
|
|
1813
|
+
return {
|
|
1814
|
+
next: completed.configuration,
|
|
1815
|
+
commands: [],
|
|
1816
|
+
emittedEvents: [],
|
|
1817
|
+
microsteps: [],
|
|
1818
|
+
done: true as const,
|
|
1819
|
+
output: completed.configuration.outputs.get(root) as Output
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
const selections = selectEventTransitions<States, Events, Emits, E, R>(
|
|
1824
|
+
machine,
|
|
1825
|
+
configuration,
|
|
1826
|
+
decodedEvent as Machine.EventByTag<Events, Machine.TagOf<Events[number]>>
|
|
1827
|
+
)
|
|
1828
|
+
if (selections.length === 0) {
|
|
1829
|
+
return {
|
|
1830
|
+
next: configuration,
|
|
1831
|
+
commands: [],
|
|
1832
|
+
emittedEvents: [],
|
|
1833
|
+
microsteps: [],
|
|
1834
|
+
done: false as const,
|
|
1835
|
+
output: undefined
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
const step = microstep(
|
|
1839
|
+
machine,
|
|
1840
|
+
configuration,
|
|
1841
|
+
decodedEvent,
|
|
1842
|
+
selections
|
|
1843
|
+
)
|
|
1844
|
+
const commands = [...step.commands]
|
|
1845
|
+
const raisedEvents = [...step.raisedEvents]
|
|
1846
|
+
const emittedEvents = [...step.emittedEvents]
|
|
1847
|
+
const microsteps = [step]
|
|
1848
|
+
return settle(machine, step.next, decodedEvent, commands, raisedEvents, emittedEvents, microsteps)
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
const snapshotMacrostep = <
|
|
1852
|
+
const States extends Machine.StateSchemas,
|
|
1853
|
+
Event,
|
|
1854
|
+
E,
|
|
1855
|
+
R,
|
|
1856
|
+
Output
|
|
1857
|
+
>(
|
|
1858
|
+
machine: Machine.Any,
|
|
1859
|
+
settled: MacrostepPlan<ActiveConfiguration, Event, E, R, Output>
|
|
1860
|
+
): MacrostepPlan<Machine.Snapshot<States>, Event, E, R, Output> => {
|
|
1861
|
+
const planned = {
|
|
1862
|
+
next: snapshotFromConfiguration<States>(machine, settled.next),
|
|
1863
|
+
commands: settled.commands,
|
|
1864
|
+
emittedEvents: settled.emittedEvents,
|
|
1865
|
+
microsteps: settled.microsteps.map((step) => ({
|
|
1866
|
+
next: snapshotFromConfiguration<States>(machine, step.next),
|
|
1867
|
+
event: step.event,
|
|
1868
|
+
transitions: step.transitions,
|
|
1869
|
+
commands: step.commands,
|
|
1870
|
+
raisedEvents: step.raisedEvents,
|
|
1871
|
+
emittedEvents: step.emittedEvents,
|
|
1872
|
+
exitPaths: step.exitPaths,
|
|
1873
|
+
entryPaths: step.entryPaths,
|
|
1874
|
+
changed: step.changed
|
|
1875
|
+
}))
|
|
1876
|
+
}
|
|
1877
|
+
return settled.done
|
|
1878
|
+
? { ...planned, done: true, output: settled.output }
|
|
1879
|
+
: { ...planned, done: false, output: undefined }
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
const macrostep = <
|
|
1883
|
+
const States extends Machine.StateSchemas,
|
|
1884
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>,
|
|
1885
|
+
const Emits extends ReadonlyArray<Machine.TaggedSchema> = readonly [],
|
|
1886
|
+
const Input extends Schema.Top = typeof Schema.Void,
|
|
1887
|
+
UnhandledStates extends Machine.StateIdentifier<States> = Machine.StateIdentifier<States>,
|
|
1888
|
+
E = never,
|
|
1889
|
+
R = never,
|
|
1890
|
+
InitialE = never,
|
|
1891
|
+
InitialR = never,
|
|
1892
|
+
FinalStates extends Machine.StateIdentifier<States> = never,
|
|
1893
|
+
Output = never
|
|
1894
|
+
>(
|
|
1895
|
+
machine: Machine<States, Events, Input, UnhandledStates, E, R, InitialE, InitialR, FinalStates, Output, Emits>,
|
|
1896
|
+
state: Machine.Snapshot<States>,
|
|
1897
|
+
event: Machine.EventOf<Events>
|
|
1898
|
+
) => {
|
|
1899
|
+
const configuration = normalizeConfigurationSync<States>(machine, state)
|
|
1900
|
+
const settled = macrostepConfiguration(machine, configuration, event)
|
|
1901
|
+
return snapshotMacrostep<States, Machine.EventOf<Events>, E, R, Output>(machine, settled)
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
export const planSync = macrostep
|
|
1905
|
+
|
|
1906
|
+
export const planConfiguration = macrostepConfiguration
|
|
1907
|
+
|
|
1908
|
+
const planningEffect = <A>(thunk: () => A): Effect.Effect<A, InfiniteTransitionError | MachineSchemaDecodeError> =>
|
|
1909
|
+
Effect.suspend(() => {
|
|
1910
|
+
try {
|
|
1911
|
+
return Effect.succeed(thunk())
|
|
1912
|
+
} catch (error) {
|
|
1913
|
+
return error instanceof InfiniteTransitionError || error instanceof MachineSchemaDecodeError
|
|
1914
|
+
? Effect.fail(error)
|
|
1915
|
+
: Effect.die(error)
|
|
1916
|
+
}
|
|
1917
|
+
})
|
|
1918
|
+
|
|
1919
|
+
export const plan = (machine: Machine.Any, state: Machine.Snapshot<any>, event: unknown) =>
|
|
1920
|
+
planningEffect(() => planSync(machine as any, state, event as any))
|
|
1921
|
+
|
|
1922
|
+
export const planInitial = (
|
|
1923
|
+
machine: Machine.Any,
|
|
1924
|
+
...args: ReadonlyArray<unknown>
|
|
1925
|
+
): Effect.Effect<any, InfiniteTransitionError | MachineSchemaDecodeError | StartupError> =>
|
|
1926
|
+
Effect.try({
|
|
1927
|
+
try: () => (planInitialSync as any)(machine, ...args),
|
|
1928
|
+
catch: (error) => {
|
|
1929
|
+
return error instanceof InfiniteTransitionError || error instanceof MachineSchemaDecodeError
|
|
1930
|
+
? error
|
|
1931
|
+
: new StartupError({ cause: Cause.die(error) })
|
|
1932
|
+
}
|
|
1933
|
+
})
|