@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,1331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal active-configuration, history, and completion 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 * as Option from "effect/Option"
|
|
10
|
+
import { hasProperty } from "effect/Predicate"
|
|
11
|
+
import type { Machine } from "../../Machine.js"
|
|
12
|
+
import { MachineSchemaDecodeError } from "./errors.js"
|
|
13
|
+
import {
|
|
14
|
+
decodeBoundary,
|
|
15
|
+
decodeOutputValue,
|
|
16
|
+
decodeOutputValueSync,
|
|
17
|
+
decodeStateValue,
|
|
18
|
+
decodeStateValueSync
|
|
19
|
+
} from "./protocol.js"
|
|
20
|
+
import { getNode, getStateNodeSchema, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js"
|
|
21
|
+
|
|
22
|
+
export interface HistoryRecord {
|
|
23
|
+
readonly mode: "shallow" | "deep"
|
|
24
|
+
readonly parent: string
|
|
25
|
+
readonly active: ReadonlySet<string>
|
|
26
|
+
readonly values: ReadonlyMap<string, unknown>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const validateHistoryRecordControl = (machine: Machine.Any, record: HistoryRecord): void => {
|
|
30
|
+
const ancestry = new Set(getPathToRoot(machine, record.parent))
|
|
31
|
+
const visit = (path: string): void => {
|
|
32
|
+
const node = getNode(machine, path)
|
|
33
|
+
if (node.type === "compound") {
|
|
34
|
+
const children = node.children.filter((child) => record.active.has(child))
|
|
35
|
+
if (children.length !== 1) {
|
|
36
|
+
throw new Error(`Machine history expected compound state "${path}" to retain one active child`)
|
|
37
|
+
}
|
|
38
|
+
if (record.mode === "deep") visit(children[0]!)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
if (node.type === "parallel") {
|
|
42
|
+
for (const child of node.children) {
|
|
43
|
+
if (!record.active.has(child)) {
|
|
44
|
+
throw new Error(`Machine history expected parallel state "${path}" to retain region "${child}"`)
|
|
45
|
+
}
|
|
46
|
+
if (record.mode === "deep") visit(child)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
visit(record.parent)
|
|
51
|
+
for (const path of record.active) {
|
|
52
|
+
if (!ancestry.has(path) && !isPathInSubtree(path, record.parent)) {
|
|
53
|
+
throw new Error(`Machine history contains state "${path}" outside parent "${record.parent}"`)
|
|
54
|
+
}
|
|
55
|
+
if (
|
|
56
|
+
record.mode === "shallow" && isDescendantOf(path, record.parent) &&
|
|
57
|
+
getNode(machine, path).parent !== record.parent
|
|
58
|
+
) {
|
|
59
|
+
throw new Error(`Machine shallow history contains deep descendant "${path}"`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type SnapshotWithHistory = Machine.AtomicSnapshot<string, unknown> & {
|
|
65
|
+
readonly history?: Readonly<
|
|
66
|
+
Record<string, {
|
|
67
|
+
readonly mode: "shallow" | "deep"
|
|
68
|
+
readonly active: ReadonlyArray<string>
|
|
69
|
+
readonly values: Readonly<Record<string, unknown>>
|
|
70
|
+
}>
|
|
71
|
+
>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const historyFromSnapshot = (
|
|
75
|
+
machine: Machine.Any,
|
|
76
|
+
snapshot: SnapshotWithHistory
|
|
77
|
+
): ReadonlyMap<string, HistoryRecord> => {
|
|
78
|
+
const history = new Map<string, HistoryRecord>()
|
|
79
|
+
for (const [path, entry] of Object.entries(snapshot.history ?? {})) {
|
|
80
|
+
const historyNode = getNode(machine, path)
|
|
81
|
+
if (historyNode.type !== "history" || historyNode.parent === undefined || historyNode.history !== entry.mode) {
|
|
82
|
+
throw new Error(`Machine snapshot contains invalid history record "${path}"`)
|
|
83
|
+
}
|
|
84
|
+
const active = new Set<string>()
|
|
85
|
+
const values = new Map<string, unknown>()
|
|
86
|
+
for (const activePath of entry.active) {
|
|
87
|
+
if (active.has(activePath) || !Object.prototype.hasOwnProperty.call(entry.values, activePath)) {
|
|
88
|
+
throw new Error(`Machine snapshot contains invalid remembered state "${activePath}"`)
|
|
89
|
+
}
|
|
90
|
+
const node = getNode(machine, activePath)
|
|
91
|
+
if (
|
|
92
|
+
node.type === "history" || node.type === "choice" ||
|
|
93
|
+
!(isPathInSubtree(activePath, historyNode.parent) ||
|
|
94
|
+
getPathToRoot(machine, historyNode.parent).includes(activePath))
|
|
95
|
+
) {
|
|
96
|
+
throw new Error(`Machine snapshot contains invalid remembered value for "${activePath}"`)
|
|
97
|
+
}
|
|
98
|
+
active.add(activePath)
|
|
99
|
+
values.set(activePath, decodeStateValueSync(machine, node, entry.values[activePath]))
|
|
100
|
+
}
|
|
101
|
+
if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== active.size) {
|
|
102
|
+
throw new Error(`Machine snapshot contains incomplete history record "${path}"`)
|
|
103
|
+
}
|
|
104
|
+
history.set(path, {
|
|
105
|
+
mode: entry.mode,
|
|
106
|
+
parent: historyNode.parent,
|
|
107
|
+
active,
|
|
108
|
+
values
|
|
109
|
+
})
|
|
110
|
+
validateHistoryRecordControl(machine, history.get(path)!)
|
|
111
|
+
}
|
|
112
|
+
return history
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const historyFromSnapshotEffect = Effect.fnUntraced(function*(
|
|
116
|
+
machine: Machine.Any,
|
|
117
|
+
snapshot: SnapshotWithHistory
|
|
118
|
+
) {
|
|
119
|
+
const history = new Map<string, HistoryRecord>()
|
|
120
|
+
for (const [path, entry] of Object.entries(snapshot.history ?? {})) {
|
|
121
|
+
const historyNode = machine.stateNodes.byPath.get(path)
|
|
122
|
+
if (
|
|
123
|
+
historyNode === undefined || historyNode.type !== "history" || historyNode.parent === undefined ||
|
|
124
|
+
historyNode.history !== entry.mode
|
|
125
|
+
) {
|
|
126
|
+
return yield* Effect.fail(
|
|
127
|
+
new MachineSchemaDecodeError({
|
|
128
|
+
machineId: machine.id,
|
|
129
|
+
boundary: "history",
|
|
130
|
+
state: path,
|
|
131
|
+
cause: Cause.die(new Error(`Machine snapshot contains invalid history record "${path}"`))
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
const active = new Set<string>()
|
|
136
|
+
const values = new Map<string, unknown>()
|
|
137
|
+
for (const activePath of entry.active) {
|
|
138
|
+
const node = machine.stateNodes.byPath.get(activePath)
|
|
139
|
+
if (
|
|
140
|
+
active.has(activePath) || node === undefined || node.type === "history" || node.type === "choice" ||
|
|
141
|
+
!Object.prototype.hasOwnProperty.call(entry.values, activePath) ||
|
|
142
|
+
!(isPathInSubtree(activePath, historyNode.parent) ||
|
|
143
|
+
getPathToRoot(machine, historyNode.parent).includes(activePath))
|
|
144
|
+
) {
|
|
145
|
+
return yield* Effect.fail(
|
|
146
|
+
new MachineSchemaDecodeError({
|
|
147
|
+
machineId: machine.id,
|
|
148
|
+
boundary: "history",
|
|
149
|
+
state: activePath,
|
|
150
|
+
cause: Cause.die(new Error(`Machine snapshot contains invalid remembered state "${activePath}"`))
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
active.add(activePath)
|
|
155
|
+
values.set(
|
|
156
|
+
activePath,
|
|
157
|
+
yield* decodeBoundary(machine, getStateNodeSchema(node), entry.values[activePath], {
|
|
158
|
+
boundary: "history",
|
|
159
|
+
state: activePath
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== active.size) {
|
|
164
|
+
return yield* Effect.fail(
|
|
165
|
+
new MachineSchemaDecodeError({
|
|
166
|
+
machineId: machine.id,
|
|
167
|
+
boundary: "history",
|
|
168
|
+
state: path,
|
|
169
|
+
cause: Cause.die(new Error(`Machine snapshot contains incomplete history record "${path}"`))
|
|
170
|
+
})
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
history.set(path, {
|
|
174
|
+
mode: entry.mode,
|
|
175
|
+
parent: historyNode.parent,
|
|
176
|
+
active,
|
|
177
|
+
values
|
|
178
|
+
})
|
|
179
|
+
try {
|
|
180
|
+
validateHistoryRecordControl(machine, history.get(path)!)
|
|
181
|
+
} catch (cause) {
|
|
182
|
+
return yield* Effect.fail(
|
|
183
|
+
new MachineSchemaDecodeError({
|
|
184
|
+
machineId: machine.id,
|
|
185
|
+
boundary: "history",
|
|
186
|
+
state: path,
|
|
187
|
+
cause: Cause.die(cause)
|
|
188
|
+
})
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return history
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const historyToSnapshot = (
|
|
196
|
+
machine: Machine.Any,
|
|
197
|
+
history: ReadonlyMap<string, HistoryRecord>
|
|
198
|
+
): Readonly<
|
|
199
|
+
Record<string, {
|
|
200
|
+
readonly mode: "shallow" | "deep"
|
|
201
|
+
readonly active: ReadonlyArray<string>
|
|
202
|
+
readonly values: Readonly<Record<string, unknown>>
|
|
203
|
+
}>
|
|
204
|
+
> => {
|
|
205
|
+
const entries: Record<string, {
|
|
206
|
+
readonly mode: "shallow" | "deep"
|
|
207
|
+
readonly active: ReadonlyArray<string>
|
|
208
|
+
readonly values: Readonly<Record<string, unknown>>
|
|
209
|
+
}> = {}
|
|
210
|
+
for (const [path, record] of history) {
|
|
211
|
+
const active = Array.from(record.active).sort((left, right) => compareDocumentOrder(machine, left, right))
|
|
212
|
+
entries[path] = {
|
|
213
|
+
mode: record.mode,
|
|
214
|
+
active,
|
|
215
|
+
values: Object.fromEntries(active.map((path) => [path, record.values.get(path)]))
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return entries
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export interface ActiveConfiguration {
|
|
222
|
+
readonly active: ReadonlySet<string>
|
|
223
|
+
readonly values: ReadonlyMap<string, unknown>
|
|
224
|
+
readonly outputs: ReadonlyMap<string, unknown>
|
|
225
|
+
readonly history: ReadonlyMap<string, HistoryRecord>
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export interface FinalCompletion {
|
|
229
|
+
readonly path: string
|
|
230
|
+
readonly output: unknown
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface CompletionResult extends FinalCompletion {
|
|
234
|
+
readonly isNew: boolean
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const pathToRootCache = new WeakMap<Machine.StateNodes, Map<string, ReadonlyArray<string>>>()
|
|
238
|
+
|
|
239
|
+
export const hasOwn = (u: object, key: string): boolean => Object.prototype.hasOwnProperty.call(u, key)
|
|
240
|
+
|
|
241
|
+
export const isDescendantOf = (path: string, ancestor: string): boolean => path.startsWith(`${ancestor}.`)
|
|
242
|
+
|
|
243
|
+
export const isPathInSubtree = (path: string, ancestor: string): boolean =>
|
|
244
|
+
path === ancestor || isDescendantOf(path, ancestor)
|
|
245
|
+
|
|
246
|
+
export const getPathToRoot = (machine: Machine.Any, path: string): ReadonlyArray<string> => {
|
|
247
|
+
let pathsByLeaf = pathToRootCache.get(machine.stateNodes)
|
|
248
|
+
if (pathsByLeaf === undefined) {
|
|
249
|
+
pathsByLeaf = new Map()
|
|
250
|
+
pathToRootCache.set(machine.stateNodes, pathsByLeaf)
|
|
251
|
+
}
|
|
252
|
+
const cached = pathsByLeaf.get(path)
|
|
253
|
+
if (cached !== undefined) {
|
|
254
|
+
return cached
|
|
255
|
+
}
|
|
256
|
+
const paths: Array<string> = []
|
|
257
|
+
let current: string | undefined = path
|
|
258
|
+
while (current !== undefined) {
|
|
259
|
+
paths.unshift(current)
|
|
260
|
+
current = getNode(machine, current).parent
|
|
261
|
+
}
|
|
262
|
+
pathsByLeaf.set(path, paths)
|
|
263
|
+
return paths
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export const pathDepth = (machine: Machine.Any, path: string): number => getPathToRoot(machine, path).length
|
|
267
|
+
|
|
268
|
+
export const compareDocumentOrder = (machine: Machine.Any, left: string, right: string): number =>
|
|
269
|
+
getNode(machine, left).order - getNode(machine, right).order
|
|
270
|
+
|
|
271
|
+
export const hasActiveChild = (machine: Machine.Any, configuration: ActiveConfiguration, path: string): boolean =>
|
|
272
|
+
getNode(machine, path).children.some((child) => configuration.active.has(child))
|
|
273
|
+
|
|
274
|
+
export const getActiveLeafPaths = (machine: Machine.Any, configuration: ActiveConfiguration): ReadonlyArray<string> => {
|
|
275
|
+
const leaves = Array.from(configuration.active)
|
|
276
|
+
.filter((path) => !hasActiveChild(machine, configuration, path))
|
|
277
|
+
.sort((left, right) => compareDocumentOrder(machine, left, right))
|
|
278
|
+
if (leaves.length === 0) {
|
|
279
|
+
throw new Error("Machine expected an active leaf state")
|
|
280
|
+
}
|
|
281
|
+
return leaves
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export const getLeafPath = (machine: Machine.Any, configuration: ActiveConfiguration): string =>
|
|
285
|
+
getActiveLeafPaths(
|
|
286
|
+
machine,
|
|
287
|
+
configuration
|
|
288
|
+
)[0]!
|
|
289
|
+
|
|
290
|
+
export const getActiveLeafPathFrom = (
|
|
291
|
+
machine: Machine.Any,
|
|
292
|
+
configuration: ActiveConfiguration,
|
|
293
|
+
path: string
|
|
294
|
+
): string => {
|
|
295
|
+
const leaves = getActiveLeafPaths(machine, configuration)
|
|
296
|
+
.filter((leaf) => isPathInSubtree(leaf, path))
|
|
297
|
+
if (leaves.length === 0) {
|
|
298
|
+
throw new Error(`Machine expected state "${path}" to have an active leaf state`)
|
|
299
|
+
}
|
|
300
|
+
return leaves[0]!
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export const getRootPath = (machine: Machine.Any, configuration: ActiveConfiguration): string => {
|
|
304
|
+
for (const path of configuration.active) {
|
|
305
|
+
if (getNode(machine, path).parent === undefined) {
|
|
306
|
+
return path
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
throw new Error("Machine expected an active root state")
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export const getActiveValue = (configuration: ActiveConfiguration, path: string): unknown => {
|
|
313
|
+
if (!configuration.values.has(path)) {
|
|
314
|
+
throw new Error(
|
|
315
|
+
`Machine expected active state "${path}" to have a value (available: ${
|
|
316
|
+
Array.from(configuration.values.keys()).join(", ")
|
|
317
|
+
})`
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
return configuration.values.get(path)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export const getParentValues = (
|
|
324
|
+
machine: Machine.Any,
|
|
325
|
+
configuration: ActiveConfiguration,
|
|
326
|
+
path: string
|
|
327
|
+
): Readonly<Record<string, unknown>> => {
|
|
328
|
+
const parents: Record<string, unknown> = {}
|
|
329
|
+
const paths = getPathToRoot(machine, path)
|
|
330
|
+
for (let index = 0; index < paths.length - 1; index++) {
|
|
331
|
+
const parent = paths[index]!
|
|
332
|
+
parents[parent] = getActiveValue(configuration, parent)
|
|
333
|
+
}
|
|
334
|
+
return parents
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export const getParentValue = (
|
|
338
|
+
machine: Machine.Any,
|
|
339
|
+
configuration: ActiveConfiguration,
|
|
340
|
+
path: string
|
|
341
|
+
): unknown => {
|
|
342
|
+
const parent = getNode(machine, path).parent
|
|
343
|
+
return parent === undefined ? undefined : getActiveValue(configuration, parent)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export const getInitialEntryPaths = (
|
|
347
|
+
machine: Machine.Any,
|
|
348
|
+
configuration: ActiveConfiguration
|
|
349
|
+
): ReadonlyArray<string> => {
|
|
350
|
+
const visit = (path: string): ReadonlyArray<string> => {
|
|
351
|
+
if (!configuration.active.has(path)) {
|
|
352
|
+
return []
|
|
353
|
+
}
|
|
354
|
+
const node = getNode(machine, path)
|
|
355
|
+
return [
|
|
356
|
+
path,
|
|
357
|
+
...node.children.flatMap(visit)
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
return machine.stateNodes.roots.flatMap(visit)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export const snapshotFromPath = <const States extends Machine.StateSchemas>(
|
|
364
|
+
machine: Machine.Any,
|
|
365
|
+
configuration: ActiveConfiguration,
|
|
366
|
+
path: string
|
|
367
|
+
): Machine.SnapshotByIdentifier<States, Machine.StateIdentifier<States>> => {
|
|
368
|
+
const node = getNode(machine, path)
|
|
369
|
+
const snapshot: Record<string, unknown> = {
|
|
370
|
+
path,
|
|
371
|
+
value: getActiveValue(configuration, path)
|
|
372
|
+
}
|
|
373
|
+
if (node.type === "compound") {
|
|
374
|
+
const child = node.children.find((child) => configuration.active.has(child))
|
|
375
|
+
if (child === undefined) {
|
|
376
|
+
throw new Error(`Machine expected compound state "${path}" to have an active child`)
|
|
377
|
+
}
|
|
378
|
+
snapshot.state = snapshotFromPath(machine, configuration, child)
|
|
379
|
+
}
|
|
380
|
+
if (node.type === "parallel") {
|
|
381
|
+
const states: Record<string, unknown> = {}
|
|
382
|
+
for (const child of node.children) {
|
|
383
|
+
if (!configuration.active.has(child)) {
|
|
384
|
+
throw new Error(`Machine expected parallel state "${path}" to have active child region "${child}"`)
|
|
385
|
+
}
|
|
386
|
+
const childNode = getNode(machine, child)
|
|
387
|
+
states[childNode.key] = snapshotFromPath(machine, configuration, child)
|
|
388
|
+
}
|
|
389
|
+
snapshot.states = states
|
|
390
|
+
}
|
|
391
|
+
return snapshot as unknown as Machine.SnapshotByIdentifier<States, Machine.StateIdentifier<States>>
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export const snapshotFromConfiguration = <const States extends Machine.StateSchemas>(
|
|
395
|
+
machine: Machine.Any,
|
|
396
|
+
configuration: ActiveConfiguration
|
|
397
|
+
): Machine.Snapshot<States> => {
|
|
398
|
+
const snapshot = snapshotFromPath<States>(
|
|
399
|
+
machine,
|
|
400
|
+
configuration,
|
|
401
|
+
getRootPath(machine, configuration)
|
|
402
|
+
) as Machine.Snapshot<States>
|
|
403
|
+
const completed = Array.from(configuration.outputs)
|
|
404
|
+
.map(([path, output]) => ({ path, output }))
|
|
405
|
+
if (completed.length > 0) {
|
|
406
|
+
;(snapshot as Machine.AtomicSnapshot<string, unknown> & {
|
|
407
|
+
completed: ReadonlyArray<Machine.SnapshotCompletion>
|
|
408
|
+
}).completed = completed
|
|
409
|
+
}
|
|
410
|
+
if (configuration.history.size > 0) {
|
|
411
|
+
Object.assign(snapshot, { history: historyToSnapshot(machine, configuration.history) })
|
|
412
|
+
}
|
|
413
|
+
return snapshot
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/** Creates a targetable subtree snapshot while carrying machine-level history
|
|
417
|
+
* metadata on that subtree root. This is used when a nested history target
|
|
418
|
+
* must preserve active ancestors and unaffected parallel regions. */
|
|
419
|
+
export const snapshotFromConfigurationAtPath = <const States extends Machine.StateSchemas>(
|
|
420
|
+
machine: Machine.Any,
|
|
421
|
+
configuration: ActiveConfiguration,
|
|
422
|
+
path: string
|
|
423
|
+
): Machine.SnapshotByIdentifier<States, Machine.StateIdentifier<States>> => {
|
|
424
|
+
const snapshot = snapshotFromPath<States>(machine, configuration, path)
|
|
425
|
+
if (configuration.history.size > 0) {
|
|
426
|
+
Object.assign(snapshot, { history: historyToSnapshot(machine, configuration.history) })
|
|
427
|
+
}
|
|
428
|
+
return snapshot
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export const configurationFromSnapshot = (
|
|
432
|
+
machine: Machine.Any,
|
|
433
|
+
snapshot: Machine.AtomicSnapshot<string, unknown>
|
|
434
|
+
): ActiveConfiguration => {
|
|
435
|
+
const active = new Set<string>()
|
|
436
|
+
const values = new Map<string, unknown>()
|
|
437
|
+
const snapshotOutputs = snapshot.completed
|
|
438
|
+
|
|
439
|
+
const visit = (current: Machine.AtomicSnapshot<string, unknown>): void => {
|
|
440
|
+
const node = getNode(machine, String(current.path))
|
|
441
|
+
const value = decodeStateValueSync(machine, node, current.value)
|
|
442
|
+
active.add(node.path)
|
|
443
|
+
values.set(node.path, value)
|
|
444
|
+
if (node.type === "compound") {
|
|
445
|
+
if (!hasProperty(current, "state") || !isSnapshot(current.state)) {
|
|
446
|
+
throw new Error(`Machine expected compound snapshot "${node.path}" to include an active child state`)
|
|
447
|
+
}
|
|
448
|
+
const child = getNode(machine, String(current.state.path))
|
|
449
|
+
if (child.parent !== node.path) {
|
|
450
|
+
throw new Error(`Machine expected snapshot "${child.path}" to be a child of "${node.path}"`)
|
|
451
|
+
}
|
|
452
|
+
visit(current.state)
|
|
453
|
+
}
|
|
454
|
+
if (node.type === "parallel") {
|
|
455
|
+
if (!hasProperty(current, "states") || typeof current.states !== "object" || current.states === null) {
|
|
456
|
+
throw new Error(`Machine expected parallel snapshot "${node.path}" to include active child regions`)
|
|
457
|
+
}
|
|
458
|
+
const states = current.states as Readonly<Record<string, unknown>>
|
|
459
|
+
for (const childPath of node.children) {
|
|
460
|
+
const child = getNode(machine, childPath)
|
|
461
|
+
const childSnapshot = states[child.key]
|
|
462
|
+
if (!hasOwn(states, child.key) || !isSnapshot(childSnapshot)) {
|
|
463
|
+
throw new Error(`Machine expected parallel snapshot "${node.path}" to include region "${child.key}"`)
|
|
464
|
+
}
|
|
465
|
+
const snapshotChild = getNode(machine, String(childSnapshot.path))
|
|
466
|
+
if (snapshotChild.path !== child.path) {
|
|
467
|
+
throw new Error(`Machine expected snapshot "${snapshotChild.path}" to be region "${child.path}"`)
|
|
468
|
+
}
|
|
469
|
+
visit(childSnapshot)
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
visit(snapshot)
|
|
475
|
+
const outputs = new Map<string, unknown>()
|
|
476
|
+
if (snapshotOutputs !== undefined) {
|
|
477
|
+
for (const { output, path } of snapshotOutputs) {
|
|
478
|
+
if (active.has(path)) {
|
|
479
|
+
outputs.set(path, output)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return { active, values, outputs, history: historyFromSnapshot(machine, snapshot) }
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export const normalizeConfiguration = <const States extends Machine.StateSchemas>(
|
|
487
|
+
machine: Machine.Any,
|
|
488
|
+
state: Machine.Snapshot<States>
|
|
489
|
+
): ActiveConfiguration => configurationFromSnapshot(machine, state)
|
|
490
|
+
|
|
491
|
+
export const normalizeConfigurationSync = <const States extends Machine.StateSchemas>(
|
|
492
|
+
machine: Machine.Any,
|
|
493
|
+
state: Machine.Snapshot<States>
|
|
494
|
+
): ActiveConfiguration => {
|
|
495
|
+
try {
|
|
496
|
+
return configurationFromSnapshot(machine, state)
|
|
497
|
+
} catch (cause) {
|
|
498
|
+
if (cause instanceof MachineSchemaDecodeError) {
|
|
499
|
+
throw cause
|
|
500
|
+
}
|
|
501
|
+
throw new MachineSchemaDecodeError({
|
|
502
|
+
machineId: machine.id,
|
|
503
|
+
boundary: "configuration",
|
|
504
|
+
cause: Cause.die(cause)
|
|
505
|
+
})
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export const configurationFromSnapshotEffect = Effect.fnUntraced(function*(
|
|
510
|
+
machine: Machine.Any,
|
|
511
|
+
snapshot: Machine.AtomicSnapshot<string, unknown>
|
|
512
|
+
) {
|
|
513
|
+
const active = new Set<string>()
|
|
514
|
+
const values = new Map<string, unknown>()
|
|
515
|
+
const snapshotOutputs = snapshot.completed
|
|
516
|
+
|
|
517
|
+
const visit: (
|
|
518
|
+
current: Machine.AtomicSnapshot<string, unknown>
|
|
519
|
+
) => Effect.Effect<void, MachineSchemaDecodeError> = Effect.fnUntraced(function*(
|
|
520
|
+
current: Machine.AtomicSnapshot<string, unknown>
|
|
521
|
+
) {
|
|
522
|
+
const node = getNode(machine, String(current.path))
|
|
523
|
+
const value = yield* decodeStateValue(machine, node, current.value)
|
|
524
|
+
active.add(node.path)
|
|
525
|
+
values.set(node.path, value)
|
|
526
|
+
if (node.type === "compound") {
|
|
527
|
+
if (!hasProperty(current, "state") || !isSnapshot(current.state)) {
|
|
528
|
+
throw new Error(`Machine expected compound snapshot "${node.path}" to include an active child state`)
|
|
529
|
+
}
|
|
530
|
+
const child = getNode(machine, String(current.state.path))
|
|
531
|
+
if (child.parent !== node.path) {
|
|
532
|
+
throw new Error(`Machine expected snapshot "${child.path}" to be a child of "${node.path}"`)
|
|
533
|
+
}
|
|
534
|
+
yield* visit(current.state)
|
|
535
|
+
}
|
|
536
|
+
if (node.type === "parallel") {
|
|
537
|
+
if (!hasProperty(current, "states") || typeof current.states !== "object" || current.states === null) {
|
|
538
|
+
throw new Error(`Machine expected parallel snapshot "${node.path}" to include active child regions`)
|
|
539
|
+
}
|
|
540
|
+
const states = current.states as Readonly<Record<string, unknown>>
|
|
541
|
+
for (const childPath of node.children) {
|
|
542
|
+
const child = getNode(machine, childPath)
|
|
543
|
+
const childSnapshot = states[child.key]
|
|
544
|
+
if (!hasOwn(states, child.key) || !isSnapshot(childSnapshot)) {
|
|
545
|
+
throw new Error(`Machine expected parallel snapshot "${node.path}" to include region "${child.key}"`)
|
|
546
|
+
}
|
|
547
|
+
const snapshotChild = getNode(machine, String(childSnapshot.path))
|
|
548
|
+
if (snapshotChild.path !== child.path) {
|
|
549
|
+
throw new Error(`Machine expected snapshot "${snapshotChild.path}" to be region "${child.path}"`)
|
|
550
|
+
}
|
|
551
|
+
yield* visit(childSnapshot)
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
yield* visit(snapshot)
|
|
557
|
+
const outputs = new Map<string, unknown>()
|
|
558
|
+
if (snapshotOutputs !== undefined) {
|
|
559
|
+
for (const { output, path } of snapshotOutputs) {
|
|
560
|
+
if (active.has(path)) {
|
|
561
|
+
outputs.set(path, output)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
active,
|
|
567
|
+
values,
|
|
568
|
+
outputs,
|
|
569
|
+
history: yield* historyFromSnapshotEffect(machine, snapshot)
|
|
570
|
+
} as ActiveConfiguration
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
export const normalizeConfigurationEffect = <const States extends Machine.StateSchemas>(
|
|
574
|
+
machine: Machine.Any,
|
|
575
|
+
state: Machine.Snapshot<States>
|
|
576
|
+
): Effect.Effect<ActiveConfiguration, MachineSchemaDecodeError> =>
|
|
577
|
+
configurationFromSnapshotEffect(machine, state).pipe(
|
|
578
|
+
Effect.catchCause((cause) => {
|
|
579
|
+
const error = Cause.findErrorOption(cause)
|
|
580
|
+
return Option.isSome(error) && error.value instanceof MachineSchemaDecodeError
|
|
581
|
+
? Effect.fail(error.value)
|
|
582
|
+
: Effect.fail(
|
|
583
|
+
new MachineSchemaDecodeError({
|
|
584
|
+
machineId: machine.id,
|
|
585
|
+
boundary: "configuration",
|
|
586
|
+
cause
|
|
587
|
+
})
|
|
588
|
+
)
|
|
589
|
+
})
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
export const validateInitialConfiguration = (machine: Machine.Any, configuration: ActiveConfiguration): void => {
|
|
593
|
+
for (const path of configuration.active) {
|
|
594
|
+
const node = getNode(machine, path)
|
|
595
|
+
if (node.type === "compound") {
|
|
596
|
+
const child = node.children.find((child) => configuration.active.has(child))
|
|
597
|
+
const initialNode = node.initial === undefined ? undefined : getNode(machine, node.initial)
|
|
598
|
+
if (initialNode?.type === "choice" ? child === undefined : child !== node.initial) {
|
|
599
|
+
throw new Error(`Machine initial state "${node.path}" must enter initial child "${node.initial}"`)
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (node.type === "parallel") {
|
|
603
|
+
for (const child of node.children) {
|
|
604
|
+
if (!configuration.active.has(child)) {
|
|
605
|
+
throw new Error(`Machine initial state "${node.path}" must enter child region "${child}"`)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Capture every history register whose owning parent exits in this microstep.
|
|
613
|
+
* The control record is deliberately independent from effects/actions: it is
|
|
614
|
+
* part of the logical snapshot and is therefore preserved by pure planning. */
|
|
615
|
+
export const captureHistory = (
|
|
616
|
+
machine: Machine.Any,
|
|
617
|
+
current: ActiveConfiguration,
|
|
618
|
+
next: ActiveConfiguration,
|
|
619
|
+
exitPaths: ReadonlyArray<string>
|
|
620
|
+
): ActiveConfiguration => {
|
|
621
|
+
if (exitPaths.length === 0) {
|
|
622
|
+
return next
|
|
623
|
+
}
|
|
624
|
+
const exited = new Set(exitPaths)
|
|
625
|
+
const history = new Map(next.history)
|
|
626
|
+
for (const node of machine.stateNodes.byPath.values() as Iterable<Machine.StateNode>) {
|
|
627
|
+
if (node.type !== "history" || node.parent === undefined || !exited.has(node.parent)) {
|
|
628
|
+
continue
|
|
629
|
+
}
|
|
630
|
+
const mode = node.history === "deep" ? "deep" : "shallow"
|
|
631
|
+
const active = new Set<string>()
|
|
632
|
+
for (const ancestor of getPathToRoot(machine, node.parent)) {
|
|
633
|
+
if (current.active.has(ancestor)) {
|
|
634
|
+
active.add(ancestor)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
for (const path of current.active) {
|
|
638
|
+
if (
|
|
639
|
+
path === node.parent ||
|
|
640
|
+
(mode === "deep" && isDescendantOf(path, node.parent)) ||
|
|
641
|
+
(mode === "shallow" && getNode(machine, path).parent === node.parent)
|
|
642
|
+
) {
|
|
643
|
+
active.add(path)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const values = new Map<string, unknown>()
|
|
647
|
+
for (const path of active) {
|
|
648
|
+
values.set(path, getActiveValue(current, path))
|
|
649
|
+
}
|
|
650
|
+
history.set(node.path, {
|
|
651
|
+
mode,
|
|
652
|
+
parent: node.parent,
|
|
653
|
+
active,
|
|
654
|
+
values
|
|
655
|
+
})
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
active: next.active,
|
|
659
|
+
values: next.values,
|
|
660
|
+
outputs: next.outputs,
|
|
661
|
+
history
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export const getHistoryRecord = (
|
|
666
|
+
configuration: ActiveConfiguration,
|
|
667
|
+
path: string
|
|
668
|
+
): HistoryRecord | undefined => configuration.history.get(path)
|
|
669
|
+
|
|
670
|
+
/** Builds the remembered portion of a configuration. Shallow records are
|
|
671
|
+
* intentionally incomplete below their direct child; the planner completes
|
|
672
|
+
* them by invoking only the required typed initializers. */
|
|
673
|
+
export const configurationFromHistoryRecord = (
|
|
674
|
+
machine: Machine.Any,
|
|
675
|
+
current: ActiveConfiguration,
|
|
676
|
+
record: HistoryRecord
|
|
677
|
+
): ActiveConfiguration => {
|
|
678
|
+
const active = new Set(record.active)
|
|
679
|
+
const values = new Map(record.values)
|
|
680
|
+
const outputs = new Map<string, unknown>()
|
|
681
|
+
const ancestors = getPathToRoot(machine, record.parent)
|
|
682
|
+
const ancestorSet = new Set(ancestors)
|
|
683
|
+
|
|
684
|
+
// A history transition can occur while an ancestor parallel state remains
|
|
685
|
+
// active. Its unaffected regions retain their current configuration.
|
|
686
|
+
for (const ancestor of ancestors) {
|
|
687
|
+
const node = getNode(machine, ancestor)
|
|
688
|
+
if (node.type !== "parallel") {
|
|
689
|
+
continue
|
|
690
|
+
}
|
|
691
|
+
for (const child of node.children) {
|
|
692
|
+
if (ancestorSet.has(child) || record.active.has(child) || !current.active.has(child)) {
|
|
693
|
+
continue
|
|
694
|
+
}
|
|
695
|
+
for (const path of current.active) {
|
|
696
|
+
if (isPathInSubtree(path, child)) {
|
|
697
|
+
active.add(path)
|
|
698
|
+
if (current.values.has(path)) values.set(path, current.values.get(path))
|
|
699
|
+
if (current.outputs.has(path)) outputs.set(path, current.outputs.get(path))
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return { active, values, outputs, history: current.history }
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export const configurationFromTargetPathEffect = Effect.fnUntraced(function*(
|
|
709
|
+
machine: Machine.Any,
|
|
710
|
+
current: ActiveConfiguration,
|
|
711
|
+
path: string,
|
|
712
|
+
value: unknown,
|
|
713
|
+
providedValues: Readonly<Record<string, unknown>> | undefined
|
|
714
|
+
) {
|
|
715
|
+
const node = getNode(machine, path)
|
|
716
|
+
const active = new Set<string>()
|
|
717
|
+
const values = new Map<string, unknown>()
|
|
718
|
+
const outputs = new Map<string, unknown>()
|
|
719
|
+
const paths = getPathToRoot(machine, node.path)
|
|
720
|
+
const pathSet = new Set(paths)
|
|
721
|
+
|
|
722
|
+
for (const currentPath of paths) {
|
|
723
|
+
const currentNode = getNode(machine, currentPath)
|
|
724
|
+
active.add(currentPath)
|
|
725
|
+
if (currentPath === node.path) {
|
|
726
|
+
values.set(currentPath, yield* decodeStateValue(machine, currentNode, value))
|
|
727
|
+
} else if (providedValues !== undefined && hasOwn(providedValues, currentPath)) {
|
|
728
|
+
values.set(currentPath, yield* decodeStateValue(machine, currentNode, providedValues[currentPath]))
|
|
729
|
+
} else if (current.values.has(currentPath)) {
|
|
730
|
+
values.set(currentPath, current.values.get(currentPath))
|
|
731
|
+
} else {
|
|
732
|
+
throw new Error(`Machine target "${node.path}" requires a value for ancestor state "${currentPath}"`)
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
for (const ancestor of paths) {
|
|
737
|
+
const ancestorNode = getNode(machine, ancestor)
|
|
738
|
+
if (ancestorNode.type === "parallel") {
|
|
739
|
+
for (const child of ancestorNode.children) {
|
|
740
|
+
if (pathSet.has(child) || !current.active.has(child)) {
|
|
741
|
+
continue
|
|
742
|
+
}
|
|
743
|
+
for (const activePath of current.active) {
|
|
744
|
+
if (isPathInSubtree(activePath, child)) {
|
|
745
|
+
active.add(activePath)
|
|
746
|
+
if (current.values.has(activePath)) {
|
|
747
|
+
values.set(activePath, current.values.get(activePath))
|
|
748
|
+
}
|
|
749
|
+
if (current.outputs.has(activePath)) {
|
|
750
|
+
outputs.set(activePath, current.outputs.get(activePath))
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (node.type === "compound" || node.type === "parallel") {
|
|
759
|
+
throw new Error(`Machine target "${node.path}" must include an active child state`)
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return {
|
|
763
|
+
active,
|
|
764
|
+
values,
|
|
765
|
+
outputs,
|
|
766
|
+
history: current.history
|
|
767
|
+
} as ActiveConfiguration
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
export const configurationFromTargetSnapshotEffect = Effect.fnUntraced(function*(
|
|
771
|
+
machine: Machine.Any,
|
|
772
|
+
current: ActiveConfiguration,
|
|
773
|
+
snapshot: Machine.AtomicSnapshot<string, unknown>,
|
|
774
|
+
providedValues: Readonly<Record<string, unknown>> | undefined
|
|
775
|
+
) {
|
|
776
|
+
const subtree = yield* configurationFromSnapshotEffect(machine, snapshot)
|
|
777
|
+
const active = new Set(subtree.active)
|
|
778
|
+
const values = new Map(subtree.values)
|
|
779
|
+
const outputs = new Map(subtree.outputs)
|
|
780
|
+
const paths = getPathToRoot(machine, String(snapshot.path))
|
|
781
|
+
const pathSet = new Set(paths)
|
|
782
|
+
|
|
783
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
784
|
+
const node = getNode(machine, ancestor)
|
|
785
|
+
active.add(ancestor)
|
|
786
|
+
if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
|
|
787
|
+
values.set(ancestor, yield* decodeStateValue(machine, node, providedValues[ancestor]))
|
|
788
|
+
} else if (current.values.has(ancestor)) {
|
|
789
|
+
values.set(ancestor, current.values.get(ancestor))
|
|
790
|
+
} else {
|
|
791
|
+
throw new Error(`Machine target "${snapshot.path}" requires a value for ancestor state "${ancestor}"`)
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
796
|
+
const ancestorNode = getNode(machine, ancestor)
|
|
797
|
+
if (ancestorNode.type === "parallel") {
|
|
798
|
+
for (const child of ancestorNode.children) {
|
|
799
|
+
if (pathSet.has(child)) continue
|
|
800
|
+
if (current.active.has(child)) {
|
|
801
|
+
for (const activePath of current.active) {
|
|
802
|
+
if (isPathInSubtree(activePath, child)) {
|
|
803
|
+
active.add(activePath)
|
|
804
|
+
if (current.values.has(activePath)) {
|
|
805
|
+
values.set(activePath, current.values.get(activePath))
|
|
806
|
+
}
|
|
807
|
+
if (current.outputs.has(activePath)) {
|
|
808
|
+
outputs.set(activePath, current.outputs.get(activePath))
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
continue
|
|
813
|
+
}
|
|
814
|
+
if (providedValues !== undefined && hasOwn(providedValues, child)) {
|
|
815
|
+
for (const [providedPath, providedValue] of Object.entries(providedValues)) {
|
|
816
|
+
if (!isPathInSubtree(providedPath, child)) continue
|
|
817
|
+
const providedNode = getNode(machine, providedPath)
|
|
818
|
+
active.add(providedPath)
|
|
819
|
+
values.set(providedPath, yield* decodeStateValue(machine, providedNode, providedValue))
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
return {
|
|
827
|
+
active,
|
|
828
|
+
values,
|
|
829
|
+
outputs,
|
|
830
|
+
history: new Map([...current.history, ...subtree.history])
|
|
831
|
+
} as ActiveConfiguration
|
|
832
|
+
})
|
|
833
|
+
|
|
834
|
+
export const normalizeTargetConfigurationEffect = <const States extends Machine.StateSchemas>(
|
|
835
|
+
machine: Machine.Any,
|
|
836
|
+
current: ActiveConfiguration,
|
|
837
|
+
target: Machine.Snapshot<States> | Machine.Target<States, Machine.StateIdentifier<States>>
|
|
838
|
+
): Effect.Effect<ActiveConfiguration, MachineSchemaDecodeError> => {
|
|
839
|
+
if (isTarget(target)) {
|
|
840
|
+
const snapshot = target[TargetSnapshotTypeId]
|
|
841
|
+
if (snapshot !== undefined) {
|
|
842
|
+
if (String(snapshot.path) !== String(target.path)) {
|
|
843
|
+
throw new Error(`Machine expected target snapshot path to be "${target.path}"`)
|
|
844
|
+
}
|
|
845
|
+
return configurationFromTargetSnapshotEffect(
|
|
846
|
+
machine,
|
|
847
|
+
current,
|
|
848
|
+
snapshot,
|
|
849
|
+
target.values as Readonly<Record<string, unknown>> | undefined
|
|
850
|
+
)
|
|
851
|
+
}
|
|
852
|
+
return configurationFromTargetPathEffect(
|
|
853
|
+
machine,
|
|
854
|
+
current,
|
|
855
|
+
target.path,
|
|
856
|
+
target.value,
|
|
857
|
+
target.values as Readonly<Record<string, unknown>> | undefined
|
|
858
|
+
)
|
|
859
|
+
}
|
|
860
|
+
if (isSnapshot(target)) {
|
|
861
|
+
return normalizeConfigurationEffect(machine, target).pipe(
|
|
862
|
+
Effect.map((configuration) => ({
|
|
863
|
+
...configuration,
|
|
864
|
+
history: new Map([...current.history, ...configuration.history])
|
|
865
|
+
}))
|
|
866
|
+
)
|
|
867
|
+
}
|
|
868
|
+
throw new Error("Machine expected transition target to be a snapshot or target builder result")
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const configurationFromTargetPathSync = (
|
|
872
|
+
machine: Machine.Any,
|
|
873
|
+
current: ActiveConfiguration,
|
|
874
|
+
path: string,
|
|
875
|
+
value: unknown,
|
|
876
|
+
providedValues: Readonly<Record<string, unknown>> | undefined
|
|
877
|
+
): ActiveConfiguration => {
|
|
878
|
+
const node = getNode(machine, path)
|
|
879
|
+
const active = new Set<string>()
|
|
880
|
+
const values = new Map<string, unknown>()
|
|
881
|
+
const outputs = new Map<string, unknown>()
|
|
882
|
+
const paths = getPathToRoot(machine, node.path)
|
|
883
|
+
const pathSet = new Set(paths)
|
|
884
|
+
|
|
885
|
+
for (const currentPath of paths) {
|
|
886
|
+
const currentNode = getNode(machine, currentPath)
|
|
887
|
+
active.add(currentPath)
|
|
888
|
+
if (currentPath === node.path) {
|
|
889
|
+
values.set(currentPath, decodeStateValueSync(machine, currentNode, value))
|
|
890
|
+
} else if (providedValues !== undefined && hasOwn(providedValues, currentPath)) {
|
|
891
|
+
values.set(currentPath, decodeStateValueSync(machine, currentNode, providedValues[currentPath]))
|
|
892
|
+
} else if (current.values.has(currentPath)) {
|
|
893
|
+
values.set(currentPath, current.values.get(currentPath))
|
|
894
|
+
} else {
|
|
895
|
+
throw new Error(`Machine target "${node.path}" requires a value for ancestor state "${currentPath}"`)
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
for (const ancestor of paths) {
|
|
900
|
+
const ancestorNode = getNode(machine, ancestor)
|
|
901
|
+
if (ancestorNode.type !== "parallel") continue
|
|
902
|
+
for (const child of ancestorNode.children) {
|
|
903
|
+
if (pathSet.has(child) || !current.active.has(child)) continue
|
|
904
|
+
for (const activePath of current.active) {
|
|
905
|
+
if (!isPathInSubtree(activePath, child)) continue
|
|
906
|
+
active.add(activePath)
|
|
907
|
+
if (current.values.has(activePath)) values.set(activePath, current.values.get(activePath))
|
|
908
|
+
if (current.outputs.has(activePath)) outputs.set(activePath, current.outputs.get(activePath))
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
if (node.type === "compound" || node.type === "parallel") {
|
|
914
|
+
throw new Error(`Machine target "${node.path}" must include an active child state`)
|
|
915
|
+
}
|
|
916
|
+
return { active, values, outputs, history: current.history }
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const configurationFromTargetSnapshotSync = (
|
|
920
|
+
machine: Machine.Any,
|
|
921
|
+
current: ActiveConfiguration,
|
|
922
|
+
snapshot: Machine.AtomicSnapshot<string, unknown>,
|
|
923
|
+
providedValues: Readonly<Record<string, unknown>> | undefined
|
|
924
|
+
): ActiveConfiguration => {
|
|
925
|
+
const subtree = configurationFromSnapshot(machine, snapshot)
|
|
926
|
+
const active = new Set(subtree.active)
|
|
927
|
+
const values = new Map(subtree.values)
|
|
928
|
+
const outputs = new Map(subtree.outputs)
|
|
929
|
+
const paths = getPathToRoot(machine, String(snapshot.path))
|
|
930
|
+
const pathSet = new Set(paths)
|
|
931
|
+
|
|
932
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
933
|
+
const node = getNode(machine, ancestor)
|
|
934
|
+
active.add(ancestor)
|
|
935
|
+
if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
|
|
936
|
+
values.set(ancestor, decodeStateValueSync(machine, node, providedValues[ancestor]))
|
|
937
|
+
} else if (current.values.has(ancestor)) {
|
|
938
|
+
values.set(ancestor, current.values.get(ancestor))
|
|
939
|
+
} else {
|
|
940
|
+
throw new Error(`Machine target "${snapshot.path}" requires a value for ancestor state "${ancestor}"`)
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
945
|
+
const ancestorNode = getNode(machine, ancestor)
|
|
946
|
+
if (ancestorNode.type !== "parallel") continue
|
|
947
|
+
for (const child of ancestorNode.children) {
|
|
948
|
+
if (pathSet.has(child)) continue
|
|
949
|
+
if (current.active.has(child)) {
|
|
950
|
+
for (const activePath of current.active) {
|
|
951
|
+
if (!isPathInSubtree(activePath, child)) continue
|
|
952
|
+
active.add(activePath)
|
|
953
|
+
if (current.values.has(activePath)) values.set(activePath, current.values.get(activePath))
|
|
954
|
+
if (current.outputs.has(activePath)) outputs.set(activePath, current.outputs.get(activePath))
|
|
955
|
+
}
|
|
956
|
+
continue
|
|
957
|
+
}
|
|
958
|
+
if (providedValues !== undefined && hasOwn(providedValues, child)) {
|
|
959
|
+
for (const [providedPath, providedValue] of Object.entries(providedValues)) {
|
|
960
|
+
if (!isPathInSubtree(providedPath, child)) continue
|
|
961
|
+
const providedNode = getNode(machine, providedPath)
|
|
962
|
+
active.add(providedPath)
|
|
963
|
+
values.set(providedPath, decodeStateValueSync(machine, providedNode, providedValue))
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
active,
|
|
970
|
+
values,
|
|
971
|
+
outputs,
|
|
972
|
+
history: new Map([...current.history, ...subtree.history])
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
export const normalizeTargetConfigurationSync = <const States extends Machine.StateSchemas>(
|
|
977
|
+
machine: Machine.Any,
|
|
978
|
+
current: ActiveConfiguration,
|
|
979
|
+
target: Machine.Snapshot<States> | Machine.Target<States, Machine.StateIdentifier<States>>
|
|
980
|
+
): ActiveConfiguration => {
|
|
981
|
+
if (isTarget(target)) {
|
|
982
|
+
const snapshot = target[TargetSnapshotTypeId]
|
|
983
|
+
if (snapshot !== undefined) {
|
|
984
|
+
if (String(snapshot.path) !== String(target.path)) {
|
|
985
|
+
throw new Error(`Machine expected target snapshot path to be "${target.path}"`)
|
|
986
|
+
}
|
|
987
|
+
return configurationFromTargetSnapshotSync(
|
|
988
|
+
machine,
|
|
989
|
+
current,
|
|
990
|
+
snapshot,
|
|
991
|
+
target.values as Readonly<Record<string, unknown>> | undefined
|
|
992
|
+
)
|
|
993
|
+
}
|
|
994
|
+
return configurationFromTargetPathSync(
|
|
995
|
+
machine,
|
|
996
|
+
current,
|
|
997
|
+
target.path,
|
|
998
|
+
target.value,
|
|
999
|
+
target.values as Readonly<Record<string, unknown>> | undefined
|
|
1000
|
+
)
|
|
1001
|
+
}
|
|
1002
|
+
if (isSnapshot(target)) {
|
|
1003
|
+
const configuration = normalizeConfigurationSync(machine, target)
|
|
1004
|
+
return { ...configuration, history: new Map([...current.history, ...configuration.history]) }
|
|
1005
|
+
}
|
|
1006
|
+
throw new Error("Machine expected transition target to be a snapshot or target builder result")
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
export const getStateConfigByPath = (
|
|
1010
|
+
machine: Machine.Any,
|
|
1011
|
+
path: string
|
|
1012
|
+
): Machine.AnyStateConfig | undefined => machine.handlers[path]
|
|
1013
|
+
|
|
1014
|
+
export const getActiveChildPath = (
|
|
1015
|
+
machine: Machine.Any,
|
|
1016
|
+
configuration: ActiveConfiguration,
|
|
1017
|
+
path: string
|
|
1018
|
+
): string | undefined => getNode(machine, path).children.find((child) => configuration.active.has(child))
|
|
1019
|
+
|
|
1020
|
+
export const isDirectFinalPath = (
|
|
1021
|
+
machine: Machine.Any,
|
|
1022
|
+
path: string
|
|
1023
|
+
): boolean => getNode(machine, path).type === "final"
|
|
1024
|
+
|
|
1025
|
+
export const hasCompletionHandler = (
|
|
1026
|
+
machine: Machine.Any,
|
|
1027
|
+
path: string
|
|
1028
|
+
): boolean => getStateConfigByPath(machine, path)?.onDone !== undefined
|
|
1029
|
+
|
|
1030
|
+
export const isActiveFinalNode = (
|
|
1031
|
+
machine: Machine.Any,
|
|
1032
|
+
configuration: ActiveConfiguration,
|
|
1033
|
+
path: string
|
|
1034
|
+
): boolean => {
|
|
1035
|
+
if (!configuration.active.has(path)) {
|
|
1036
|
+
return false
|
|
1037
|
+
}
|
|
1038
|
+
const node = getNode(machine, path)
|
|
1039
|
+
if (node.type === "compound") {
|
|
1040
|
+
const child = getActiveChildPath(machine, configuration, path)
|
|
1041
|
+
return child !== undefined && isDirectFinalPath(machine, child)
|
|
1042
|
+
}
|
|
1043
|
+
if (node.type === "parallel") {
|
|
1044
|
+
for (const child of node.children) {
|
|
1045
|
+
if (!isActiveFinalNode(machine, configuration, child)) {
|
|
1046
|
+
return false
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
return true
|
|
1050
|
+
}
|
|
1051
|
+
return isDirectFinalPath(machine, path)
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
export const isActiveFinalConfiguration = (
|
|
1055
|
+
machine: Machine.Any,
|
|
1056
|
+
configuration: ActiveConfiguration
|
|
1057
|
+
): boolean => {
|
|
1058
|
+
const root = getRootPath(machine, configuration)
|
|
1059
|
+
return isActiveFinalNode(machine, configuration, root) && !hasCompletionHandler(machine, root)
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
export const setCompletedOutput = (
|
|
1063
|
+
outputs: Map<string, unknown>,
|
|
1064
|
+
path: string,
|
|
1065
|
+
output: unknown
|
|
1066
|
+
): CompletionResult => {
|
|
1067
|
+
if (outputs.has(path)) {
|
|
1068
|
+
return {
|
|
1069
|
+
path,
|
|
1070
|
+
output: outputs.get(path),
|
|
1071
|
+
isNew: false
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
outputs.set(path, output)
|
|
1075
|
+
return {
|
|
1076
|
+
path,
|
|
1077
|
+
output,
|
|
1078
|
+
isNew: true
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
export const resolveFinalOutputEffect: <
|
|
1083
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>
|
|
1084
|
+
>(
|
|
1085
|
+
machine: Machine.Any,
|
|
1086
|
+
configuration: ActiveConfiguration,
|
|
1087
|
+
path: string,
|
|
1088
|
+
event: Machine.LifecycleEvent<Events>,
|
|
1089
|
+
outputs?: Readonly<Record<string, unknown>>
|
|
1090
|
+
) => Effect.Effect<unknown, MachineSchemaDecodeError> = Effect.fnUntraced(function*<
|
|
1091
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>
|
|
1092
|
+
>(
|
|
1093
|
+
machine: Machine.Any,
|
|
1094
|
+
configuration: ActiveConfiguration,
|
|
1095
|
+
path: string,
|
|
1096
|
+
event: Machine.LifecycleEvent<Events>,
|
|
1097
|
+
outputs?: Readonly<Record<string, unknown>>
|
|
1098
|
+
) {
|
|
1099
|
+
const node = getNode(machine, path)
|
|
1100
|
+
const output = getStateConfigByPath(machine, path)?.output?.({
|
|
1101
|
+
state: getActiveValue(configuration, path),
|
|
1102
|
+
parent: getParentValue(machine, configuration, path),
|
|
1103
|
+
parents: getParentValues(machine, configuration, path),
|
|
1104
|
+
event,
|
|
1105
|
+
outputs
|
|
1106
|
+
} as any)
|
|
1107
|
+
return yield* decodeOutputValue(machine, node, output)
|
|
1108
|
+
})
|
|
1109
|
+
|
|
1110
|
+
export const completeActiveFinalNodeEffect: <
|
|
1111
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>
|
|
1112
|
+
>(
|
|
1113
|
+
machine: Machine.Any,
|
|
1114
|
+
configuration: ActiveConfiguration,
|
|
1115
|
+
path: string,
|
|
1116
|
+
event: Machine.LifecycleEvent<Events>,
|
|
1117
|
+
outputs: Map<string, unknown>,
|
|
1118
|
+
completions: Array<FinalCompletion>
|
|
1119
|
+
) => Effect.Effect<CompletionResult | undefined, MachineSchemaDecodeError> = Effect.fnUntraced(function*<
|
|
1120
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>
|
|
1121
|
+
>(
|
|
1122
|
+
machine: Machine.Any,
|
|
1123
|
+
configuration: ActiveConfiguration,
|
|
1124
|
+
path: string,
|
|
1125
|
+
event: Machine.LifecycleEvent<Events>,
|
|
1126
|
+
outputs: Map<string, unknown>,
|
|
1127
|
+
completions: Array<FinalCompletion>
|
|
1128
|
+
) {
|
|
1129
|
+
if (!configuration.active.has(path)) {
|
|
1130
|
+
return undefined
|
|
1131
|
+
}
|
|
1132
|
+
if (outputs.has(path)) {
|
|
1133
|
+
return {
|
|
1134
|
+
path,
|
|
1135
|
+
output: outputs.get(path),
|
|
1136
|
+
isNew: false
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const node = getNode(machine, path)
|
|
1140
|
+
if (node.type === "compound") {
|
|
1141
|
+
const child = getActiveChildPath(machine, configuration, path)
|
|
1142
|
+
if (child === undefined || !isDirectFinalPath(machine, child)) {
|
|
1143
|
+
return undefined
|
|
1144
|
+
}
|
|
1145
|
+
const childCompletion = yield* completeActiveFinalNodeEffect(
|
|
1146
|
+
machine,
|
|
1147
|
+
configuration,
|
|
1148
|
+
child,
|
|
1149
|
+
event,
|
|
1150
|
+
outputs,
|
|
1151
|
+
completions
|
|
1152
|
+
)
|
|
1153
|
+
if (childCompletion === undefined) {
|
|
1154
|
+
return undefined
|
|
1155
|
+
}
|
|
1156
|
+
const completion = setCompletedOutput(outputs, path, childCompletion.output)
|
|
1157
|
+
if (completion.isNew) {
|
|
1158
|
+
completions.push(completion)
|
|
1159
|
+
}
|
|
1160
|
+
return completion
|
|
1161
|
+
}
|
|
1162
|
+
if (node.type === "parallel") {
|
|
1163
|
+
const regionOutputs: Record<string, unknown> = {}
|
|
1164
|
+
let completed = true
|
|
1165
|
+
for (const child of node.children) {
|
|
1166
|
+
const childCompletion = yield* completeActiveFinalNodeEffect(
|
|
1167
|
+
machine,
|
|
1168
|
+
configuration,
|
|
1169
|
+
child,
|
|
1170
|
+
event,
|
|
1171
|
+
outputs,
|
|
1172
|
+
completions
|
|
1173
|
+
)
|
|
1174
|
+
if (childCompletion === undefined) {
|
|
1175
|
+
completed = false
|
|
1176
|
+
} else {
|
|
1177
|
+
regionOutputs[getNode(machine, child).key] = childCompletion.output
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (!completed) {
|
|
1181
|
+
return undefined
|
|
1182
|
+
}
|
|
1183
|
+
const completion = setCompletedOutput(
|
|
1184
|
+
outputs,
|
|
1185
|
+
path,
|
|
1186
|
+
yield* resolveFinalOutputEffect(machine, configuration, path, event, regionOutputs)
|
|
1187
|
+
)
|
|
1188
|
+
if (completion.isNew) {
|
|
1189
|
+
completions.push(completion)
|
|
1190
|
+
}
|
|
1191
|
+
return completion
|
|
1192
|
+
}
|
|
1193
|
+
if (!isDirectFinalPath(machine, path)) {
|
|
1194
|
+
return undefined
|
|
1195
|
+
}
|
|
1196
|
+
const completion = setCompletedOutput(
|
|
1197
|
+
outputs,
|
|
1198
|
+
path,
|
|
1199
|
+
yield* resolveFinalOutputEffect(machine, configuration, path, event)
|
|
1200
|
+
)
|
|
1201
|
+
if (completion.isNew) {
|
|
1202
|
+
completions.push(completion)
|
|
1203
|
+
}
|
|
1204
|
+
return completion
|
|
1205
|
+
})
|
|
1206
|
+
|
|
1207
|
+
export const completeConfigurationEffect: <
|
|
1208
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>
|
|
1209
|
+
>(
|
|
1210
|
+
machine: Machine.Any,
|
|
1211
|
+
configuration: ActiveConfiguration,
|
|
1212
|
+
event: Machine.LifecycleEvent<Events>
|
|
1213
|
+
) => Effect.Effect<{
|
|
1214
|
+
readonly configuration: ActiveConfiguration
|
|
1215
|
+
readonly completions: ReadonlyArray<FinalCompletion>
|
|
1216
|
+
}, MachineSchemaDecodeError> = Effect.fnUntraced(function*<
|
|
1217
|
+
const Events extends ReadonlyArray<Machine.TaggedSchema>
|
|
1218
|
+
>(
|
|
1219
|
+
machine: Machine.Any,
|
|
1220
|
+
configuration: ActiveConfiguration,
|
|
1221
|
+
event: Machine.LifecycleEvent<Events>
|
|
1222
|
+
) {
|
|
1223
|
+
const outputs = new Map(configuration.outputs)
|
|
1224
|
+
const completions: Array<FinalCompletion> = []
|
|
1225
|
+
const completed = {
|
|
1226
|
+
active: configuration.active,
|
|
1227
|
+
values: configuration.values,
|
|
1228
|
+
outputs,
|
|
1229
|
+
history: configuration.history
|
|
1230
|
+
}
|
|
1231
|
+
for (
|
|
1232
|
+
const path of Array.from(completed.active).sort((left, right) => {
|
|
1233
|
+
const depth = pathDepth(machine, right) - pathDepth(machine, left)
|
|
1234
|
+
return depth === 0 ? compareDocumentOrder(machine, left, right) : depth
|
|
1235
|
+
})
|
|
1236
|
+
) {
|
|
1237
|
+
yield* completeActiveFinalNodeEffect(machine, completed, path, event, outputs, completions)
|
|
1238
|
+
}
|
|
1239
|
+
return { configuration: completed, completions } as {
|
|
1240
|
+
readonly configuration: ActiveConfiguration
|
|
1241
|
+
readonly completions: ReadonlyArray<FinalCompletion>
|
|
1242
|
+
}
|
|
1243
|
+
})
|
|
1244
|
+
|
|
1245
|
+
const resolveFinalOutputSync = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
1246
|
+
machine: Machine.Any,
|
|
1247
|
+
configuration: ActiveConfiguration,
|
|
1248
|
+
path: string,
|
|
1249
|
+
event: Machine.LifecycleEvent<Events>,
|
|
1250
|
+
outputs?: Readonly<Record<string, unknown>>
|
|
1251
|
+
): unknown => {
|
|
1252
|
+
const node = getNode(machine, path)
|
|
1253
|
+
const output = getStateConfigByPath(machine, path)?.output?.({
|
|
1254
|
+
state: getActiveValue(configuration, path),
|
|
1255
|
+
parent: getParentValue(machine, configuration, path),
|
|
1256
|
+
parents: getParentValues(machine, configuration, path),
|
|
1257
|
+
event,
|
|
1258
|
+
outputs
|
|
1259
|
+
} as any)
|
|
1260
|
+
return decodeOutputValueSync(machine, node, output)
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const completeActiveFinalNodeSync = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
1264
|
+
machine: Machine.Any,
|
|
1265
|
+
configuration: ActiveConfiguration,
|
|
1266
|
+
path: string,
|
|
1267
|
+
event: Machine.LifecycleEvent<Events>,
|
|
1268
|
+
outputs: Map<string, unknown>,
|
|
1269
|
+
completions: Array<FinalCompletion>
|
|
1270
|
+
): CompletionResult | undefined => {
|
|
1271
|
+
if (!configuration.active.has(path)) return undefined
|
|
1272
|
+
if (outputs.has(path)) return { path, output: outputs.get(path), isNew: false }
|
|
1273
|
+
const node = getNode(machine, path)
|
|
1274
|
+
if (node.type === "compound") {
|
|
1275
|
+
const child = getActiveChildPath(machine, configuration, path)
|
|
1276
|
+
if (child === undefined || !isDirectFinalPath(machine, child)) return undefined
|
|
1277
|
+
const childCompletion = completeActiveFinalNodeSync(machine, configuration, child, event, outputs, completions)
|
|
1278
|
+
if (childCompletion === undefined) return undefined
|
|
1279
|
+
const completion = setCompletedOutput(outputs, path, childCompletion.output)
|
|
1280
|
+
if (completion.isNew) completions.push(completion)
|
|
1281
|
+
return completion
|
|
1282
|
+
}
|
|
1283
|
+
if (node.type === "parallel") {
|
|
1284
|
+
const regionOutputs: Record<string, unknown> = {}
|
|
1285
|
+
let completed = true
|
|
1286
|
+
for (const child of node.children) {
|
|
1287
|
+
const childCompletion = completeActiveFinalNodeSync(machine, configuration, child, event, outputs, completions)
|
|
1288
|
+
if (childCompletion === undefined) completed = false
|
|
1289
|
+
else regionOutputs[getNode(machine, child).key] = childCompletion.output
|
|
1290
|
+
}
|
|
1291
|
+
if (!completed) return undefined
|
|
1292
|
+
const completion = setCompletedOutput(
|
|
1293
|
+
outputs,
|
|
1294
|
+
path,
|
|
1295
|
+
resolveFinalOutputSync(machine, configuration, path, event, regionOutputs)
|
|
1296
|
+
)
|
|
1297
|
+
if (completion.isNew) completions.push(completion)
|
|
1298
|
+
return completion
|
|
1299
|
+
}
|
|
1300
|
+
if (!isDirectFinalPath(machine, path)) return undefined
|
|
1301
|
+
const completion = setCompletedOutput(outputs, path, resolveFinalOutputSync(machine, configuration, path, event))
|
|
1302
|
+
if (completion.isNew) completions.push(completion)
|
|
1303
|
+
return completion
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
export const completeConfigurationSync = <const Events extends ReadonlyArray<Machine.TaggedSchema>>(
|
|
1307
|
+
machine: Machine.Any,
|
|
1308
|
+
configuration: ActiveConfiguration,
|
|
1309
|
+
event: Machine.LifecycleEvent<Events>
|
|
1310
|
+
): {
|
|
1311
|
+
readonly configuration: ActiveConfiguration
|
|
1312
|
+
readonly completions: ReadonlyArray<FinalCompletion>
|
|
1313
|
+
} => {
|
|
1314
|
+
const outputs = new Map(configuration.outputs)
|
|
1315
|
+
const completions: Array<FinalCompletion> = []
|
|
1316
|
+
const completed: ActiveConfiguration = {
|
|
1317
|
+
active: configuration.active,
|
|
1318
|
+
values: configuration.values,
|
|
1319
|
+
outputs,
|
|
1320
|
+
history: configuration.history
|
|
1321
|
+
}
|
|
1322
|
+
for (
|
|
1323
|
+
const path of Array.from(completed.active).sort((left, right) => {
|
|
1324
|
+
const depth = pathDepth(machine, right) - pathDepth(machine, left)
|
|
1325
|
+
return depth === 0 ? compareDocumentOrder(machine, left, right) : depth
|
|
1326
|
+
})
|
|
1327
|
+
) {
|
|
1328
|
+
completeActiveFinalNodeSync(machine, completed, path, event, outputs, completions)
|
|
1329
|
+
}
|
|
1330
|
+
return { configuration: completed, completions }
|
|
1331
|
+
}
|