@typeonce/effect-machine 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +345 -42
- package/dist/Machine.d.ts +1273 -347
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +161 -426
- package/dist/Machine.js.map +1 -1
- package/dist/internal/machine/activities.d.ts +57 -0
- package/dist/internal/machine/activities.d.ts.map +1 -0
- package/dist/internal/machine/activities.js +49 -0
- package/dist/internal/machine/activities.js.map +1 -0
- package/dist/internal/machine/atom.d.ts +69 -0
- package/dist/internal/machine/atom.d.ts.map +1 -0
- package/dist/{AtomMachine.js → internal/machine/atom.js} +19 -94
- package/dist/internal/machine/atom.js.map +1 -0
- package/dist/internal/machine/cluster.d.ts +63 -0
- package/dist/internal/machine/cluster.d.ts.map +1 -0
- package/dist/{ClusterMachine.js → internal/machine/cluster.js} +15 -119
- package/dist/internal/machine/cluster.js.map +1 -0
- package/dist/internal/machine/command.d.ts +16 -0
- package/dist/internal/machine/command.d.ts.map +1 -0
- package/dist/internal/machine/command.js +45 -0
- package/dist/internal/machine/command.js.map +1 -0
- package/dist/internal/machine/commandRuntime.d.ts +13 -0
- package/dist/internal/machine/commandRuntime.d.ts.map +1 -0
- package/dist/internal/machine/commandRuntime.js +16 -0
- package/dist/internal/machine/commandRuntime.js.map +1 -0
- package/dist/internal/{machineModel.d.ts → machine/configuration.d.ts} +32 -38
- package/dist/internal/machine/configuration.d.ts.map +1 -0
- package/dist/internal/machine/configuration.js +965 -0
- package/dist/internal/machine/configuration.js.map +1 -0
- package/dist/internal/{machineErrors.d.ts → machine/errors.d.ts} +3 -3
- package/dist/internal/machine/errors.d.ts.map +1 -0
- package/dist/internal/{machineErrors.js → machine/errors.js} +1 -1
- package/dist/internal/machine/errors.js.map +1 -0
- package/dist/internal/machine/executionPlan.d.ts +49 -0
- package/dist/internal/machine/executionPlan.d.ts.map +1 -0
- package/dist/internal/machine/executionPlan.js +667 -0
- package/dist/internal/machine/executionPlan.js.map +1 -0
- package/dist/internal/machine/invocation.d.ts +23 -0
- package/dist/internal/machine/invocation.d.ts.map +1 -0
- package/dist/internal/machine/invocation.js +77 -0
- package/dist/internal/machine/invocation.js.map +1 -0
- package/dist/internal/machine/machine.d.ts +183 -0
- package/dist/internal/machine/machine.d.ts.map +1 -0
- package/dist/internal/machine/machine.js +552 -0
- package/dist/internal/machine/machine.js.map +1 -0
- package/dist/internal/machine/planner.d.ts +182 -0
- package/dist/internal/machine/planner.d.ts.map +1 -0
- package/dist/internal/machine/planner.js +1082 -0
- package/dist/internal/machine/planner.js.map +1 -0
- package/dist/internal/{machineProcess.d.ts → machine/process.d.ts} +10 -4
- package/dist/internal/machine/process.d.ts.map +1 -0
- package/dist/internal/machine/process.js +446 -0
- package/dist/internal/machine/process.js.map +1 -0
- package/dist/internal/machine/protocol.d.ts +34 -0
- package/dist/internal/machine/protocol.d.ts.map +1 -0
- package/dist/internal/machine/protocol.js +182 -0
- package/dist/internal/machine/protocol.js.map +1 -0
- package/dist/internal/machine/readiness.d.ts +4 -0
- package/dist/internal/machine/readiness.d.ts.map +1 -0
- package/dist/internal/machine/readiness.js +2 -0
- package/dist/internal/machine/readiness.js.map +1 -0
- package/dist/internal/{machineRuntime.d.ts → machine/runtime.d.ts} +104 -2
- package/dist/internal/machine/runtime.d.ts.map +1 -0
- package/dist/internal/machine/runtime.js +1381 -0
- package/dist/internal/machine/runtime.js.map +1 -0
- package/dist/internal/machine/serialization.d.ts +14 -0
- package/dist/internal/machine/serialization.d.ts.map +1 -0
- package/dist/internal/machine/serialization.js +338 -0
- package/dist/internal/machine/serialization.js.map +1 -0
- package/dist/internal/machine/stateDefinition.d.ts +15 -0
- package/dist/internal/machine/stateDefinition.d.ts.map +1 -0
- package/dist/internal/machine/stateDefinition.js +210 -0
- package/dist/internal/machine/stateDefinition.js.map +1 -0
- package/dist/internal/machine/symbols.d.ts +3 -0
- package/dist/internal/machine/symbols.d.ts.map +1 -0
- package/dist/internal/machine/symbols.js +3 -0
- package/dist/internal/machine/symbols.js.map +1 -0
- package/dist/internal/machine/topology.d.ts +98 -0
- package/dist/internal/machine/topology.d.ts.map +1 -0
- package/dist/internal/machine/topology.js +347 -0
- package/dist/internal/machine/topology.js.map +1 -0
- package/dist/internal/testing/machine/arbitrary.d.ts +36 -0
- package/dist/internal/testing/machine/arbitrary.d.ts.map +1 -0
- package/dist/internal/testing/machine/arbitrary.js +68 -0
- package/dist/internal/testing/machine/arbitrary.js.map +1 -0
- package/dist/internal/testing/machine/finiteModel.d.ts +322 -0
- package/dist/internal/testing/machine/finiteModel.d.ts.map +1 -0
- package/dist/internal/testing/machine/finiteModel.js +976 -0
- package/dist/internal/testing/machine/finiteModel.js.map +1 -0
- package/dist/internal/testing/machine/referenceModel.d.ts +203 -0
- package/dist/internal/testing/machine/referenceModel.d.ts.map +1 -0
- package/dist/internal/testing/machine/referenceModel.js +1012 -0
- package/dist/internal/testing/machine/referenceModel.js.map +1 -0
- package/dist/internal/testing/machine/runtime.d.ts +328 -0
- package/dist/internal/testing/machine/runtime.d.ts.map +1 -0
- package/dist/internal/testing/machine/runtime.js +437 -0
- package/dist/internal/testing/machine/runtime.js.map +1 -0
- package/dist/internal/testing/machine/verification.d.ts +33 -0
- package/dist/internal/testing/machine/verification.d.ts.map +1 -0
- package/dist/internal/testing/machine/verification.js +1446 -0
- package/dist/internal/testing/machine/verification.js.map +1 -0
- package/dist/testing/MachineTest.d.ts +606 -0
- package/dist/testing/MachineTest.d.ts.map +1 -0
- package/dist/testing/MachineTest.js +102 -0
- package/dist/testing/MachineTest.js.map +1 -0
- package/dist/testing/index.d.ts +7 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/{ClusterMachine.d.ts → unstable/cluster/ClusterMachine.d.ts} +22 -30
- package/dist/unstable/cluster/ClusterMachine.d.ts.map +1 -0
- package/dist/unstable/cluster/ClusterMachine.js +126 -0
- package/dist/unstable/cluster/ClusterMachine.js.map +1 -0
- package/dist/{cluster.js → unstable/cluster/index.d.ts} +1 -1
- package/dist/unstable/cluster/index.d.ts.map +1 -0
- package/dist/{cluster.d.ts → unstable/cluster/index.js} +1 -1
- package/dist/unstable/cluster/index.js.map +1 -0
- package/dist/{AtomMachine.d.ts → unstable/reactivity/AtomMachine.d.ts} +40 -23
- package/dist/unstable/reactivity/AtomMachine.d.ts.map +1 -0
- package/dist/unstable/reactivity/AtomMachine.js +113 -0
- package/dist/unstable/reactivity/AtomMachine.js.map +1 -0
- package/dist/{reactivity.js → unstable/reactivity/index.d.ts} +1 -1
- package/dist/unstable/reactivity/index.d.ts.map +1 -0
- package/dist/{reactivity.d.ts → unstable/reactivity/index.js} +1 -1
- package/dist/unstable/reactivity/index.js.map +1 -0
- package/docs/agent-guide.md +271 -38
- package/package.json +23 -13
- package/dist/AtomMachine.d.ts.map +0 -1
- package/dist/AtomMachine.js.map +0 -1
- package/dist/ClusterMachine.d.ts.map +0 -1
- package/dist/ClusterMachine.js.map +0 -1
- package/dist/cluster.d.ts.map +0 -1
- package/dist/cluster.js.map +0 -1
- package/dist/internal/machineErrors.d.ts.map +0 -1
- package/dist/internal/machineErrors.js.map +0 -1
- package/dist/internal/machineModel.d.ts.map +0 -1
- package/dist/internal/machineModel.js +0 -766
- package/dist/internal/machineModel.js.map +0 -1
- package/dist/internal/machinePlanner.d.ts +0 -74
- package/dist/internal/machinePlanner.d.ts.map +0 -1
- package/dist/internal/machinePlanner.js +0 -621
- package/dist/internal/machinePlanner.js.map +0 -1
- package/dist/internal/machineProcess.d.ts.map +0 -1
- package/dist/internal/machineProcess.js +0 -181
- package/dist/internal/machineProcess.js.map +0 -1
- package/dist/internal/machineRuntime.d.ts.map +0 -1
- package/dist/internal/machineRuntime.js +0 -365
- package/dist/internal/machineRuntime.js.map +0 -1
- package/dist/reactivity.d.ts.map +0 -1
- package/dist/reactivity.js.map +0 -1
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal active-configuration, history, and completion helpers.
|
|
3
|
+
*
|
|
4
|
+
* @since 4.0.0
|
|
5
|
+
*/
|
|
6
|
+
import * as Cause from "effect/Cause";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Option from "effect/Option";
|
|
9
|
+
import { hasProperty } from "effect/Predicate";
|
|
10
|
+
import { MachineSchemaDecodeError } from "./errors.js";
|
|
11
|
+
import { decodeBoundary, decodeOutputValue, decodeOutputValueSync, decodeStateValue, decodeStateValueSync } from "./protocol.js";
|
|
12
|
+
import { getNode, getStateNodeSchema, isSnapshot, isTarget, TargetSnapshotTypeId } from "./topology.js";
|
|
13
|
+
export const validateHistoryRecordControl = (machine, record) => {
|
|
14
|
+
const ancestry = new Set(getPathToRoot(machine, record.parent));
|
|
15
|
+
const visit = (path) => {
|
|
16
|
+
const node = getNode(machine, path);
|
|
17
|
+
if (node.type === "compound") {
|
|
18
|
+
const children = node.children.filter((child) => record.active.has(child));
|
|
19
|
+
if (children.length !== 1) {
|
|
20
|
+
throw new Error(`Machine history expected compound state "${path}" to retain one active child`);
|
|
21
|
+
}
|
|
22
|
+
if (record.mode === "deep")
|
|
23
|
+
visit(children[0]);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (node.type === "parallel") {
|
|
27
|
+
for (const child of node.children) {
|
|
28
|
+
if (!record.active.has(child)) {
|
|
29
|
+
throw new Error(`Machine history expected parallel state "${path}" to retain region "${child}"`);
|
|
30
|
+
}
|
|
31
|
+
if (record.mode === "deep")
|
|
32
|
+
visit(child);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
visit(record.parent);
|
|
37
|
+
for (const path of record.active) {
|
|
38
|
+
if (!ancestry.has(path) && !isPathInSubtree(path, record.parent)) {
|
|
39
|
+
throw new Error(`Machine history contains state "${path}" outside parent "${record.parent}"`);
|
|
40
|
+
}
|
|
41
|
+
if (record.mode === "shallow" && isDescendantOf(path, record.parent) &&
|
|
42
|
+
getNode(machine, path).parent !== record.parent) {
|
|
43
|
+
throw new Error(`Machine shallow history contains deep descendant "${path}"`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const historyFromSnapshot = (machine, snapshot) => {
|
|
48
|
+
const history = new Map();
|
|
49
|
+
for (const [path, entry] of Object.entries(snapshot.history ?? {})) {
|
|
50
|
+
const historyNode = getNode(machine, path);
|
|
51
|
+
if (historyNode.type !== "history" || historyNode.parent === undefined || historyNode.history !== entry.mode) {
|
|
52
|
+
throw new Error(`Machine snapshot contains invalid history record "${path}"`);
|
|
53
|
+
}
|
|
54
|
+
const active = new Set();
|
|
55
|
+
const values = new Map();
|
|
56
|
+
for (const activePath of entry.active) {
|
|
57
|
+
if (active.has(activePath) || !Object.prototype.hasOwnProperty.call(entry.values, activePath)) {
|
|
58
|
+
throw new Error(`Machine snapshot contains invalid remembered state "${activePath}"`);
|
|
59
|
+
}
|
|
60
|
+
const node = getNode(machine, activePath);
|
|
61
|
+
if (node.type === "history" || node.type === "choice" ||
|
|
62
|
+
!(isPathInSubtree(activePath, historyNode.parent) ||
|
|
63
|
+
getPathToRoot(machine, historyNode.parent).includes(activePath))) {
|
|
64
|
+
throw new Error(`Machine snapshot contains invalid remembered value for "${activePath}"`);
|
|
65
|
+
}
|
|
66
|
+
active.add(activePath);
|
|
67
|
+
values.set(activePath, decodeStateValueSync(machine, node, entry.values[activePath]));
|
|
68
|
+
}
|
|
69
|
+
if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== active.size) {
|
|
70
|
+
throw new Error(`Machine snapshot contains incomplete history record "${path}"`);
|
|
71
|
+
}
|
|
72
|
+
history.set(path, {
|
|
73
|
+
mode: entry.mode,
|
|
74
|
+
parent: historyNode.parent,
|
|
75
|
+
active,
|
|
76
|
+
values
|
|
77
|
+
});
|
|
78
|
+
validateHistoryRecordControl(machine, history.get(path));
|
|
79
|
+
}
|
|
80
|
+
return history;
|
|
81
|
+
};
|
|
82
|
+
const historyFromSnapshotEffect = Effect.fnUntraced(function* (machine, snapshot) {
|
|
83
|
+
const history = new Map();
|
|
84
|
+
for (const [path, entry] of Object.entries(snapshot.history ?? {})) {
|
|
85
|
+
const historyNode = machine.stateNodes.byPath.get(path);
|
|
86
|
+
if (historyNode === undefined || historyNode.type !== "history" || historyNode.parent === undefined ||
|
|
87
|
+
historyNode.history !== entry.mode) {
|
|
88
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
89
|
+
machineId: machine.id,
|
|
90
|
+
boundary: "history",
|
|
91
|
+
state: path,
|
|
92
|
+
cause: Cause.die(new Error(`Machine snapshot contains invalid history record "${path}"`))
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
const active = new Set();
|
|
96
|
+
const values = new Map();
|
|
97
|
+
for (const activePath of entry.active) {
|
|
98
|
+
const node = machine.stateNodes.byPath.get(activePath);
|
|
99
|
+
if (active.has(activePath) || node === undefined || node.type === "history" || node.type === "choice" ||
|
|
100
|
+
!Object.prototype.hasOwnProperty.call(entry.values, activePath) ||
|
|
101
|
+
!(isPathInSubtree(activePath, historyNode.parent) ||
|
|
102
|
+
getPathToRoot(machine, historyNode.parent).includes(activePath))) {
|
|
103
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
104
|
+
machineId: machine.id,
|
|
105
|
+
boundary: "history",
|
|
106
|
+
state: activePath,
|
|
107
|
+
cause: Cause.die(new Error(`Machine snapshot contains invalid remembered state "${activePath}"`))
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
active.add(activePath);
|
|
111
|
+
values.set(activePath, yield* decodeBoundary(machine, getStateNodeSchema(node), entry.values[activePath], {
|
|
112
|
+
boundary: "history",
|
|
113
|
+
state: activePath
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
if (!active.has(historyNode.parent) || Object.keys(entry.values).length !== active.size) {
|
|
117
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
118
|
+
machineId: machine.id,
|
|
119
|
+
boundary: "history",
|
|
120
|
+
state: path,
|
|
121
|
+
cause: Cause.die(new Error(`Machine snapshot contains incomplete history record "${path}"`))
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
history.set(path, {
|
|
125
|
+
mode: entry.mode,
|
|
126
|
+
parent: historyNode.parent,
|
|
127
|
+
active,
|
|
128
|
+
values
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
validateHistoryRecordControl(machine, history.get(path));
|
|
132
|
+
}
|
|
133
|
+
catch (cause) {
|
|
134
|
+
return yield* Effect.fail(new MachineSchemaDecodeError({
|
|
135
|
+
machineId: machine.id,
|
|
136
|
+
boundary: "history",
|
|
137
|
+
state: path,
|
|
138
|
+
cause: Cause.die(cause)
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return history;
|
|
143
|
+
});
|
|
144
|
+
const historyToSnapshot = (machine, history) => {
|
|
145
|
+
const entries = {};
|
|
146
|
+
for (const [path, record] of history) {
|
|
147
|
+
const active = Array.from(record.active).sort((left, right) => compareDocumentOrder(machine, left, right));
|
|
148
|
+
entries[path] = {
|
|
149
|
+
mode: record.mode,
|
|
150
|
+
active,
|
|
151
|
+
values: Object.fromEntries(active.map((path) => [path, record.values.get(path)]))
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return entries;
|
|
155
|
+
};
|
|
156
|
+
const pathToRootCache = new WeakMap();
|
|
157
|
+
export const hasOwn = (u, key) => Object.prototype.hasOwnProperty.call(u, key);
|
|
158
|
+
export const isDescendantOf = (path, ancestor) => path.startsWith(`${ancestor}.`);
|
|
159
|
+
export const isPathInSubtree = (path, ancestor) => path === ancestor || isDescendantOf(path, ancestor);
|
|
160
|
+
export const getPathToRoot = (machine, path) => {
|
|
161
|
+
let pathsByLeaf = pathToRootCache.get(machine.stateNodes);
|
|
162
|
+
if (pathsByLeaf === undefined) {
|
|
163
|
+
pathsByLeaf = new Map();
|
|
164
|
+
pathToRootCache.set(machine.stateNodes, pathsByLeaf);
|
|
165
|
+
}
|
|
166
|
+
const cached = pathsByLeaf.get(path);
|
|
167
|
+
if (cached !== undefined) {
|
|
168
|
+
return cached;
|
|
169
|
+
}
|
|
170
|
+
const paths = [];
|
|
171
|
+
let current = path;
|
|
172
|
+
while (current !== undefined) {
|
|
173
|
+
paths.unshift(current);
|
|
174
|
+
current = getNode(machine, current).parent;
|
|
175
|
+
}
|
|
176
|
+
pathsByLeaf.set(path, paths);
|
|
177
|
+
return paths;
|
|
178
|
+
};
|
|
179
|
+
export const pathDepth = (machine, path) => getPathToRoot(machine, path).length;
|
|
180
|
+
export const compareDocumentOrder = (machine, left, right) => getNode(machine, left).order - getNode(machine, right).order;
|
|
181
|
+
export const hasActiveChild = (machine, configuration, path) => getNode(machine, path).children.some((child) => configuration.active.has(child));
|
|
182
|
+
export const getActiveLeafPaths = (machine, configuration) => {
|
|
183
|
+
const leaves = Array.from(configuration.active)
|
|
184
|
+
.filter((path) => !hasActiveChild(machine, configuration, path))
|
|
185
|
+
.sort((left, right) => compareDocumentOrder(machine, left, right));
|
|
186
|
+
if (leaves.length === 0) {
|
|
187
|
+
throw new Error("Machine expected an active leaf state");
|
|
188
|
+
}
|
|
189
|
+
return leaves;
|
|
190
|
+
};
|
|
191
|
+
export const getLeafPath = (machine, configuration) => getActiveLeafPaths(machine, configuration)[0];
|
|
192
|
+
export const getActiveLeafPathFrom = (machine, configuration, path) => {
|
|
193
|
+
const leaves = getActiveLeafPaths(machine, configuration)
|
|
194
|
+
.filter((leaf) => isPathInSubtree(leaf, path));
|
|
195
|
+
if (leaves.length === 0) {
|
|
196
|
+
throw new Error(`Machine expected state "${path}" to have an active leaf state`);
|
|
197
|
+
}
|
|
198
|
+
return leaves[0];
|
|
199
|
+
};
|
|
200
|
+
export const getRootPath = (machine, configuration) => {
|
|
201
|
+
for (const path of configuration.active) {
|
|
202
|
+
if (getNode(machine, path).parent === undefined) {
|
|
203
|
+
return path;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
throw new Error("Machine expected an active root state");
|
|
207
|
+
};
|
|
208
|
+
export const getActiveValue = (configuration, path) => {
|
|
209
|
+
if (!configuration.values.has(path)) {
|
|
210
|
+
throw new Error(`Machine expected active state "${path}" to have a value (available: ${Array.from(configuration.values.keys()).join(", ")})`);
|
|
211
|
+
}
|
|
212
|
+
return configuration.values.get(path);
|
|
213
|
+
};
|
|
214
|
+
export const getParentValues = (machine, configuration, path) => {
|
|
215
|
+
const parents = {};
|
|
216
|
+
const paths = getPathToRoot(machine, path);
|
|
217
|
+
for (let index = 0; index < paths.length - 1; index++) {
|
|
218
|
+
const parent = paths[index];
|
|
219
|
+
parents[parent] = getActiveValue(configuration, parent);
|
|
220
|
+
}
|
|
221
|
+
return parents;
|
|
222
|
+
};
|
|
223
|
+
export const getParentValue = (machine, configuration, path) => {
|
|
224
|
+
const parent = getNode(machine, path).parent;
|
|
225
|
+
return parent === undefined ? undefined : getActiveValue(configuration, parent);
|
|
226
|
+
};
|
|
227
|
+
export const getInitialEntryPaths = (machine, configuration) => {
|
|
228
|
+
const visit = (path) => {
|
|
229
|
+
if (!configuration.active.has(path)) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
const node = getNode(machine, path);
|
|
233
|
+
return [
|
|
234
|
+
path,
|
|
235
|
+
...node.children.flatMap(visit)
|
|
236
|
+
];
|
|
237
|
+
};
|
|
238
|
+
return machine.stateNodes.roots.flatMap(visit);
|
|
239
|
+
};
|
|
240
|
+
export const snapshotFromPath = (machine, configuration, path) => {
|
|
241
|
+
const node = getNode(machine, path);
|
|
242
|
+
const snapshot = {
|
|
243
|
+
path,
|
|
244
|
+
value: getActiveValue(configuration, path)
|
|
245
|
+
};
|
|
246
|
+
if (node.type === "compound") {
|
|
247
|
+
const child = node.children.find((child) => configuration.active.has(child));
|
|
248
|
+
if (child === undefined) {
|
|
249
|
+
throw new Error(`Machine expected compound state "${path}" to have an active child`);
|
|
250
|
+
}
|
|
251
|
+
snapshot.state = snapshotFromPath(machine, configuration, child);
|
|
252
|
+
}
|
|
253
|
+
if (node.type === "parallel") {
|
|
254
|
+
const states = {};
|
|
255
|
+
for (const child of node.children) {
|
|
256
|
+
if (!configuration.active.has(child)) {
|
|
257
|
+
throw new Error(`Machine expected parallel state "${path}" to have active child region "${child}"`);
|
|
258
|
+
}
|
|
259
|
+
const childNode = getNode(machine, child);
|
|
260
|
+
states[childNode.key] = snapshotFromPath(machine, configuration, child);
|
|
261
|
+
}
|
|
262
|
+
snapshot.states = states;
|
|
263
|
+
}
|
|
264
|
+
return snapshot;
|
|
265
|
+
};
|
|
266
|
+
export const snapshotFromConfiguration = (machine, configuration) => {
|
|
267
|
+
const snapshot = snapshotFromPath(machine, configuration, getRootPath(machine, configuration));
|
|
268
|
+
const completed = Array.from(configuration.outputs)
|
|
269
|
+
.map(([path, output]) => ({ path, output }));
|
|
270
|
+
if (completed.length > 0) {
|
|
271
|
+
;
|
|
272
|
+
snapshot.completed = completed;
|
|
273
|
+
}
|
|
274
|
+
if (configuration.history.size > 0) {
|
|
275
|
+
Object.assign(snapshot, { history: historyToSnapshot(machine, configuration.history) });
|
|
276
|
+
}
|
|
277
|
+
return snapshot;
|
|
278
|
+
};
|
|
279
|
+
/** Creates a targetable subtree snapshot while carrying machine-level history
|
|
280
|
+
* metadata on that subtree root. This is used when a nested history target
|
|
281
|
+
* must preserve active ancestors and unaffected parallel regions. */
|
|
282
|
+
export const snapshotFromConfigurationAtPath = (machine, configuration, path) => {
|
|
283
|
+
const snapshot = snapshotFromPath(machine, configuration, path);
|
|
284
|
+
if (configuration.history.size > 0) {
|
|
285
|
+
Object.assign(snapshot, { history: historyToSnapshot(machine, configuration.history) });
|
|
286
|
+
}
|
|
287
|
+
return snapshot;
|
|
288
|
+
};
|
|
289
|
+
export const configurationFromSnapshot = (machine, snapshot) => {
|
|
290
|
+
const active = new Set();
|
|
291
|
+
const values = new Map();
|
|
292
|
+
const snapshotOutputs = snapshot.completed;
|
|
293
|
+
const visit = (current) => {
|
|
294
|
+
const node = getNode(machine, String(current.path));
|
|
295
|
+
const value = decodeStateValueSync(machine, node, current.value);
|
|
296
|
+
active.add(node.path);
|
|
297
|
+
values.set(node.path, value);
|
|
298
|
+
if (node.type === "compound") {
|
|
299
|
+
if (!hasProperty(current, "state") || !isSnapshot(current.state)) {
|
|
300
|
+
throw new Error(`Machine expected compound snapshot "${node.path}" to include an active child state`);
|
|
301
|
+
}
|
|
302
|
+
const child = getNode(machine, String(current.state.path));
|
|
303
|
+
if (child.parent !== node.path) {
|
|
304
|
+
throw new Error(`Machine expected snapshot "${child.path}" to be a child of "${node.path}"`);
|
|
305
|
+
}
|
|
306
|
+
visit(current.state);
|
|
307
|
+
}
|
|
308
|
+
if (node.type === "parallel") {
|
|
309
|
+
if (!hasProperty(current, "states") || typeof current.states !== "object" || current.states === null) {
|
|
310
|
+
throw new Error(`Machine expected parallel snapshot "${node.path}" to include active child regions`);
|
|
311
|
+
}
|
|
312
|
+
const states = current.states;
|
|
313
|
+
for (const childPath of node.children) {
|
|
314
|
+
const child = getNode(machine, childPath);
|
|
315
|
+
const childSnapshot = states[child.key];
|
|
316
|
+
if (!hasOwn(states, child.key) || !isSnapshot(childSnapshot)) {
|
|
317
|
+
throw new Error(`Machine expected parallel snapshot "${node.path}" to include region "${child.key}"`);
|
|
318
|
+
}
|
|
319
|
+
const snapshotChild = getNode(machine, String(childSnapshot.path));
|
|
320
|
+
if (snapshotChild.path !== child.path) {
|
|
321
|
+
throw new Error(`Machine expected snapshot "${snapshotChild.path}" to be region "${child.path}"`);
|
|
322
|
+
}
|
|
323
|
+
visit(childSnapshot);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
visit(snapshot);
|
|
328
|
+
const outputs = new Map();
|
|
329
|
+
if (snapshotOutputs !== undefined) {
|
|
330
|
+
for (const { output, path } of snapshotOutputs) {
|
|
331
|
+
if (active.has(path)) {
|
|
332
|
+
outputs.set(path, output);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return { active, values, outputs, history: historyFromSnapshot(machine, snapshot) };
|
|
337
|
+
};
|
|
338
|
+
export const normalizeConfiguration = (machine, state) => configurationFromSnapshot(machine, state);
|
|
339
|
+
export const normalizeConfigurationSync = (machine, state) => {
|
|
340
|
+
try {
|
|
341
|
+
return configurationFromSnapshot(machine, state);
|
|
342
|
+
}
|
|
343
|
+
catch (cause) {
|
|
344
|
+
if (cause instanceof MachineSchemaDecodeError) {
|
|
345
|
+
throw cause;
|
|
346
|
+
}
|
|
347
|
+
throw new MachineSchemaDecodeError({
|
|
348
|
+
machineId: machine.id,
|
|
349
|
+
boundary: "configuration",
|
|
350
|
+
cause: Cause.die(cause)
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
export const configurationFromSnapshotEffect = Effect.fnUntraced(function* (machine, snapshot) {
|
|
355
|
+
const active = new Set();
|
|
356
|
+
const values = new Map();
|
|
357
|
+
const snapshotOutputs = snapshot.completed;
|
|
358
|
+
const visit = Effect.fnUntraced(function* (current) {
|
|
359
|
+
const node = getNode(machine, String(current.path));
|
|
360
|
+
const value = yield* decodeStateValue(machine, node, current.value);
|
|
361
|
+
active.add(node.path);
|
|
362
|
+
values.set(node.path, value);
|
|
363
|
+
if (node.type === "compound") {
|
|
364
|
+
if (!hasProperty(current, "state") || !isSnapshot(current.state)) {
|
|
365
|
+
throw new Error(`Machine expected compound snapshot "${node.path}" to include an active child state`);
|
|
366
|
+
}
|
|
367
|
+
const child = getNode(machine, String(current.state.path));
|
|
368
|
+
if (child.parent !== node.path) {
|
|
369
|
+
throw new Error(`Machine expected snapshot "${child.path}" to be a child of "${node.path}"`);
|
|
370
|
+
}
|
|
371
|
+
yield* visit(current.state);
|
|
372
|
+
}
|
|
373
|
+
if (node.type === "parallel") {
|
|
374
|
+
if (!hasProperty(current, "states") || typeof current.states !== "object" || current.states === null) {
|
|
375
|
+
throw new Error(`Machine expected parallel snapshot "${node.path}" to include active child regions`);
|
|
376
|
+
}
|
|
377
|
+
const states = current.states;
|
|
378
|
+
for (const childPath of node.children) {
|
|
379
|
+
const child = getNode(machine, childPath);
|
|
380
|
+
const childSnapshot = states[child.key];
|
|
381
|
+
if (!hasOwn(states, child.key) || !isSnapshot(childSnapshot)) {
|
|
382
|
+
throw new Error(`Machine expected parallel snapshot "${node.path}" to include region "${child.key}"`);
|
|
383
|
+
}
|
|
384
|
+
const snapshotChild = getNode(machine, String(childSnapshot.path));
|
|
385
|
+
if (snapshotChild.path !== child.path) {
|
|
386
|
+
throw new Error(`Machine expected snapshot "${snapshotChild.path}" to be region "${child.path}"`);
|
|
387
|
+
}
|
|
388
|
+
yield* visit(childSnapshot);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
yield* visit(snapshot);
|
|
393
|
+
const outputs = new Map();
|
|
394
|
+
if (snapshotOutputs !== undefined) {
|
|
395
|
+
for (const { output, path } of snapshotOutputs) {
|
|
396
|
+
if (active.has(path)) {
|
|
397
|
+
outputs.set(path, output);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
active,
|
|
403
|
+
values,
|
|
404
|
+
outputs,
|
|
405
|
+
history: yield* historyFromSnapshotEffect(machine, snapshot)
|
|
406
|
+
};
|
|
407
|
+
});
|
|
408
|
+
export const normalizeConfigurationEffect = (machine, state) => configurationFromSnapshotEffect(machine, state).pipe(Effect.catchCause((cause) => {
|
|
409
|
+
const error = Cause.findErrorOption(cause);
|
|
410
|
+
return Option.isSome(error) && error.value instanceof MachineSchemaDecodeError
|
|
411
|
+
? Effect.fail(error.value)
|
|
412
|
+
: Effect.fail(new MachineSchemaDecodeError({
|
|
413
|
+
machineId: machine.id,
|
|
414
|
+
boundary: "configuration",
|
|
415
|
+
cause
|
|
416
|
+
}));
|
|
417
|
+
}));
|
|
418
|
+
export const validateInitialConfiguration = (machine, configuration) => {
|
|
419
|
+
for (const path of configuration.active) {
|
|
420
|
+
const node = getNode(machine, path);
|
|
421
|
+
if (node.type === "compound") {
|
|
422
|
+
const child = node.children.find((child) => configuration.active.has(child));
|
|
423
|
+
const initialNode = node.initial === undefined ? undefined : getNode(machine, node.initial);
|
|
424
|
+
if (initialNode?.type === "choice" ? child === undefined : child !== node.initial) {
|
|
425
|
+
throw new Error(`Machine initial state "${node.path}" must enter initial child "${node.initial}"`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (node.type === "parallel") {
|
|
429
|
+
for (const child of node.children) {
|
|
430
|
+
if (!configuration.active.has(child)) {
|
|
431
|
+
throw new Error(`Machine initial state "${node.path}" must enter child region "${child}"`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
/** Capture every history register whose owning parent exits in this microstep.
|
|
438
|
+
* The control record is deliberately independent from effects/actions: it is
|
|
439
|
+
* part of the logical snapshot and is therefore preserved by pure planning. */
|
|
440
|
+
export const captureHistory = (machine, current, next, exitPaths) => {
|
|
441
|
+
if (exitPaths.length === 0) {
|
|
442
|
+
return next;
|
|
443
|
+
}
|
|
444
|
+
const exited = new Set(exitPaths);
|
|
445
|
+
const history = new Map(next.history);
|
|
446
|
+
for (const node of machine.stateNodes.byPath.values()) {
|
|
447
|
+
if (node.type !== "history" || node.parent === undefined || !exited.has(node.parent)) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const mode = node.history === "deep" ? "deep" : "shallow";
|
|
451
|
+
const active = new Set();
|
|
452
|
+
for (const ancestor of getPathToRoot(machine, node.parent)) {
|
|
453
|
+
if (current.active.has(ancestor)) {
|
|
454
|
+
active.add(ancestor);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
for (const path of current.active) {
|
|
458
|
+
if (path === node.parent ||
|
|
459
|
+
(mode === "deep" && isDescendantOf(path, node.parent)) ||
|
|
460
|
+
(mode === "shallow" && getNode(machine, path).parent === node.parent)) {
|
|
461
|
+
active.add(path);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
const values = new Map();
|
|
465
|
+
for (const path of active) {
|
|
466
|
+
values.set(path, getActiveValue(current, path));
|
|
467
|
+
}
|
|
468
|
+
history.set(node.path, {
|
|
469
|
+
mode,
|
|
470
|
+
parent: node.parent,
|
|
471
|
+
active,
|
|
472
|
+
values
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
return {
|
|
476
|
+
active: next.active,
|
|
477
|
+
values: next.values,
|
|
478
|
+
outputs: next.outputs,
|
|
479
|
+
history
|
|
480
|
+
};
|
|
481
|
+
};
|
|
482
|
+
export const getHistoryRecord = (configuration, path) => configuration.history.get(path);
|
|
483
|
+
/** Builds the remembered portion of a configuration. Shallow records are
|
|
484
|
+
* intentionally incomplete below their direct child; the planner completes
|
|
485
|
+
* them by invoking only the required typed initializers. */
|
|
486
|
+
export const configurationFromHistoryRecord = (machine, current, record) => {
|
|
487
|
+
const active = new Set(record.active);
|
|
488
|
+
const values = new Map(record.values);
|
|
489
|
+
const outputs = new Map();
|
|
490
|
+
const ancestors = getPathToRoot(machine, record.parent);
|
|
491
|
+
const ancestorSet = new Set(ancestors);
|
|
492
|
+
// A history transition can occur while an ancestor parallel state remains
|
|
493
|
+
// active. Its unaffected regions retain their current configuration.
|
|
494
|
+
for (const ancestor of ancestors) {
|
|
495
|
+
const node = getNode(machine, ancestor);
|
|
496
|
+
if (node.type !== "parallel") {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
for (const child of node.children) {
|
|
500
|
+
if (ancestorSet.has(child) || record.active.has(child) || !current.active.has(child)) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
for (const path of current.active) {
|
|
504
|
+
if (isPathInSubtree(path, child)) {
|
|
505
|
+
active.add(path);
|
|
506
|
+
if (current.values.has(path))
|
|
507
|
+
values.set(path, current.values.get(path));
|
|
508
|
+
if (current.outputs.has(path))
|
|
509
|
+
outputs.set(path, current.outputs.get(path));
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return { active, values, outputs, history: current.history };
|
|
515
|
+
};
|
|
516
|
+
export const configurationFromTargetPathEffect = Effect.fnUntraced(function* (machine, current, path, value, providedValues) {
|
|
517
|
+
const node = getNode(machine, path);
|
|
518
|
+
const active = new Set();
|
|
519
|
+
const values = new Map();
|
|
520
|
+
const outputs = new Map();
|
|
521
|
+
const paths = getPathToRoot(machine, node.path);
|
|
522
|
+
const pathSet = new Set(paths);
|
|
523
|
+
for (const currentPath of paths) {
|
|
524
|
+
const currentNode = getNode(machine, currentPath);
|
|
525
|
+
active.add(currentPath);
|
|
526
|
+
if (currentPath === node.path) {
|
|
527
|
+
values.set(currentPath, yield* decodeStateValue(machine, currentNode, value));
|
|
528
|
+
}
|
|
529
|
+
else if (providedValues !== undefined && hasOwn(providedValues, currentPath)) {
|
|
530
|
+
values.set(currentPath, yield* decodeStateValue(machine, currentNode, providedValues[currentPath]));
|
|
531
|
+
}
|
|
532
|
+
else if (current.values.has(currentPath)) {
|
|
533
|
+
values.set(currentPath, current.values.get(currentPath));
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
throw new Error(`Machine target "${node.path}" requires a value for ancestor state "${currentPath}"`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
for (const ancestor of paths) {
|
|
540
|
+
const ancestorNode = getNode(machine, ancestor);
|
|
541
|
+
if (ancestorNode.type === "parallel") {
|
|
542
|
+
for (const child of ancestorNode.children) {
|
|
543
|
+
if (pathSet.has(child) || !current.active.has(child)) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
for (const activePath of current.active) {
|
|
547
|
+
if (isPathInSubtree(activePath, child)) {
|
|
548
|
+
active.add(activePath);
|
|
549
|
+
if (current.values.has(activePath)) {
|
|
550
|
+
values.set(activePath, current.values.get(activePath));
|
|
551
|
+
}
|
|
552
|
+
if (current.outputs.has(activePath)) {
|
|
553
|
+
outputs.set(activePath, current.outputs.get(activePath));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (node.type === "compound" || node.type === "parallel") {
|
|
561
|
+
throw new Error(`Machine target "${node.path}" must include an active child state`);
|
|
562
|
+
}
|
|
563
|
+
return {
|
|
564
|
+
active,
|
|
565
|
+
values,
|
|
566
|
+
outputs,
|
|
567
|
+
history: current.history
|
|
568
|
+
};
|
|
569
|
+
});
|
|
570
|
+
export const configurationFromTargetSnapshotEffect = Effect.fnUntraced(function* (machine, current, snapshot, providedValues) {
|
|
571
|
+
const subtree = yield* configurationFromSnapshotEffect(machine, snapshot);
|
|
572
|
+
const active = new Set(subtree.active);
|
|
573
|
+
const values = new Map(subtree.values);
|
|
574
|
+
const outputs = new Map(subtree.outputs);
|
|
575
|
+
const paths = getPathToRoot(machine, String(snapshot.path));
|
|
576
|
+
const pathSet = new Set(paths);
|
|
577
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
578
|
+
const node = getNode(machine, ancestor);
|
|
579
|
+
active.add(ancestor);
|
|
580
|
+
if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
|
|
581
|
+
values.set(ancestor, yield* decodeStateValue(machine, node, providedValues[ancestor]));
|
|
582
|
+
}
|
|
583
|
+
else if (current.values.has(ancestor)) {
|
|
584
|
+
values.set(ancestor, current.values.get(ancestor));
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
throw new Error(`Machine target "${snapshot.path}" requires a value for ancestor state "${ancestor}"`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
591
|
+
const ancestorNode = getNode(machine, ancestor);
|
|
592
|
+
if (ancestorNode.type === "parallel") {
|
|
593
|
+
for (const child of ancestorNode.children) {
|
|
594
|
+
if (pathSet.has(child))
|
|
595
|
+
continue;
|
|
596
|
+
if (current.active.has(child)) {
|
|
597
|
+
for (const activePath of current.active) {
|
|
598
|
+
if (isPathInSubtree(activePath, child)) {
|
|
599
|
+
active.add(activePath);
|
|
600
|
+
if (current.values.has(activePath)) {
|
|
601
|
+
values.set(activePath, current.values.get(activePath));
|
|
602
|
+
}
|
|
603
|
+
if (current.outputs.has(activePath)) {
|
|
604
|
+
outputs.set(activePath, current.outputs.get(activePath));
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (providedValues !== undefined && hasOwn(providedValues, child)) {
|
|
611
|
+
for (const [providedPath, providedValue] of Object.entries(providedValues)) {
|
|
612
|
+
if (!isPathInSubtree(providedPath, child))
|
|
613
|
+
continue;
|
|
614
|
+
const providedNode = getNode(machine, providedPath);
|
|
615
|
+
active.add(providedPath);
|
|
616
|
+
values.set(providedPath, yield* decodeStateValue(machine, providedNode, providedValue));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return {
|
|
623
|
+
active,
|
|
624
|
+
values,
|
|
625
|
+
outputs,
|
|
626
|
+
history: new Map([...current.history, ...subtree.history])
|
|
627
|
+
};
|
|
628
|
+
});
|
|
629
|
+
export const normalizeTargetConfigurationEffect = (machine, current, target) => {
|
|
630
|
+
if (isTarget(target)) {
|
|
631
|
+
const snapshot = target[TargetSnapshotTypeId];
|
|
632
|
+
if (snapshot !== undefined) {
|
|
633
|
+
if (String(snapshot.path) !== String(target.path)) {
|
|
634
|
+
throw new Error(`Machine expected target snapshot path to be "${target.path}"`);
|
|
635
|
+
}
|
|
636
|
+
return configurationFromTargetSnapshotEffect(machine, current, snapshot, target.values);
|
|
637
|
+
}
|
|
638
|
+
return configurationFromTargetPathEffect(machine, current, target.path, target.value, target.values);
|
|
639
|
+
}
|
|
640
|
+
if (isSnapshot(target)) {
|
|
641
|
+
return normalizeConfigurationEffect(machine, target).pipe(Effect.map((configuration) => ({
|
|
642
|
+
...configuration,
|
|
643
|
+
history: new Map([...current.history, ...configuration.history])
|
|
644
|
+
})));
|
|
645
|
+
}
|
|
646
|
+
throw new Error("Machine expected transition target to be a snapshot or target builder result");
|
|
647
|
+
};
|
|
648
|
+
const configurationFromTargetPathSync = (machine, current, path, value, providedValues) => {
|
|
649
|
+
const node = getNode(machine, path);
|
|
650
|
+
const active = new Set();
|
|
651
|
+
const values = new Map();
|
|
652
|
+
const outputs = new Map();
|
|
653
|
+
const paths = getPathToRoot(machine, node.path);
|
|
654
|
+
const pathSet = new Set(paths);
|
|
655
|
+
for (const currentPath of paths) {
|
|
656
|
+
const currentNode = getNode(machine, currentPath);
|
|
657
|
+
active.add(currentPath);
|
|
658
|
+
if (currentPath === node.path) {
|
|
659
|
+
values.set(currentPath, decodeStateValueSync(machine, currentNode, value));
|
|
660
|
+
}
|
|
661
|
+
else if (providedValues !== undefined && hasOwn(providedValues, currentPath)) {
|
|
662
|
+
values.set(currentPath, decodeStateValueSync(machine, currentNode, providedValues[currentPath]));
|
|
663
|
+
}
|
|
664
|
+
else if (current.values.has(currentPath)) {
|
|
665
|
+
values.set(currentPath, current.values.get(currentPath));
|
|
666
|
+
}
|
|
667
|
+
else {
|
|
668
|
+
throw new Error(`Machine target "${node.path}" requires a value for ancestor state "${currentPath}"`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
for (const ancestor of paths) {
|
|
672
|
+
const ancestorNode = getNode(machine, ancestor);
|
|
673
|
+
if (ancestorNode.type !== "parallel")
|
|
674
|
+
continue;
|
|
675
|
+
for (const child of ancestorNode.children) {
|
|
676
|
+
if (pathSet.has(child) || !current.active.has(child))
|
|
677
|
+
continue;
|
|
678
|
+
for (const activePath of current.active) {
|
|
679
|
+
if (!isPathInSubtree(activePath, child))
|
|
680
|
+
continue;
|
|
681
|
+
active.add(activePath);
|
|
682
|
+
if (current.values.has(activePath))
|
|
683
|
+
values.set(activePath, current.values.get(activePath));
|
|
684
|
+
if (current.outputs.has(activePath))
|
|
685
|
+
outputs.set(activePath, current.outputs.get(activePath));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (node.type === "compound" || node.type === "parallel") {
|
|
690
|
+
throw new Error(`Machine target "${node.path}" must include an active child state`);
|
|
691
|
+
}
|
|
692
|
+
return { active, values, outputs, history: current.history };
|
|
693
|
+
};
|
|
694
|
+
const configurationFromTargetSnapshotSync = (machine, current, snapshot, providedValues) => {
|
|
695
|
+
const subtree = configurationFromSnapshot(machine, snapshot);
|
|
696
|
+
const active = new Set(subtree.active);
|
|
697
|
+
const values = new Map(subtree.values);
|
|
698
|
+
const outputs = new Map(subtree.outputs);
|
|
699
|
+
const paths = getPathToRoot(machine, String(snapshot.path));
|
|
700
|
+
const pathSet = new Set(paths);
|
|
701
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
702
|
+
const node = getNode(machine, ancestor);
|
|
703
|
+
active.add(ancestor);
|
|
704
|
+
if (providedValues !== undefined && hasOwn(providedValues, ancestor)) {
|
|
705
|
+
values.set(ancestor, decodeStateValueSync(machine, node, providedValues[ancestor]));
|
|
706
|
+
}
|
|
707
|
+
else if (current.values.has(ancestor)) {
|
|
708
|
+
values.set(ancestor, current.values.get(ancestor));
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
throw new Error(`Machine target "${snapshot.path}" requires a value for ancestor state "${ancestor}"`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
for (const ancestor of paths.slice(0, -1)) {
|
|
715
|
+
const ancestorNode = getNode(machine, ancestor);
|
|
716
|
+
if (ancestorNode.type !== "parallel")
|
|
717
|
+
continue;
|
|
718
|
+
for (const child of ancestorNode.children) {
|
|
719
|
+
if (pathSet.has(child))
|
|
720
|
+
continue;
|
|
721
|
+
if (current.active.has(child)) {
|
|
722
|
+
for (const activePath of current.active) {
|
|
723
|
+
if (!isPathInSubtree(activePath, child))
|
|
724
|
+
continue;
|
|
725
|
+
active.add(activePath);
|
|
726
|
+
if (current.values.has(activePath))
|
|
727
|
+
values.set(activePath, current.values.get(activePath));
|
|
728
|
+
if (current.outputs.has(activePath))
|
|
729
|
+
outputs.set(activePath, current.outputs.get(activePath));
|
|
730
|
+
}
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (providedValues !== undefined && hasOwn(providedValues, child)) {
|
|
734
|
+
for (const [providedPath, providedValue] of Object.entries(providedValues)) {
|
|
735
|
+
if (!isPathInSubtree(providedPath, child))
|
|
736
|
+
continue;
|
|
737
|
+
const providedNode = getNode(machine, providedPath);
|
|
738
|
+
active.add(providedPath);
|
|
739
|
+
values.set(providedPath, decodeStateValueSync(machine, providedNode, providedValue));
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return {
|
|
745
|
+
active,
|
|
746
|
+
values,
|
|
747
|
+
outputs,
|
|
748
|
+
history: new Map([...current.history, ...subtree.history])
|
|
749
|
+
};
|
|
750
|
+
};
|
|
751
|
+
export const normalizeTargetConfigurationSync = (machine, current, target) => {
|
|
752
|
+
if (isTarget(target)) {
|
|
753
|
+
const snapshot = target[TargetSnapshotTypeId];
|
|
754
|
+
if (snapshot !== undefined) {
|
|
755
|
+
if (String(snapshot.path) !== String(target.path)) {
|
|
756
|
+
throw new Error(`Machine expected target snapshot path to be "${target.path}"`);
|
|
757
|
+
}
|
|
758
|
+
return configurationFromTargetSnapshotSync(machine, current, snapshot, target.values);
|
|
759
|
+
}
|
|
760
|
+
return configurationFromTargetPathSync(machine, current, target.path, target.value, target.values);
|
|
761
|
+
}
|
|
762
|
+
if (isSnapshot(target)) {
|
|
763
|
+
const configuration = normalizeConfigurationSync(machine, target);
|
|
764
|
+
return { ...configuration, history: new Map([...current.history, ...configuration.history]) };
|
|
765
|
+
}
|
|
766
|
+
throw new Error("Machine expected transition target to be a snapshot or target builder result");
|
|
767
|
+
};
|
|
768
|
+
export const getStateConfigByPath = (machine, path) => machine.handlers[path];
|
|
769
|
+
export const getActiveChildPath = (machine, configuration, path) => getNode(machine, path).children.find((child) => configuration.active.has(child));
|
|
770
|
+
export const isDirectFinalPath = (machine, path) => getNode(machine, path).type === "final";
|
|
771
|
+
export const hasCompletionHandler = (machine, path) => getStateConfigByPath(machine, path)?.onDone !== undefined;
|
|
772
|
+
export const isActiveFinalNode = (machine, configuration, path) => {
|
|
773
|
+
if (!configuration.active.has(path)) {
|
|
774
|
+
return false;
|
|
775
|
+
}
|
|
776
|
+
const node = getNode(machine, path);
|
|
777
|
+
if (node.type === "compound") {
|
|
778
|
+
const child = getActiveChildPath(machine, configuration, path);
|
|
779
|
+
return child !== undefined && isDirectFinalPath(machine, child);
|
|
780
|
+
}
|
|
781
|
+
if (node.type === "parallel") {
|
|
782
|
+
for (const child of node.children) {
|
|
783
|
+
if (!isActiveFinalNode(machine, configuration, child)) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
return isDirectFinalPath(machine, path);
|
|
790
|
+
};
|
|
791
|
+
export const isActiveFinalConfiguration = (machine, configuration) => {
|
|
792
|
+
const root = getRootPath(machine, configuration);
|
|
793
|
+
return isActiveFinalNode(machine, configuration, root) && !hasCompletionHandler(machine, root);
|
|
794
|
+
};
|
|
795
|
+
export const setCompletedOutput = (outputs, path, output) => {
|
|
796
|
+
if (outputs.has(path)) {
|
|
797
|
+
return {
|
|
798
|
+
path,
|
|
799
|
+
output: outputs.get(path),
|
|
800
|
+
isNew: false
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
outputs.set(path, output);
|
|
804
|
+
return {
|
|
805
|
+
path,
|
|
806
|
+
output,
|
|
807
|
+
isNew: true
|
|
808
|
+
};
|
|
809
|
+
};
|
|
810
|
+
export const resolveFinalOutputEffect = Effect.fnUntraced(function* (machine, configuration, path, event, outputs) {
|
|
811
|
+
const node = getNode(machine, path);
|
|
812
|
+
const output = getStateConfigByPath(machine, path)?.output?.({
|
|
813
|
+
state: getActiveValue(configuration, path),
|
|
814
|
+
parent: getParentValue(machine, configuration, path),
|
|
815
|
+
parents: getParentValues(machine, configuration, path),
|
|
816
|
+
event,
|
|
817
|
+
outputs
|
|
818
|
+
});
|
|
819
|
+
return yield* decodeOutputValue(machine, node, output);
|
|
820
|
+
});
|
|
821
|
+
export const completeActiveFinalNodeEffect = Effect.fnUntraced(function* (machine, configuration, path, event, outputs, completions) {
|
|
822
|
+
if (!configuration.active.has(path)) {
|
|
823
|
+
return undefined;
|
|
824
|
+
}
|
|
825
|
+
if (outputs.has(path)) {
|
|
826
|
+
return {
|
|
827
|
+
path,
|
|
828
|
+
output: outputs.get(path),
|
|
829
|
+
isNew: false
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
const node = getNode(machine, path);
|
|
833
|
+
if (node.type === "compound") {
|
|
834
|
+
const child = getActiveChildPath(machine, configuration, path);
|
|
835
|
+
if (child === undefined || !isDirectFinalPath(machine, child)) {
|
|
836
|
+
return undefined;
|
|
837
|
+
}
|
|
838
|
+
const childCompletion = yield* completeActiveFinalNodeEffect(machine, configuration, child, event, outputs, completions);
|
|
839
|
+
if (childCompletion === undefined) {
|
|
840
|
+
return undefined;
|
|
841
|
+
}
|
|
842
|
+
const completion = setCompletedOutput(outputs, path, childCompletion.output);
|
|
843
|
+
if (completion.isNew) {
|
|
844
|
+
completions.push(completion);
|
|
845
|
+
}
|
|
846
|
+
return completion;
|
|
847
|
+
}
|
|
848
|
+
if (node.type === "parallel") {
|
|
849
|
+
const regionOutputs = {};
|
|
850
|
+
let completed = true;
|
|
851
|
+
for (const child of node.children) {
|
|
852
|
+
const childCompletion = yield* completeActiveFinalNodeEffect(machine, configuration, child, event, outputs, completions);
|
|
853
|
+
if (childCompletion === undefined) {
|
|
854
|
+
completed = false;
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
regionOutputs[getNode(machine, child).key] = childCompletion.output;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
if (!completed) {
|
|
861
|
+
return undefined;
|
|
862
|
+
}
|
|
863
|
+
const completion = setCompletedOutput(outputs, path, yield* resolveFinalOutputEffect(machine, configuration, path, event, regionOutputs));
|
|
864
|
+
if (completion.isNew) {
|
|
865
|
+
completions.push(completion);
|
|
866
|
+
}
|
|
867
|
+
return completion;
|
|
868
|
+
}
|
|
869
|
+
if (!isDirectFinalPath(machine, path)) {
|
|
870
|
+
return undefined;
|
|
871
|
+
}
|
|
872
|
+
const completion = setCompletedOutput(outputs, path, yield* resolveFinalOutputEffect(machine, configuration, path, event));
|
|
873
|
+
if (completion.isNew) {
|
|
874
|
+
completions.push(completion);
|
|
875
|
+
}
|
|
876
|
+
return completion;
|
|
877
|
+
});
|
|
878
|
+
export const completeConfigurationEffect = Effect.fnUntraced(function* (machine, configuration, event) {
|
|
879
|
+
const outputs = new Map(configuration.outputs);
|
|
880
|
+
const completions = [];
|
|
881
|
+
const completed = {
|
|
882
|
+
active: configuration.active,
|
|
883
|
+
values: configuration.values,
|
|
884
|
+
outputs,
|
|
885
|
+
history: configuration.history
|
|
886
|
+
};
|
|
887
|
+
for (const path of Array.from(completed.active).sort((left, right) => {
|
|
888
|
+
const depth = pathDepth(machine, right) - pathDepth(machine, left);
|
|
889
|
+
return depth === 0 ? compareDocumentOrder(machine, left, right) : depth;
|
|
890
|
+
})) {
|
|
891
|
+
yield* completeActiveFinalNodeEffect(machine, completed, path, event, outputs, completions);
|
|
892
|
+
}
|
|
893
|
+
return { configuration: completed, completions };
|
|
894
|
+
});
|
|
895
|
+
const resolveFinalOutputSync = (machine, configuration, path, event, outputs) => {
|
|
896
|
+
const node = getNode(machine, path);
|
|
897
|
+
const output = getStateConfigByPath(machine, path)?.output?.({
|
|
898
|
+
state: getActiveValue(configuration, path),
|
|
899
|
+
parent: getParentValue(machine, configuration, path),
|
|
900
|
+
parents: getParentValues(machine, configuration, path),
|
|
901
|
+
event,
|
|
902
|
+
outputs
|
|
903
|
+
});
|
|
904
|
+
return decodeOutputValueSync(machine, node, output);
|
|
905
|
+
};
|
|
906
|
+
const completeActiveFinalNodeSync = (machine, configuration, path, event, outputs, completions) => {
|
|
907
|
+
if (!configuration.active.has(path))
|
|
908
|
+
return undefined;
|
|
909
|
+
if (outputs.has(path))
|
|
910
|
+
return { path, output: outputs.get(path), isNew: false };
|
|
911
|
+
const node = getNode(machine, path);
|
|
912
|
+
if (node.type === "compound") {
|
|
913
|
+
const child = getActiveChildPath(machine, configuration, path);
|
|
914
|
+
if (child === undefined || !isDirectFinalPath(machine, child))
|
|
915
|
+
return undefined;
|
|
916
|
+
const childCompletion = completeActiveFinalNodeSync(machine, configuration, child, event, outputs, completions);
|
|
917
|
+
if (childCompletion === undefined)
|
|
918
|
+
return undefined;
|
|
919
|
+
const completion = setCompletedOutput(outputs, path, childCompletion.output);
|
|
920
|
+
if (completion.isNew)
|
|
921
|
+
completions.push(completion);
|
|
922
|
+
return completion;
|
|
923
|
+
}
|
|
924
|
+
if (node.type === "parallel") {
|
|
925
|
+
const regionOutputs = {};
|
|
926
|
+
let completed = true;
|
|
927
|
+
for (const child of node.children) {
|
|
928
|
+
const childCompletion = completeActiveFinalNodeSync(machine, configuration, child, event, outputs, completions);
|
|
929
|
+
if (childCompletion === undefined)
|
|
930
|
+
completed = false;
|
|
931
|
+
else
|
|
932
|
+
regionOutputs[getNode(machine, child).key] = childCompletion.output;
|
|
933
|
+
}
|
|
934
|
+
if (!completed)
|
|
935
|
+
return undefined;
|
|
936
|
+
const completion = setCompletedOutput(outputs, path, resolveFinalOutputSync(machine, configuration, path, event, regionOutputs));
|
|
937
|
+
if (completion.isNew)
|
|
938
|
+
completions.push(completion);
|
|
939
|
+
return completion;
|
|
940
|
+
}
|
|
941
|
+
if (!isDirectFinalPath(machine, path))
|
|
942
|
+
return undefined;
|
|
943
|
+
const completion = setCompletedOutput(outputs, path, resolveFinalOutputSync(machine, configuration, path, event));
|
|
944
|
+
if (completion.isNew)
|
|
945
|
+
completions.push(completion);
|
|
946
|
+
return completion;
|
|
947
|
+
};
|
|
948
|
+
export const completeConfigurationSync = (machine, configuration, event) => {
|
|
949
|
+
const outputs = new Map(configuration.outputs);
|
|
950
|
+
const completions = [];
|
|
951
|
+
const completed = {
|
|
952
|
+
active: configuration.active,
|
|
953
|
+
values: configuration.values,
|
|
954
|
+
outputs,
|
|
955
|
+
history: configuration.history
|
|
956
|
+
};
|
|
957
|
+
for (const path of Array.from(completed.active).sort((left, right) => {
|
|
958
|
+
const depth = pathDepth(machine, right) - pathDepth(machine, left);
|
|
959
|
+
return depth === 0 ? compareDocumentOrder(machine, left, right) : depth;
|
|
960
|
+
})) {
|
|
961
|
+
completeActiveFinalNodeSync(machine, completed, path, event, outputs, completions);
|
|
962
|
+
}
|
|
963
|
+
return { configuration: completed, completions };
|
|
964
|
+
};
|
|
965
|
+
//# sourceMappingURL=configuration.js.map
|